From 6b00abf05c0825244c66a20627b29805e2e14a4b Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Thu, 12 Jan 2023 20:40:33 -0800 Subject: [PATCH] refactor: :art: rome --- CALCKEY.md | 2 + package.json | 1 + packages/backend/package.json | 2 +- packages/backend/src/@types/hcaptcha.d.ts | 9 +- .../backend/src/@types/http-signature.d.ts | 43 +- .../backend/src/@types/koa-json-body.d.ts | 4 +- packages/backend/src/@types/koa-slow.d.ts | 4 +- packages/backend/src/@types/os-utils.d.ts | 7 +- packages/backend/src/@types/package.json.d.ts | 2 +- .../backend/src/@types/probe-image-size.d.ts | 24 +- packages/backend/src/boot/index.ts | 81 +- packages/backend/src/boot/master.ts | 140 +- packages/backend/src/boot/worker.ts | 10 +- packages/backend/src/config/index.ts | 2 +- packages/backend/src/config/load.ts | 36 +- packages/backend/src/config/types.ts | 3 +- packages/backend/src/const.ts | 57 +- packages/backend/src/daemons/janitor.ts | 6 +- packages/backend/src/daemons/queue-stats.ts | 14 +- packages/backend/src/daemons/server-stats.ts | 12 +- packages/backend/src/db/elasticsearch.ts | 59 +- packages/backend/src/db/logger.ts | 4 +- packages/backend/src/db/postgre.ts | 186 +-- packages/backend/src/db/redis.ts | 4 +- packages/backend/src/env.ts | 13 +- packages/backend/src/index.ts | 6 +- packages/backend/src/mfm/from-html.ts | 154 +- packages/backend/src/mfm/to-html.ts | 85 +- packages/backend/src/misc/acct.ts | 4 +- packages/backend/src/misc/antenna-cache.ts | 20 +- packages/backend/src/misc/api-permissions.ts | 64 +- packages/backend/src/misc/app-lock.ts | 16 +- packages/backend/src/misc/before-shutdown.ts | 21 +- packages/backend/src/misc/cache.ts | 18 +- packages/backend/src/misc/captcha.ts | 44 +- .../backend/src/misc/check-hit-antenna.ts | 117 +- packages/backend/src/misc/check-word-mute.ts | 28 +- packages/backend/src/misc/clone.ts | 10 +- .../backend/src/misc/content-disposition.ts | 9 +- packages/backend/src/misc/convert-host.ts | 10 +- .../backend/src/misc/count-same-renotes.ts | 16 +- packages/backend/src/misc/create-temp.ts | 4 +- packages/backend/src/misc/detect-url-mime.ts | 6 +- .../backend/src/misc/download-text-file.ts | 14 +- packages/backend/src/misc/download-url.ts | 116 +- packages/backend/src/misc/emoji-regex.ts | 2 +- .../misc/extract-custom-emojis-from-mfm.ts | 8 +- packages/backend/src/misc/extract-hashtags.ts | 8 +- packages/backend/src/misc/extract-mentions.ts | 10 +- packages/backend/src/misc/fetch-meta.ts | 18 +- .../backend/src/misc/fetch-proxy-account.ts | 10 +- packages/backend/src/misc/fetch.ts | 114 +- packages/backend/src/misc/gen-id.ts | 32 +- packages/backend/src/misc/gen-identicon.ts | 64 +- packages/backend/src/misc/gen-key-pair.ts | 32 +- packages/backend/src/misc/get-file-info.ts | 219 ++- packages/backend/src/misc/get-ip-hash.ts | 4 +- packages/backend/src/misc/get-note-summary.ts | 12 +- .../backend/src/misc/get-reaction-emoji.ts | 38 +- packages/backend/src/misc/hard-limits.ts | 1 - packages/backend/src/misc/i18n.ts | 2 +- packages/backend/src/misc/id/aid.ts | 8 +- packages/backend/src/misc/id/meid.ts | 4 +- packages/backend/src/misc/id/meidg.ts | 6 +- packages/backend/src/misc/id/object-id.ts | 4 +- .../backend/src/misc/identifiable-error.ts | 2 +- .../src/misc/is-duplicate-key-value-error.ts | 4 +- .../backend/src/misc/is-instance-muted.ts | 20 +- packages/backend/src/misc/is-mime-image.ts | 20 +- packages/backend/src/misc/is-quote.ts | 11 +- packages/backend/src/misc/is-user-related.ts | 5 +- packages/backend/src/misc/keypair-store.ts | 14 +- packages/backend/src/misc/langmap.ts | 884 +++++------ .../backend/src/misc/normalize-for-search.ts | 2 +- packages/backend/src/misc/nyaize.ts | 32 +- packages/backend/src/misc/populate-emojis.ts | 122 +- packages/backend/src/misc/reaction-lib.ts | 39 +- packages/backend/src/misc/schema.ts | 216 +-- packages/backend/src/misc/secure-rndstr.ts | 13 +- .../backend/src/misc/should-block-instance.ts | 17 +- .../backend/src/misc/show-machine-info.ts | 14 +- .../backend/src/misc/skipped-instances.ts | 46 +- packages/backend/src/misc/truncate.ts | 12 +- packages/backend/src/misc/webhook-cache.ts | 22 +- .../src/models/entities/abuse-user-report.ts | 19 +- .../src/models/entities/access-token.ts | 19 +- packages/backend/src/models/entities/ad.ts | 4 +- .../src/models/entities/announcement-read.ts | 19 +- .../src/models/entities/announcement.ts | 4 +- .../src/models/entities/antenna-note.ts | 19 +- .../backend/src/models/entities/antenna.ts | 25 +- packages/backend/src/models/entities/app.ts | 8 +- .../models/entities/attestation-challenge.ts | 15 +- .../src/models/entities/auth-session.ts | 19 +- .../backend/src/models/entities/blocking.ts | 17 +- .../src/models/entities/channel-following.ts | 19 +- .../models/entities/channel-note-pining.ts | 19 +- .../backend/src/models/entities/channel.ts | 19 +- .../backend/src/models/entities/clip-note.ts | 19 +- packages/backend/src/models/entities/clip.ts | 15 +- .../backend/src/models/entities/drive-file.ts | 26 +- .../src/models/entities/drive-folder.ts | 17 +- packages/backend/src/models/entities/emoji.ts | 4 +- .../src/models/entities/follow-request.ts | 17 +- .../backend/src/models/entities/following.ts | 17 +- .../src/models/entities/gallery-like.ts | 19 +- .../src/models/entities/gallery-post.ts | 19 +- .../backend/src/models/entities/hashtag.ts | 18 +- .../backend/src/models/entities/instance.ts | 4 +- .../src/models/entities/messaging-message.ts | 27 +- packages/backend/src/models/entities/meta.ts | 21 +- .../src/models/entities/moderation-log.ts | 15 +- .../backend/src/models/entities/muted-note.ts | 21 +- .../backend/src/models/entities/muting.ts | 17 +- .../src/models/entities/note-favorite.ts | 19 +- .../src/models/entities/note-reaction.ts | 19 +- .../src/models/entities/note-thread-muting.ts | 17 +- .../src/models/entities/note-unread.ts | 25 +- .../src/models/entities/note-watching.ts | 21 +- packages/backend/src/models/entities/note.ts | 37 +- .../src/models/entities/notification.ts | 35 +- .../backend/src/models/entities/page-like.ts | 19 +- packages/backend/src/models/entities/page.ts | 23 +- .../models/entities/password-reset-request.ts | 15 +- .../backend/src/models/entities/poll-vote.ts | 19 +- packages/backend/src/models/entities/poll.ts | 21 +- .../backend/src/models/entities/promo-note.ts | 19 +- .../backend/src/models/entities/promo-read.ts | 19 +- .../models/entities/registration-tickets.ts | 4 +- .../src/models/entities/registry-item.ts | 15 +- packages/backend/src/models/entities/relay.ts | 6 +- .../backend/src/models/entities/signin.ts | 15 +- .../src/models/entities/sw-subscription.ts | 15 +- .../src/models/entities/used-username.ts | 2 +- .../models/entities/user-group-invitation.ts | 19 +- .../src/models/entities/user-group-joining.ts | 19 +- .../backend/src/models/entities/user-group.ts | 15 +- .../backend/src/models/entities/user-ip.ts | 18 +- .../src/models/entities/user-keypair.ts | 8 +- .../src/models/entities/user-list-joining.ts | 19 +- .../backend/src/models/entities/user-list.ts | 15 +- .../src/models/entities/user-note-pining.ts | 19 +- .../src/models/entities/user-pending.ts | 4 +- .../src/models/entities/user-profile.ts | 21 +- .../src/models/entities/user-publickey.ts | 15 +- .../src/models/entities/user-security-key.ts | 15 +- packages/backend/src/models/entities/user.ts | 17 +- .../backend/src/models/entities/webhook.ts | 28 +- packages/backend/src/models/id.ts | 2 +- packages/backend/src/models/index.ts | 196 +-- .../models/repositories/abuse-user-report.ts | 71 +- .../src/models/repositories/antenna.ts | 23 +- .../backend/src/models/repositories/app.ts | 54 +- .../src/models/repositories/auth-session.ts | 17 +- .../src/models/repositories/blocking.ts | 28 +- .../src/models/repositories/channel.ts | 54 +- .../backend/src/models/repositories/clip.ts | 24 +- .../src/models/repositories/drive-file.ts | 198 ++- .../src/models/repositories/drive-folder.ts | 60 +- .../backend/src/models/repositories/emoji.ts | 19 +- .../src/models/repositories/follow-request.ts | 15 +- .../src/models/repositories/following.ts | 45 +- .../src/models/repositories/gallery-like.ts | 21 +- .../src/models/repositories/gallery-post.ts | 34 +- .../src/models/repositories/hashtag.ts | 16 +- .../src/models/repositories/instance.ts | 28 +- .../models/repositories/messaging-message.ts | 81 +- .../models/repositories/moderation-logs.ts | 21 +- .../backend/src/models/repositories/muting.ts | 28 +- .../src/models/repositories/note-favorite.ts | 28 +- .../src/models/repositories/note-reaction.ts | 54 +- .../backend/src/models/repositories/note.ts | 198 ++- .../src/models/repositories/notification.ts | 229 ++- .../src/models/repositories/page-like.ts | 22 +- .../backend/src/models/repositories/page.ts | 68 +- .../backend/src/models/repositories/relay.ts | 7 +- .../backend/src/models/repositories/signin.ts | 8 +- .../repositories/user-group-invitation.ts | 39 +- .../src/models/repositories/user-group.ts | 17 +- .../src/models/repositories/user-list.ts | 17 +- .../backend/src/models/repositories/user.ts | 571 ++++--- packages/backend/src/models/schema/antenna.ts | 107 +- packages/backend/src/models/schema/app.ts | 37 +- .../backend/src/models/schema/blocking.ts | 32 +- packages/backend/src/models/schema/channel.ts | 64 +- packages/backend/src/models/schema/clip.ts | 47 +- .../backend/src/models/schema/drive-file.ts | 134 +- .../backend/src/models/schema/drive-folder.ts | 49 +- packages/backend/src/models/schema/emoji.ts | 45 +- .../src/models/schema/federation-instance.ts | 137 +- .../backend/src/models/schema/following.ts | 46 +- .../backend/src/models/schema/gallery-post.ts | 88 +- packages/backend/src/models/schema/hashtag.ts | 39 +- .../src/models/schema/messaging-message.ts | 96 +- packages/backend/src/models/schema/muting.ts | 39 +- .../src/models/schema/note-favorite.ts | 32 +- .../src/models/schema/note-reaction.ts | 30 +- packages/backend/src/models/schema/note.ts | 226 +-- .../backend/src/models/schema/notification.ts | 81 +- packages/backend/src/models/schema/page.ts | 69 +- packages/backend/src/models/schema/queue.ts | 27 +- .../backend/src/models/schema/user-group.ts | 42 +- .../backend/src/models/schema/user-list.ts | 35 +- packages/backend/src/models/schema/user.ts | 553 ++++--- packages/backend/src/prelude/array.ts | 10 +- packages/backend/src/prelude/await-all.ts | 12 +- packages/backend/src/prelude/string.ts | 2 +- packages/backend/src/prelude/symbol.ts | 2 +- packages/backend/src/prelude/time.ts | 49 +- packages/backend/src/prelude/url.ts | 10 +- packages/backend/src/prelude/xml.ts | 21 +- packages/backend/src/queue/get-job-info.ts | 11 +- packages/backend/src/queue/index.ts | 576 ++++--- packages/backend/src/queue/initialize.ts | 21 +- packages/backend/src/queue/logger.ts | 4 +- .../src/queue/processors/db/delete-account.ts | 62 +- .../queue/processors/db/delete-drive-files.ts | 23 +- .../queue/processors/db/export-blocking.ts | 44 +- .../processors/db/export-custom-emojis.ts | 80 +- .../queue/processors/db/export-following.ts | 67 +- .../src/queue/processors/db/export-mute.ts | 44 +- .../src/queue/processors/db/export-notes.ts | 60 +- .../queue/processors/db/export-user-lists.ts | 43 +- .../queue/processors/db/import-blocking.ts | 50 +- .../processors/db/import-custom-emojis.ts | 63 +- .../queue/processors/db/import-following.ts | 70 +- .../src/queue/processors/db/import-muting.ts | 51 +- .../queue/processors/db/import-user-lists.ts | 68 +- .../backend/src/queue/processors/db/index.ts | 38 +- .../backend/src/queue/processors/deliver.ts | 38 +- .../processors/ended-poll-notification.ts | 33 +- .../backend/src/queue/processors/inbox.ts | 89 +- .../object-storage/clean-remote-files.ts | 23 +- .../processors/object-storage/delete-file.ts | 8 +- .../queue/processors/object-storage/index.ts | 16 +- .../system/check-expired-mutings.ts | 33 +- .../queue/processors/system/clean-charts.ts | 30 +- .../src/queue/processors/system/clean.ts | 21 +- .../src/queue/processors/system/index.ts | 20 +- .../queue/processors/system/resync-charts.ts | 17 +- .../queue/processors/system/tick-charts.ts | 30 +- .../src/queue/processors/webhook-deliver.ts | 50 +- packages/backend/src/queue/queues.ts | 38 +- packages/backend/src/queue/types.ts | 31 +- .../src/remote/activitypub/ap-request.ts | 106 +- .../src/remote/activitypub/audience.ts | 60 +- .../src/remote/activitypub/check-fetch.ts | 51 +- .../src/remote/activitypub/db-resolver.ts | 136 +- .../src/remote/activitypub/deliver-manager.ts | 70 +- .../activitypub/kernel/accept/follow.ts | 21 +- .../remote/activitypub/kernel/accept/index.ts | 18 +- .../remote/activitypub/kernel/add/index.ts | 21 +- .../activitypub/kernel/announce/index.ts | 16 +- .../activitypub/kernel/announce/note.ts | 45 +- .../remote/activitypub/kernel/block/index.ts | 26 +- .../remote/activitypub/kernel/create/index.ts | 32 +- .../remote/activitypub/kernel/create/note.ts | 35 +- .../remote/activitypub/kernel/delete/actor.ts | 15 +- .../remote/activitypub/kernel/delete/index.ts | 72 +- .../remote/activitypub/kernel/delete/note.ts | 27 +- .../remote/activitypub/kernel/flag/index.ts | 30 +- .../src/remote/activitypub/kernel/follow.ts | 19 +- .../src/remote/activitypub/kernel/index.ts | 71 +- .../src/remote/activitypub/kernel/like.ts | 29 +- .../remote/activitypub/kernel/move/index.ts | 86 +- .../src/remote/activitypub/kernel/read.ts | 26 +- .../activitypub/kernel/reject/follow.ts | 23 +- .../remote/activitypub/kernel/reject/index.ts | 18 +- .../remote/activitypub/kernel/remove/index.ts | 21 +- .../remote/activitypub/kernel/undo/accept.ts | 23 +- .../activitypub/kernel/undo/announce.ts | 18 +- .../remote/activitypub/kernel/undo/block.ts | 21 +- .../remote/activitypub/kernel/undo/follow.ts | 27 +- .../remote/activitypub/kernel/undo/index.ts | 37 +- .../remote/activitypub/kernel/undo/like.ts | 15 +- .../remote/activitypub/kernel/update/index.ts | 34 +- .../backend/src/remote/activitypub/logger.ts | 4 +- .../src/remote/activitypub/misc/contexts.ts | 998 ++++++------- .../remote/activitypub/misc/get-note-html.ts | 10 +- .../remote/activitypub/misc/html-to-mfm.ts | 10 +- .../remote/activitypub/misc/ld-signature.ts | 62 +- .../src/remote/activitypub/models/image.ts | 52 +- .../src/remote/activitypub/models/mention.ts | 36 +- .../src/remote/activitypub/models/note.ts | 432 ++++-- .../src/remote/activitypub/models/person.ts | 448 +++--- .../src/remote/activitypub/models/question.ts | 62 +- .../src/remote/activitypub/models/tag.ts | 23 +- .../backend/src/remote/activitypub/perform.ts | 18 +- .../src/remote/activitypub/renderer/accept.ts | 8 +- .../src/remote/activitypub/renderer/add.ts | 6 +- .../remote/activitypub/renderer/announce.ts | 14 +- .../src/remote/activitypub/renderer/block.ts | 8 +- .../src/remote/activitypub/renderer/create.ts | 6 +- .../src/remote/activitypub/renderer/delete.ts | 8 +- .../remote/activitypub/renderer/document.ts | 6 +- .../src/remote/activitypub/renderer/emoji.ts | 15 +- .../src/remote/activitypub/renderer/flag.ts | 17 +- .../activitypub/renderer/follow-relay.ts | 10 +- .../activitypub/renderer/follow-user.ts | 8 +- .../src/remote/activitypub/renderer/follow.ts | 22 +- .../remote/activitypub/renderer/hashtag.ts | 4 +- .../src/remote/activitypub/renderer/image.ts | 6 +- .../src/remote/activitypub/renderer/index.ts | 100 +- .../src/remote/activitypub/renderer/key.ts | 16 +- .../src/remote/activitypub/renderer/like.ts | 32 +- .../remote/activitypub/renderer/mention.ts | 16 +- .../src/remote/activitypub/renderer/note.ts | 140 +- .../renderer/ordered-collection-page.ts | 11 +- .../renderer/ordered-collection.ts | 12 +- .../src/remote/activitypub/renderer/person.ts | 76 +- .../remote/activitypub/renderer/question.ts | 22 +- .../src/remote/activitypub/renderer/read.ts | 13 +- .../src/remote/activitypub/renderer/reject.ts | 8 +- .../src/remote/activitypub/renderer/remove.ts | 8 +- .../remote/activitypub/renderer/tombstone.ts | 2 +- .../src/remote/activitypub/renderer/undo.ts | 14 +- .../src/remote/activitypub/renderer/update.ts | 10 +- .../src/remote/activitypub/renderer/vote.ts | 22 +- .../backend/src/remote/activitypub/request.ts | 18 +- .../src/remote/activitypub/resolver.ts | 135 +- .../backend/src/remote/activitypub/type.ts | 192 ++- packages/backend/src/remote/logger.ts | 4 +- packages/backend/src/remote/resolve-user.ts | 116 +- packages/backend/src/remote/webfinger.ts | 21 +- packages/backend/src/server/activitypub.ts | 148 +- .../src/server/activitypub/featured.ts | 40 +- .../src/server/activitypub/followers.ts | 70 +- .../src/server/activitypub/following.ts | 70 +- .../backend/src/server/activitypub/outbox.ts | 113 +- packages/backend/src/server/api/2fa.ts | 165 +- .../backend/src/server/api/api-handler.ts | 188 ++- .../backend/src/server/api/authenticate.ts | 82 +- packages/backend/src/server/api/call.ts | 190 ++- .../server/api/common/generate-block-query.ts | 64 +- .../api/common/generate-channel-query.ts | 41 +- .../api/common/generate-muted-note-query.ts | 19 +- .../generate-muted-note-thread-query.ts | 31 +- .../api/common/generate-muted-user-query.ts | 100 +- .../api/common/generate-native-user-token.ts | 2 +- .../api/common/generate-replies-query.ts | 64 +- .../api/common/generate-visibility-query.ts | 85 +- .../backend/src/server/api/common/getters.ts | 42 +- .../src/server/api/common/inject-featured.ts | 41 +- .../src/server/api/common/inject-promo.ts | 22 +- .../api/common/make-pagination-query.ts | 40 +- .../api/common/read-messaging-message.ts | 142 +- .../server/api/common/read-notification.ts | 57 +- .../backend/src/server/api/common/signin.ts | 24 +- .../backend/src/server/api/common/signup.ts | 146 +- .../backend/src/server/api/compatibility.ts | 10 +- packages/backend/src/server/api/define.ts | 102 +- packages/backend/src/server/api/endpoints.ts | 1323 +++++++++-------- .../api/endpoints/admin/abuse-user-reports.ts | 140 +- .../api/endpoints/admin/accounts/create.ts | 35 +- .../api/endpoints/admin/accounts/delete.ts | 28 +- .../api/endpoints/admin/accounts/hosted.ts | 71 +- .../server/api/endpoints/admin/ad/create.ts | 34 +- .../server/api/endpoints/admin/ad/delete.ts | 20 +- .../src/server/api/endpoints/admin/ad/list.ts | 23 +- .../server/api/endpoints/admin/ad/update.ts | 43 +- .../endpoints/admin/announcements/create.ts | 68 +- .../endpoints/admin/announcements/delete.ts | 20 +- .../api/endpoints/admin/announcements/list.ts | 88 +- .../endpoints/admin/announcements/update.ts | 26 +- .../api/endpoints/admin/delete-account.ts | 17 +- .../admin/delete-all-files-of-a-user.ts | 14 +- .../admin/drive-capacity-override.ts | 26 +- .../admin/drive/clean-remote-files.ts | 8 +- .../api/endpoints/admin/drive/cleanup.ts | 12 +- .../server/api/endpoints/admin/drive/files.ts | 78 +- .../api/endpoints/admin/drive/show-file.ts | 219 +-- .../endpoints/admin/emoji/add-aliases-bulk.ts | 37 +- .../server/api/endpoints/admin/emoji/add.ts | 42 +- .../server/api/endpoints/admin/emoji/copy.ts | 54 +- .../api/endpoints/admin/emoji/delete-bulk.ts | 36 +- .../api/endpoints/admin/emoji/delete.ts | 28 +- .../api/endpoints/admin/emoji/import-zip.ts | 12 +- .../api/endpoints/admin/emoji/list-remote.ts | 90 +- .../server/api/endpoints/admin/emoji/list.ts | 84 +- .../admin/emoji/remove-aliases-bulk.ts | 39 +- .../endpoints/admin/emoji/set-aliases-bulk.ts | 52 +- .../admin/emoji/set-category-bulk.ts | 47 +- .../api/endpoints/admin/emoji/update.ts | 39 +- .../admin/federation/delete-all-files.ts | 14 +- .../refresh-remote-instance-metadata.ts | 18 +- .../admin/federation/remove-all-following.ts | 26 +- .../admin/federation/update-instance.ts | 27 +- .../api/endpoints/admin/get-index-stats.ts | 12 +- .../api/endpoints/admin/get-table-stats.ts | 21 +- .../api/endpoints/admin/get-user-ips.ts | 16 +- .../src/server/api/endpoints/admin/invite.ts | 26 +- .../src/server/api/endpoints/admin/meta.ts | 470 +++--- .../api/endpoints/admin/moderators/add.ts | 23 +- .../api/endpoints/admin/moderators/remove.ts | 21 +- .../api/endpoints/admin/promo/create.ts | 35 +- .../server/api/endpoints/admin/queue/clear.ts | 12 +- .../endpoints/admin/queue/deliver-delayed.ts | 35 +- .../endpoints/admin/queue/inbox-delayed.ts | 35 +- .../server/api/endpoints/admin/queue/stats.ts | 38 +- .../server/api/endpoints/admin/relays/add.ts | 58 +- .../server/api/endpoints/admin/relays/list.ts | 45 +- .../api/endpoints/admin/relays/remove.ts | 12 +- .../api/endpoints/admin/reset-password.ts | 45 +- .../admin/resolve-abuse-user-report.ts | 30 +- .../server/api/endpoints/admin/send-email.ts | 16 +- .../server/api/endpoints/admin/server-info.ts | 107 +- .../endpoints/admin/show-moderation-logs.ts | 70 +- .../server/api/endpoints/admin/show-user.ts | 31 +- .../server/api/endpoints/admin/show-users.ts | 139 +- .../api/endpoints/admin/silence-user.ts | 27 +- .../api/endpoints/admin/suspend-user.ts | 53 +- .../api/endpoints/admin/unsilence-user.ts | 25 +- .../api/endpoints/admin/unsuspend-user.ts | 20 +- .../server/api/endpoints/admin/update-meta.ts | 292 ++-- .../api/endpoints/admin/update-user-note.ts | 25 +- .../src/server/api/endpoints/admin/vacuum.ts | 24 +- .../src/server/api/endpoints/announcements.ts | 89 +- .../server/api/endpoints/antennas/create.ts | 106 +- .../server/api/endpoints/antennas/delete.ts | 26 +- .../src/server/api/endpoints/antennas/list.ts | 24 +- .../server/api/endpoints/antennas/markread.ts | 41 +- .../server/api/endpoints/antennas/notes.ts | 97 +- .../src/server/api/endpoints/antennas/show.ts | 29 +- .../server/api/endpoints/antennas/update.ts | 112 +- .../src/server/api/endpoints/ap/get.ts | 22 +- .../src/server/api/endpoints/ap/show.ts | 130 +- .../src/server/api/endpoints/app/create.ts | 54 +- .../src/server/api/endpoints/app/show.ts | 29 +- .../src/server/api/endpoints/auth/accept.ts | 33 +- .../api/endpoints/auth/session/generate.ts | 45 +- .../server/api/endpoints/auth/session/show.ts | 44 +- .../api/endpoints/auth/session/userkey.ts | 51 +- .../server/api/endpoints/blocking/create.ts | 52 +- .../server/api/endpoints/blocking/delete.ts | 52 +- .../src/server/api/endpoints/blocking/list.ts | 41 +- .../server/api/endpoints/channels/create.ts | 44 +- .../server/api/endpoints/channels/featured.ts | 28 +- .../server/api/endpoints/channels/follow.ts | 28 +- .../server/api/endpoints/channels/followed.ts | 45 +- .../server/api/endpoints/channels/owned.ts | 43 +- .../src/server/api/endpoints/channels/show.ts | 27 +- .../server/api/endpoints/channels/timeline.ts | 80 +- .../server/api/endpoints/channels/unfollow.ts | 26 +- .../server/api/endpoints/channels/update.ts | 52 +- .../api/endpoints/charts/active-users.ts | 24 +- .../server/api/endpoints/charts/ap-request.ts | 24 +- .../src/server/api/endpoints/charts/drive.ts | 24 +- .../server/api/endpoints/charts/federation.ts | 24 +- .../server/api/endpoints/charts/hashtag.ts | 27 +- .../server/api/endpoints/charts/instance.ts | 27 +- .../src/server/api/endpoints/charts/notes.ts | 24 +- .../server/api/endpoints/charts/user/drive.ts | 27 +- .../api/endpoints/charts/user/following.ts | 27 +- .../server/api/endpoints/charts/user/notes.ts | 27 +- .../api/endpoints/charts/user/reactions.ts | 27 +- .../src/server/api/endpoints/charts/users.ts | 24 +- .../server/api/endpoints/clips/add-note.ts | 45 +- .../src/server/api/endpoints/clips/create.ts | 34 +- .../src/server/api/endpoints/clips/delete.ts | 22 +- .../src/server/api/endpoints/clips/list.ts | 24 +- .../src/server/api/endpoints/clips/notes.ts | 90 +- .../server/api/endpoints/clips/remove-note.ts | 115 +- .../src/server/api/endpoints/clips/show.ts | 31 +- .../src/server/api/endpoints/clips/update.ts | 40 +- .../endpoints/compatibility/custom-emojis.ts | 18 +- .../endpoints/compatibility/instance-info.ts | 225 +-- .../src/server/api/endpoints/custom-motd.ts | 20 +- .../api/endpoints/custom-splash-icons.ts | 20 +- .../backend/src/server/api/endpoints/drive.ts | 32 +- .../src/server/api/endpoints/drive/files.ts | 62 +- .../endpoints/drive/files/attached-notes.ts | 40 +- .../endpoints/drive/files/caption-image.ts | 32 +- .../endpoints/drive/files/check-existence.ts | 21 +- .../api/endpoints/drive/files/create.ts | 159 +- .../api/endpoints/drive/files/delete.ts | 38 +- .../api/endpoints/drive/files/find-by-hash.ts | 28 +- .../server/api/endpoints/drive/files/find.ts | 41 +- .../server/api/endpoints/drive/files/show.ts | 63 +- .../api/endpoints/drive/files/update.ts | 65 +- .../endpoints/drive/files/upload-from-url.ts | 51 +- .../src/server/api/endpoints/drive/folders.ts | 50 +- .../api/endpoints/drive/folders/create.ts | 37 +- .../api/endpoints/drive/folders/delete.ts | 32 +- .../api/endpoints/drive/folders/find.ts | 37 +- .../api/endpoints/drive/folders/show.ts | 29 +- .../api/endpoints/drive/folders/update.ts | 49 +- .../src/server/api/endpoints/drive/stream.ts | 50 +- .../api/endpoints/email-address/available.ts | 27 +- .../src/server/api/endpoints/endpoint.ts | 14 +- .../src/server/api/endpoints/endpoints.ts | 28 +- .../api/endpoints/export-custom-emojis.ts | 8 +- .../api/endpoints/federation/followers.ts | 43 +- .../api/endpoints/federation/following.ts | 43 +- .../api/endpoints/federation/instances.ts | 162 +- .../api/endpoints/federation/show-instance.ts | 32 +- .../server/api/endpoints/federation/stats.ts | 83 +- .../federation/update-remote-user.ts | 14 +- .../server/api/endpoints/federation/users.ts | 43 +- .../src/server/api/endpoints/fetch-rss.ts | 22 +- .../server/api/endpoints/following/create.ts | 72 +- .../server/api/endpoints/following/delete.ts | 52 +- .../api/endpoints/following/invalidate.ts | 52 +- .../endpoints/following/requests/accept.ts | 40 +- .../endpoints/following/requests/cancel.ts | 49 +- .../api/endpoints/following/requests/list.ts | 43 +- .../endpoints/following/requests/reject.ts | 29 +- .../server/api/endpoints/gallery/featured.ts | 30 +- .../server/api/endpoints/gallery/popular.ts | 26 +- .../src/server/api/endpoints/gallery/posts.ts | 35 +- .../api/endpoints/gallery/posts/create.ts | 90 +- .../api/endpoints/gallery/posts/delete.ts | 22 +- .../api/endpoints/gallery/posts/like.ts | 32 +- .../api/endpoints/gallery/posts/show.ts | 27 +- .../api/endpoints/gallery/posts/unlike.ts | 30 +- .../api/endpoints/gallery/posts/update.ts | 91 +- .../api/endpoints/get-online-users-count.ts | 12 +- .../src/server/api/endpoints/hashtags/list.ts | 120 +- .../server/api/endpoints/hashtags/search.ts | 36 +- .../src/server/api/endpoints/hashtags/show.ts | 33 +- .../server/api/endpoints/hashtags/trend.ts | 125 +- .../server/api/endpoints/hashtags/users.ts | 92 +- .../backend/src/server/api/endpoints/i.ts | 15 +- .../src/server/api/endpoints/i/2fa/done.ts | 20 +- .../server/api/endpoints/i/2fa/key-done.ts | 84 +- .../api/endpoints/i/2fa/password-less.ts | 10 +- .../api/endpoints/i/2fa/register-key.ts | 35 +- .../server/api/endpoints/i/2fa/register.ts | 22 +- .../server/api/endpoints/i/2fa/remove-key.ts | 30 +- .../server/api/endpoints/i/2fa/unregister.ts | 14 +- .../src/server/api/endpoints/i/apps.ts | 53 +- .../server/api/endpoints/i/authorized-apps.ts | 24 +- .../server/api/endpoints/i/change-password.ts | 16 +- .../server/api/endpoints/i/delete-account.ts | 16 +- .../server/api/endpoints/i/export-blocking.ts | 8 +- .../api/endpoints/i/export-following.ts | 12 +- .../src/server/api/endpoints/i/export-mute.ts | 8 +- .../server/api/endpoints/i/export-notes.ts | 8 +- .../api/endpoints/i/export-user-lists.ts | 8 +- .../src/server/api/endpoints/i/favorites.ts | 44 +- .../server/api/endpoints/i/gallery/likes.ts | 58 +- .../server/api/endpoints/i/gallery/posts.ts | 41 +- .../endpoints/i/get-word-muted-notes-count.ts | 22 +- .../server/api/endpoints/i/import-blocking.ts | 40 +- .../api/endpoints/i/import-following.ts | 40 +- .../server/api/endpoints/i/import-muting.ts | 40 +- .../api/endpoints/i/import-user-lists.ts | 40 +- .../src/server/api/endpoints/i/known-as.ts | 6 +- .../src/server/api/endpoints/i/move.ts | 115 +- .../server/api/endpoints/i/notifications.ts | 193 ++- .../src/server/api/endpoints/i/page-likes.ts | 53 +- .../src/server/api/endpoints/i/pages.ts | 41 +- .../backend/src/server/api/endpoints/i/pin.ts | 54 +- .../i/read-all-messaging-messages.ts | 50 +- .../api/endpoints/i/read-all-unread-notes.ts | 16 +- .../api/endpoints/i/read-announcement.ts | 30 +- .../api/endpoints/i/regenerate-token.ts | 32 +- .../api/endpoints/i/registry/get-all.ts | 25 +- .../api/endpoints/i/registry/get-detail.ts | 39 +- .../server/api/endpoints/i/registry/get.ts | 39 +- .../endpoints/i/registry/keys-with-type.ts | 45 +- .../server/api/endpoints/i/registry/keys.ts | 29 +- .../server/api/endpoints/i/registry/remove.ts | 39 +- .../server/api/endpoints/i/registry/scopes.ts | 16 +- .../server/api/endpoints/i/registry/set.ts | 37 +- .../server/api/endpoints/i/revoke-token.ts | 14 +- .../server/api/endpoints/i/signin-history.ts | 23 +- .../src/server/api/endpoints/i/unpin.ts | 36 +- .../server/api/endpoints/i/update-email.ts | 51 +- .../src/server/api/endpoints/i/update.ts | 279 ++-- .../api/endpoints/i/user-group-invites.ts | 56 +- .../server/api/endpoints/i/webhooks/create.ts | 38 +- .../server/api/endpoints/i/webhooks/delete.ts | 26 +- .../server/api/endpoints/i/webhooks/list.ts | 10 +- .../server/api/endpoints/i/webhooks/show.ts | 22 +- .../server/api/endpoints/i/webhooks/update.ts | 47 +- .../server/api/endpoints/latest-version.ts | 10 +- .../server/api/endpoints/messaging/history.ts | 88 +- .../api/endpoints/messaging/messages.ts | 156 +- .../endpoints/messaging/messages/create.ts | 101 +- .../endpoints/messaging/messages/delete.ts | 26 +- .../api/endpoints/messaging/messages/read.ts | 45 +- .../backend/src/server/api/endpoints/meta.ts | 443 +++--- .../server/api/endpoints/miauth/gen-token.ts | 42 +- .../src/server/api/endpoints/mute/create.ts | 54 +- .../src/server/api/endpoints/mute/delete.ts | 45 +- .../src/server/api/endpoints/mute/list.ts | 41 +- .../src/server/api/endpoints/my/apps.ts | 34 +- .../backend/src/server/api/endpoints/notes.ts | 86 +- .../server/api/endpoints/notes/children.ts | 57 +- .../src/server/api/endpoints/notes/clips.ts | 45 +- .../api/endpoints/notes/conversation.ts | 49 +- .../src/server/api/endpoints/notes/create.ts | 208 +-- .../src/server/api/endpoints/notes/delete.ts | 41 +- .../api/endpoints/notes/favorites/create.ts | 37 +- .../api/endpoints/notes/favorites/delete.ts | 35 +- .../server/api/endpoints/notes/featured.ts | 71 +- .../api/endpoints/notes/global-timeline.ts | 95 +- .../api/endpoints/notes/hybrid-timeline.ts | 175 ++- .../api/endpoints/notes/local-timeline.ts | 134 +- .../server/api/endpoints/notes/mentions.ts | 98 +- .../endpoints/notes/polls/recommendation.ts | 69 +- .../server/api/endpoints/notes/polls/vote.ts | 112 +- .../server/api/endpoints/notes/reactions.ts | 62 +- .../api/endpoints/notes/reactions/create.ts | 60 +- .../api/endpoints/notes/reactions/delete.ts | 42 +- .../endpoints/notes/recommended-timeline.ts | 138 +- .../src/server/api/endpoints/notes/renotes.ts | 83 +- .../src/server/api/endpoints/notes/replies.ts | 68 +- .../api/endpoints/notes/search-by-tag.ts | 135 +- .../src/server/api/endpoints/notes/search.ts | 183 ++- .../src/server/api/endpoints/notes/show.ts | 39 +- .../src/server/api/endpoints/notes/state.ts | 39 +- .../endpoints/notes/thread-muting/create.ts | 46 +- .../endpoints/notes/thread-muting/delete.ts | 29 +- .../server/api/endpoints/notes/timeline.ts | 175 ++- .../server/api/endpoints/notes/translate.ts | 64 +- .../server/api/endpoints/notes/unrenote.ts | 33 +- .../api/endpoints/notes/user-list-timeline.ts | 154 +- .../api/endpoints/notes/watching/create.ts | 29 +- .../api/endpoints/notes/watching/delete.ts | 29 +- .../api/endpoints/notifications/create.ts | 23 +- .../notifications/mark-all-as-read.ts | 33 +- .../api/endpoints/notifications/read.ts | 33 +- .../src/server/api/endpoints/page-push.ts | 34 +- .../src/server/api/endpoints/pages/create.ts | 125 +- .../src/server/api/endpoints/pages/delete.ts | 28 +- .../server/api/endpoints/pages/featured.ts | 28 +- .../src/server/api/endpoints/pages/like.ts | 32 +- .../src/server/api/endpoints/pages/show.ts | 39 +- .../src/server/api/endpoints/pages/unlike.ts | 30 +- .../src/server/api/endpoints/pages/update.ts | 103 +- .../src/server/api/endpoints/patrons.ts | 14 +- .../backend/src/server/api/endpoints/ping.ts | 16 +- .../src/server/api/endpoints/pinned-users.ts | 48 +- .../src/server/api/endpoints/promo/read.ts | 29 +- .../api/endpoints/recommended-instances.ts | 20 +- .../src/server/api/endpoints/release.ts | 14 +- .../api/endpoints/request-reset-password.ts | 45 +- .../src/server/api/endpoints/reset-db.ts | 22 +- .../server/api/endpoints/reset-password.ts | 26 +- .../src/server/api/endpoints/server-info.ts | 10 +- .../backend/src/server/api/endpoints/stats.ts | 52 +- .../src/server/api/endpoints/sw/register.ts | 43 +- .../src/server/api/endpoints/sw/unregister.ts | 14 +- .../backend/src/server/api/endpoints/test.ts | 20 +- .../api/endpoints/username/available.ts | 26 +- .../backend/src/server/api/endpoints/users.ts | 122 +- .../src/server/api/endpoints/users/clips.ts | 48 +- .../server/api/endpoints/users/followers.ts | 91 +- .../server/api/endpoints/users/following.ts | 91 +- .../api/endpoints/users/gallery/posts.ts | 45 +- .../users/get-frequently-replied-users.ts | 80 +- .../api/endpoints/users/groups/create.ts | 31 +- .../api/endpoints/users/groups/delete.ts | 24 +- .../users/groups/invitations/accept.ts | 28 +- .../users/groups/invitations/reject.ts | 25 +- .../api/endpoints/users/groups/invite.ts | 67 +- .../api/endpoints/users/groups/joined.ts | 36 +- .../api/endpoints/users/groups/leave.ts | 31 +- .../api/endpoints/users/groups/owned.ts | 26 +- .../server/api/endpoints/users/groups/pull.ts | 51 +- .../server/api/endpoints/users/groups/show.ts | 31 +- .../api/endpoints/users/groups/transfer.ts | 53 +- .../api/endpoints/users/groups/update.ts | 33 +- .../api/endpoints/users/lists/create.ts | 29 +- .../api/endpoints/users/lists/delete-all.ts | 22 +- .../api/endpoints/users/lists/delete.ts | 24 +- .../server/api/endpoints/users/lists/list.ts | 26 +- .../server/api/endpoints/users/lists/pull.ts | 43 +- .../server/api/endpoints/users/lists/push.ts | 54 +- .../server/api/endpoints/users/lists/show.ts | 31 +- .../api/endpoints/users/lists/update.ts | 33 +- .../src/server/api/endpoints/users/notes.ts | 156 +- .../src/server/api/endpoints/users/pages.ts | 50 +- .../server/api/endpoints/users/reactions.ts | 65 +- .../api/endpoints/users/recommendation.ts | 63 +- .../server/api/endpoints/users/relation.ts | 119 +- .../api/endpoints/users/report-abuse.ts | 77 +- .../users/search-by-username-and-host.ts | 117 +- .../src/server/api/endpoints/users/search.ts | 165 +- .../src/server/api/endpoints/users/show.ts | 124 +- .../src/server/api/endpoints/users/stats.ts | 232 +-- packages/backend/src/server/api/error.ts | 26 +- packages/backend/src/server/api/index.ts | 103 +- packages/backend/src/server/api/limiter.ts | 138 +- packages/backend/src/server/api/logger.ts | 4 +- .../backend/src/server/api/openapi/errors.ts | 64 +- .../src/server/api/openapi/gen-spec.ts | 223 +-- .../backend/src/server/api/openapi/schemas.ts | 40 +- .../backend/src/server/api/private/signin.ts | 131 +- .../src/server/api/private/signup-pending.ts | 23 +- .../backend/src/server/api/private/signup.ts | 62 +- .../backend/src/server/api/service/discord.ts | 246 +-- .../backend/src/server/api/service/github.ts | 191 ++- .../backend/src/server/api/service/twitter.ts | 119 +- .../backend/src/server/api/stream/channel.ts | 29 +- .../src/server/api/stream/channels/admin.ts | 6 +- .../src/server/api/stream/channels/antenna.ts | 29 +- .../src/server/api/stream/channels/channel.ts | 38 +- .../src/server/api/stream/channels/drive.ts | 6 +- .../api/stream/channels/global-timeline.ts | 50 +- .../src/server/api/stream/channels/hashtag.ts | 28 +- .../api/stream/channels/home-timeline.ts | 46 +- .../api/stream/channels/hybrid-timeline.ts | 69 +- .../src/server/api/stream/channels/index.ts | 32 +- .../api/stream/channels/local-timeline.ts | 43 +- .../src/server/api/stream/channels/main.ts | 31 +- .../api/stream/channels/messaging-index.ts | 6 +- .../server/api/stream/channels/messaging.ts | 62 +- .../server/api/stream/channels/queue-stats.ts | 22 +- .../stream/channels/recommended-timeline.ts | 63 +- .../api/stream/channels/server-stats.ts | 22 +- .../server/api/stream/channels/user-list.ts | 28 +- .../backend/src/server/api/stream/index.ts | 218 ++- .../backend/src/server/api/stream/types.ts | 216 +-- packages/backend/src/server/api/streaming.ts | 56 +- packages/backend/src/server/file/index.ts | 29 +- .../src/server/file/send-drive-file.ts | 123 +- packages/backend/src/server/index.ts | 180 ++- packages/backend/src/server/nodeinfo.ts | 94 +- packages/backend/src/server/proxy/index.ts | 15 +- .../backend/src/server/proxy/proxy-media.ts | 72 +- packages/backend/src/server/web/feed.ts | 33 +- packages/backend/src/server/web/index.ts | 340 +++-- packages/backend/src/server/web/manifest.ts | 12 +- .../backend/src/server/web/url-preview.ts | 56 +- packages/backend/src/server/well-known.ts | 156 +- .../src/services/add-note-to-antenna.ts | 35 +- .../backend/src/services/blocking/create.ts | 84 +- .../backend/src/services/blocking/delete.ts | 23 +- .../src/services/chart/charts/active-users.ts | 43 +- .../src/services/chart/charts/ap-request.ts | 11 +- .../src/services/chart/charts/drive.ts | 37 +- .../chart/charts/entities/active-users.ts | 22 +- .../chart/charts/entities/ap-request.ts | 10 +- .../services/chart/charts/entities/drive.ts | 20 +- .../chart/charts/entities/federation.ts | 20 +- .../services/chart/charts/entities/hashtag.ts | 8 +- .../chart/charts/entities/instance.ts | 52 +- .../services/chart/charts/entities/notes.ts | 32 +- .../chart/charts/entities/per-user-drive.ts | 16 +- .../charts/entities/per-user-following.ts | 28 +- .../chart/charts/entities/per-user-notes.ts | 18 +- .../charts/entities/per-user-reactions.ts | 8 +- .../chart/charts/entities/test-grouped.ts | 10 +- .../charts/entities/test-intersection.ts | 10 +- .../chart/charts/entities/test-unique.ts | 6 +- .../services/chart/charts/entities/test.ts | 10 +- .../services/chart/charts/entities/users.ts | 16 +- .../src/services/chart/charts/federation.ts | 163 +- .../src/services/chart/charts/hashtag.ts | 25 +- .../src/services/chart/charts/instance.ts | 171 ++- .../src/services/chart/charts/notes.ts | 33 +- .../services/chart/charts/per-user-drive.ts | 36 +- .../chart/charts/per-user-following.ts | 59 +- .../services/chart/charts/per-user-notes.ts | 51 +- .../chart/charts/per-user-reactions.ts | 31 +- .../src/services/chart/charts/test-grouped.ts | 22 +- .../chart/charts/test-intersection.ts | 5 +- .../src/services/chart/charts/test-unique.ts | 5 +- .../backend/src/services/chart/charts/test.ts | 15 +- .../src/services/chart/charts/users.ts | 22 +- packages/backend/src/services/chart/core.ts | 635 +++++--- .../backend/src/services/chart/entities.ts | 86 +- packages/backend/src/services/chart/index.ts | 28 +- .../src/services/create-notification.ts | 56 +- .../src/services/create-system-user.ts | 54 +- .../backend/src/services/delete-account.ts | 12 +- .../backend/src/services/detect-sensitive.ts | 31 +- .../backend/src/services/drive/add-file.ts | 372 +++-- .../backend/src/services/drive/delete-file.ts | 34 +- .../drive/generate-video-thumbnail.ts | 25 +- .../src/services/drive/image-processor.ts | 58 +- .../src/services/drive/internal-storage.ts | 15 +- packages/backend/src/services/drive/logger.ts | 4 +- packages/backend/src/services/drive/s3.ts | 19 +- .../src/services/drive/upload-from-url.ts | 41 +- .../src/services/fetch-instance-metadata.ts | 188 ++- .../backend/src/services/following/create.ts | 179 ++- .../backend/src/services/following/delete.ts | 88 +- .../backend/src/services/following/reject.ts | 62 +- .../services/following/requests/accept-all.ts | 14 +- .../src/services/following/requests/accept.ts | 44 +- .../src/services/following/requests/cancel.ts | 41 +- .../src/services/following/requests/create.ts | 56 +- packages/backend/src/services/i/pin.ts | 70 +- packages/backend/src/services/i/update.ts | 22 +- .../src/services/insert-moderation-log.ts | 12 +- .../backend/src/services/instance-actor.ts | 18 +- packages/backend/src/services/logger.ts | 171 ++- .../backend/src/services/messages/create.ts | 122 +- .../backend/src/services/messages/delete.ts | 44 +- packages/backend/src/services/note/create.ts | 996 +++++++------ packages/backend/src/services/note/delete.ts | 134 +- .../backend/src/services/note/polls/update.ts | 24 +- .../backend/src/services/note/polls/vote.ts | 46 +- .../src/services/note/reaction/create.ts | 94 +- .../src/services/note/reaction/delete.ts | 48 +- packages/backend/src/services/note/read.ts | 151 +- packages/backend/src/services/note/unread.ts | 32 +- packages/backend/src/services/note/unwatch.ts | 8 +- packages/backend/src/services/note/watch.ts | 12 +- .../backend/src/services/push-notification.ts | 100 +- .../register-or-fetch-instance-doc.ts | 16 +- packages/backend/src/services/relay.ts | 52 +- .../src/services/send-email-notification.ts | 14 +- packages/backend/src/services/send-email.ts | 41 +- packages/backend/src/services/stream.ts | 203 ++- packages/backend/src/services/suspend-user.ts | 36 +- .../backend/src/services/unsuspend-user.ts | 33 +- .../backend/src/services/update-hashtag.ts | 82 +- packages/backend/src/services/user-cache.ts | 35 +- .../backend/src/services/user-list/push.ts | 18 +- .../services/validate-email-for-account.ts | 46 +- packages/backend/src/types.ts | 26 +- packages/client/package.json | 2 +- packages/client/src/account.ts | 278 ++-- packages/client/src/components/global/i18n.ts | 21 +- packages/client/src/components/index.ts | 74 +- packages/client/src/components/mfm.ts | 694 +++++---- packages/client/src/config.ts | 19 +- packages/client/src/const.ts | 54 +- .../client/src/directives/adaptive-border.ts | 15 +- packages/client/src/directives/anim.ts | 12 +- packages/client/src/directives/appear.ts | 8 +- packages/client/src/directives/click-anime.ts | 6 +- .../client/src/directives/follow-append.ts | 22 +- packages/client/src/directives/get-size.ts | 25 +- packages/client/src/directives/hotkey.ts | 14 +- packages/client/src/directives/index.ts | 50 +- packages/client/src/directives/panel.ts | 21 +- packages/client/src/directives/ripple.ts | 14 +- packages/client/src/directives/size.ts | 50 +- packages/client/src/directives/tooltip.ts | 77 +- .../client/src/directives/user-preview.ts | 51 +- packages/client/src/events.ts | 2 +- packages/client/src/filters/bytes.ts | 12 +- packages/client/src/filters/note.ts | 2 +- packages/client/src/filters/number.ts | 2 +- packages/client/src/filters/user.ts | 8 +- packages/client/src/i18n.ts | 12 +- packages/client/src/init.ts | 330 ++-- packages/client/src/instance.ts | 24 +- packages/client/src/navbar.ts | 171 ++- packages/client/src/nirax.ts | 116 +- packages/client/src/os.ts | 770 ++++++---- packages/client/src/pizzax.ts | 198 ++- packages/client/src/plugin.ts | 163 +- packages/client/src/router.ts | 1156 +++++++------- packages/client/src/scripts/2fa.ts | 33 +- packages/client/src/scripts/aiscript/api.ts | 37 +- packages/client/src/scripts/array.ts | 17 +- packages/client/src/scripts/autocomplete.ts | 110 +- .../client/src/scripts/check-word-mute.ts | 18 +- packages/client/src/scripts/clone.ts | 10 +- .../client/src/scripts/collect-page-vars.ts | 36 +- .../client/src/scripts/copy-to-clipboard.ts | 16 +- packages/client/src/scripts/device-kind.ts | 17 +- packages/client/src/scripts/emojilist.ts | 16 +- .../extract-avg-color-from-blurhash.ts | 16 +- .../client/src/scripts/extract-mentions.ts | 10 +- .../src/scripts/extract-url-from-mfm.ts | 21 +- packages/client/src/scripts/focus.ts | 8 +- packages/client/src/scripts/form.ts | 115 +- .../client/src/scripts/format-time-string.ts | 95 +- .../client/src/scripts/gen-search-query.ts | 26 +- .../client/src/scripts/get-account-from-id.ts | 8 +- packages/client/src/scripts/get-note-menu.ts | 526 ++++--- .../client/src/scripts/get-note-summary.ts | 10 +- .../src/scripts/get-static-image-url.ts | 10 +- packages/client/src/scripts/get-user-menu.ts | 330 ++-- packages/client/src/scripts/get-user-name.ts | 5 +- packages/client/src/scripts/hotkey.ts | 96 +- packages/client/src/scripts/hpml/block.ts | 53 +- packages/client/src/scripts/hpml/evaluator.ts | 141 +- packages/client/src/scripts/hpml/expr.ts | 28 +- packages/client/src/scripts/hpml/index.ts | 104 +- packages/client/src/scripts/hpml/lib.ts | 486 ++++-- .../client/src/scripts/hpml/type-checker.ts | 75 +- packages/client/src/scripts/i18n.ts | 4 +- packages/client/src/scripts/idb-proxy.ts | 16 +- packages/client/src/scripts/initialize-sw.ts | 83 +- .../client/src/scripts/is-device-darkmode.ts | 2 +- packages/client/src/scripts/keycode.ts | 22 +- packages/client/src/scripts/langmap.ts | 884 +++++------ packages/client/src/scripts/login-id.ts | 4 +- packages/client/src/scripts/lookup-user.ts | 20 +- packages/client/src/scripts/mfm-tags.ts | 19 +- packages/client/src/scripts/page-metadata.ts | 29 +- packages/client/src/scripts/physics.ts | 75 +- packages/client/src/scripts/please-login.ts | 33 +- packages/client/src/scripts/popout.ts | 33 +- packages/client/src/scripts/popup-position.ts | 94 +- .../client/src/scripts/reaction-picker.ts | 44 +- packages/client/src/scripts/reduced-motion.ts | 2 +- packages/client/src/scripts/scroll.ts | 61 +- packages/client/src/scripts/search.ts | 31 +- packages/client/src/scripts/select-file.ts | 116 +- .../src/scripts/show-suspended-dialog.ts | 8 +- packages/client/src/scripts/shuffle.ts | 7 +- packages/client/src/scripts/sound.ts | 15 +- packages/client/src/scripts/sticky-sidebar.ts | 56 +- packages/client/src/scripts/theme-editor.ts | 87 +- packages/client/src/scripts/theme.ts | 137 +- packages/client/src/scripts/time.ts | 49 +- packages/client/src/scripts/timezones.ts | 111 +- packages/client/src/scripts/touch.ts | 37 +- packages/client/src/scripts/twemoji-base.ts | 10 +- packages/client/src/scripts/unison-reload.ts | 4 +- packages/client/src/scripts/upload.ts | 87 +- packages/client/src/scripts/url.ts | 10 +- .../client/src/scripts/use-chart-tooltip.ts | 24 +- packages/client/src/scripts/use-interval.ts | 14 +- .../client/src/scripts/use-leave-guard.ts | 7 +- .../client/src/scripts/use-note-capture.ts | 50 +- packages/client/src/scripts/use-tooltip.ts | 35 +- packages/client/src/store.ts | 578 +++---- packages/client/src/stream.ts | 21 +- packages/client/src/theme-store.ts | 31 +- packages/client/src/types/menu.ts | 86 +- packages/client/src/ui/_common_/sw-inject.ts | 26 +- packages/client/src/ui/deck/deck-store.ts | 230 +-- packages/client/src/widgets/index.ts | 151 +- packages/client/src/widgets/widget.ts | 34 +- packages/sw/package.json | 2 +- packages/sw/src/filters/user.ts | 6 +- .../sw/src/scripts/create-notification.ts | 463 +++--- .../sw/src/scripts/get-account-from-id.ts | 8 +- packages/sw/src/scripts/get-user-name.ts | 5 +- packages/sw/src/scripts/i18n.ts | 4 +- packages/sw/src/scripts/lang.ts | 16 +- packages/sw/src/scripts/login-id.ts | 4 +- packages/sw/src/scripts/notification-read.ts | 30 +- packages/sw/src/scripts/operations.ts | 56 +- packages/sw/src/scripts/twemoji-base.ts | 10 +- packages/sw/src/scripts/url.ts | 10 +- packages/sw/src/sw.ts | 410 ++--- packages/sw/src/types.ts | 10 +- pnpm-lock.yaml | 64 + rome.json | 9 + 940 files changed, 33578 insertions(+), 24085 deletions(-) create mode 100644 rome.json diff --git a/CALCKEY.md b/CALCKEY.md index 4ce4bba7c..015232f70 100644 --- a/CALCKEY.md +++ b/CALCKEY.md @@ -106,6 +106,8 @@ - New post style - Admins set default reaction emoji - Allows custom emoji +- Fix lint errors +- Use Rome instead of ESLint - MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1) - [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996) - [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056) diff --git a/package.json b/package.json index 409924d89..fa9c2ac2d 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "cross-env": "7.0.3", "cypress": "10.11.0", "install-peers": "^1.0.4", + "rome": "^11.0.0", "start-server-and-test": "1.15.2", "typescript": "4.9.4", "vue-eslint-parser": "^9.1.0" diff --git a/packages/backend/package.json b/packages/backend/package.json index 742227fdb..fe19ba2d2 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -10,7 +10,7 @@ "revertmigration": "typeorm migration:revert -d ormconfig.js", "build": "pnpm swc src -d built -D", "watch": "pnpm swc src -d built -D -w", - "lint": "eslint --quiet \"src/**/*.ts\"", + "lint": "pnpm eslint --quiet \"src/**/*.ts\"", "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "test": "pnpm run mocha" }, diff --git a/packages/backend/src/@types/hcaptcha.d.ts b/packages/backend/src/@types/hcaptcha.d.ts index afed58756..21f65c678 100644 --- a/packages/backend/src/@types/hcaptcha.d.ts +++ b/packages/backend/src/@types/hcaptcha.d.ts @@ -1,11 +1,14 @@ -declare module 'hcaptcha' { +declare module "hcaptcha" { interface IVerifyResponse { success: boolean; challenge_ts: string; hostname: string; credit?: boolean; - 'error-codes'?: unknown[]; + "error-codes"?: unknown[]; } - export function verify(secret: string, token: string): Promise; + export function verify( + secret: string, + token: string, + ): Promise; } diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts index d1f9cd955..3bfece8cb 100644 --- a/packages/backend/src/@types/http-signature.d.ts +++ b/packages/backend/src/@types/http-signature.d.ts @@ -1,5 +1,5 @@ -declare module '@peertube/http-signature' { - import { IncomingMessage, ClientRequest } from 'node:http'; +declare module "@peertube/http-signature" { + import type { IncomingMessage, ClientRequest } from "node:http"; interface ISignature { keyId: string; @@ -28,8 +28,8 @@ declare module '@peertube/http-signature' { } type RequestSignerConstructorOptions = - IRequestSignerConstructorOptionsFromProperties | - IRequestSignerConstructorOptionsFromFunction; + | IRequestSignerConstructorOptionsFromProperties + | IRequestSignerConstructorOptionsFromFunction; interface IRequestSignerConstructorOptionsFromProperties { keyId: string; @@ -59,11 +59,23 @@ declare module '@peertube/http-signature' { httpVersion?: string; } - export function parse(request: IncomingMessage, options?: IParseRequestOptions): IParsedSignature; - export function parseRequest(request: IncomingMessage, options?: IParseRequestOptions): IParsedSignature; + export function parse( + request: IncomingMessage, + options?: IParseRequestOptions, + ): IParsedSignature; + export function parseRequest( + request: IncomingMessage, + options?: IParseRequestOptions, + ): IParsedSignature; - export function sign(request: ClientRequest, options: ISignRequestOptions): boolean; - export function signRequest(request: ClientRequest, options: ISignRequestOptions): boolean; + export function sign( + request: ClientRequest, + options: ISignRequestOptions, + ): boolean; + export function signRequest( + request: ClientRequest, + options: ISignRequestOptions, + ): boolean; export function createSigner(): RequestSigner; export function isSigner(obj: any): obj is RequestSigner; @@ -71,7 +83,16 @@ declare module '@peertube/http-signature' { export function sshKeyFingerprint(key: string): string; export function pemToRsaSSHKey(pem: string, comment: string): string; - export function verify(parsedSignature: IParsedSignature, pubkey: string | Buffer): boolean; - export function verifySignature(parsedSignature: IParsedSignature, pubkey: string | Buffer): boolean; - export function verifyHMAC(parsedSignature: IParsedSignature, secret: string): boolean; + export function verify( + parsedSignature: IParsedSignature, + pubkey: string | Buffer, + ): boolean; + export function verifySignature( + parsedSignature: IParsedSignature, + pubkey: string | Buffer, + ): boolean; + export function verifyHMAC( + parsedSignature: IParsedSignature, + secret: string, + ): boolean; } diff --git a/packages/backend/src/@types/koa-json-body.d.ts b/packages/backend/src/@types/koa-json-body.d.ts index 5aa8179c5..e5282d81b 100644 --- a/packages/backend/src/@types/koa-json-body.d.ts +++ b/packages/backend/src/@types/koa-json-body.d.ts @@ -1,5 +1,5 @@ -declare module 'koa-json-body' { - import { Middleware } from 'koa'; +declare module "koa-json-body" { + import type { Middleware } from "koa"; interface IKoaJsonBodyOptions { strict: boolean; diff --git a/packages/backend/src/@types/koa-slow.d.ts b/packages/backend/src/@types/koa-slow.d.ts index e748e2cc9..e24be51e2 100644 --- a/packages/backend/src/@types/koa-slow.d.ts +++ b/packages/backend/src/@types/koa-slow.d.ts @@ -1,5 +1,5 @@ -declare module 'koa-slow' { - import { Middleware } from 'koa'; +declare module "koa-slow" { + import type { Middleware } from "koa"; interface ISlowOptions { url?: RegExp; diff --git a/packages/backend/src/@types/os-utils.d.ts b/packages/backend/src/@types/os-utils.d.ts index 390df17d3..504096ae2 100644 --- a/packages/backend/src/@types/os-utils.d.ts +++ b/packages/backend/src/@types/os-utils.d.ts @@ -1,4 +1,4 @@ -declare module 'os-utils' { +declare module "os-utils" { type FreeCommandCallback = (usedmem: number) => void; type HarddriveCallback = (total: number, free: number, used: number) => void; @@ -20,7 +20,10 @@ declare module 'os-utils' { export function harddrive(callback: HarddriveCallback): void; export function getProcesses(callback: GetProcessesCallback): void; - export function getProcesses(nProcess: number, callback: GetProcessesCallback): void; + export function getProcesses( + nProcess: number, + callback: GetProcessesCallback, + ): void; export function allLoadavg(): string; export function loadavg(_time?: number): number; diff --git a/packages/backend/src/@types/package.json.d.ts b/packages/backend/src/@types/package.json.d.ts index abe5fae68..d8ec63644 100644 --- a/packages/backend/src/@types/package.json.d.ts +++ b/packages/backend/src/@types/package.json.d.ts @@ -1,4 +1,4 @@ -declare module '*/package.json' { +declare module "*/package.json" { interface IRepository { type: string; url: string; diff --git a/packages/backend/src/@types/probe-image-size.d.ts b/packages/backend/src/@types/probe-image-size.d.ts index 11bb6c620..4ed13df7f 100644 --- a/packages/backend/src/@types/probe-image-size.d.ts +++ b/packages/backend/src/@types/probe-image-size.d.ts @@ -1,5 +1,5 @@ -declare module 'probe-image-size' { - import { ReadStream } from 'node:fs'; +declare module "probe-image-size" { + import type { ReadStream } from "node:fs"; type ProbeOptions = { retries: 1; @@ -12,14 +12,24 @@ declare module 'probe-image-size' { length?: number; type: string; mime: string; - wUnits: 'in' | 'mm' | 'cm' | 'pt' | 'pc' | 'px' | 'em' | 'ex'; - hUnits: 'in' | 'mm' | 'cm' | 'pt' | 'pc' | 'px' | 'em' | 'ex'; + wUnits: "in" | "mm" | "cm" | "pt" | "pc" | "px" | "em" | "ex"; + hUnits: "in" | "mm" | "cm" | "pt" | "pc" | "px" | "em" | "ex"; url?: string; }; - function probeImageSize(src: string | ReadStream, options?: ProbeOptions): Promise; - function probeImageSize(src: string | ReadStream, callback: (err: Error | null, result?: ProbeResult) => void): void; - function probeImageSize(src: string | ReadStream, options: ProbeOptions, callback: (err: Error | null, result?: ProbeResult) => void): void; + function probeImageSize( + src: string | ReadStream, + options?: ProbeOptions, + ): Promise; + function probeImageSize( + src: string | ReadStream, + callback: (err: Error | null, result?: ProbeResult) => void, + ): void; + function probeImageSize( + src: string | ReadStream, + options: ProbeOptions, + callback: (err: Error | null, result?: ProbeResult) => void, + ): void; namespace probeImageSize {} // Hack diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 185dab673..4e1d94765 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -1,79 +1,78 @@ -import cluster from 'node:cluster'; -import chalk from 'chalk'; -import Xev from 'xev'; +import cluster from "node:cluster"; +import chalk from "chalk"; +import Xev from "xev"; -import Logger from '@/services/logger.js'; -import { envOption } from '../env.js'; +import Logger from "@/services/logger.js"; +import { envOption } from "../env.js"; // for typeorm -import 'reflect-metadata'; -import { masterMain } from './master.js'; -import { workerMain } from './worker.js'; +import "reflect-metadata"; +import { masterMain } from "./master.js"; +import { workerMain } from "./worker.js"; -const logger = new Logger('core', 'cyan'); -const clusterLogger = logger.createSubLogger('cluster', 'orange', false); +const logger = new Logger("core", "cyan"); +const clusterLogger = logger.createSubLogger("cluster", "orange", false); const ev = new Xev(); /** * Init process */ -export default async function() { - process.title = `Calckey (${cluster.isPrimary ? 'master' : 'worker'})`; - - if (cluster.isPrimary || envOption.disableClustering) { +export default async function () { + process.title = `Calckey (${cluster.isPrimary ? "master" : "worker"})`; + + if (cluster.isPrimary || envOption.disableClustering) { await masterMain(); - if (cluster.isPrimary) { - ev.mount(); + ev.mount(); } - } - - if (cluster.isWorker || envOption.disableClustering) { + } + + if (cluster.isWorker || envOption.disableClustering) { await workerMain(); - } - - // For when Calckey is started in a child process during unit testing. - // Otherwise, process.send cannot be used, so start it. - if (process.send) { - process.send('ok'); - } + } + + // For when Calckey is started in a child process during unit testing. + // Otherwise, process.send cannot be used, so start it. + if (process.send) { + process.send("ok"); + } } //#region Events // Listen new workers -cluster.on('fork', worker => { - clusterLogger.debug(`Process forked: [${worker.id}]`); +cluster.on("fork", (worker) => { + clusterLogger.debug(`Process forked: [${worker.id}]`); }); // Listen online workers -cluster.on('online', worker => { - clusterLogger.debug(`Process is now online: [${worker.id}]`); +cluster.on("online", (worker) => { + clusterLogger.debug(`Process is now online: [${worker.id}]`); }); // Listen for dying workers -cluster.on('exit', worker => { - // Replace the dead worker, - // we're not sentimental - clusterLogger.error(chalk.red(`[${worker.id}] died :(`)); - cluster.fork(); +cluster.on("exit", (worker) => { + // Replace the dead worker, + // we're not sentimental + clusterLogger.error(chalk.red(`[${worker.id}] died :(`)); + cluster.fork(); }); // Display detail of unhandled promise rejection if (!envOption.quiet) { - process.on('unhandledRejection', console.dir); + process.on("unhandledRejection", console.dir); } // Display detail of uncaught exception -process.on('uncaughtException', err => { - try { +process.on("uncaughtException", (err) => { + try { logger.error(err); - } catch { } + } catch {} }); // Dying away... -process.on('exit', code => { - logger.info(`The process is going to exit with code ${code}`); +process.on("exit", (code) => { + logger.info(`The process is going to exit with code ${code}`); }); //#endregion diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index f8e4059e7..193f02429 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -1,50 +1,64 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import * as os from 'node:os'; -import cluster from 'node:cluster'; -import chalk from 'chalk'; -import chalkTemplate from 'chalk-template'; -import semver from 'semver'; +import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import * as os from "node:os"; +import cluster from "node:cluster"; +import chalk from "chalk"; +import chalkTemplate from "chalk-template"; +import semver from "semver"; -import Logger from '@/services/logger.js'; -import loadConfig from '@/config/load.js'; -import { Config } from '@/config/types.js'; -import { lessThan } from '@/prelude/array.js'; -import { envOption } from '../env.js'; -import { showMachineInfo } from '@/misc/show-machine-info.js'; -import { db, initDb } from '../db/postgre.js'; +import Logger from "@/services/logger.js"; +import loadConfig from "@/config/load.js"; +import type { Config } from "@/config/types.js"; +import { lessThan } from "@/prelude/array.js"; +import { envOption } from "../env.js"; +import { showMachineInfo } from "@/misc/show-machine-info.js"; +import { db, initDb } from "../db/postgre.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); +const meta = JSON.parse( + fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"), +); -const logger = new Logger('core', 'cyan'); -const bootLogger = logger.createSubLogger('boot', 'magenta', false); +const logger = new Logger("core", "cyan"); +const bootLogger = logger.createSubLogger("boot", "magenta", false); -const themeColor = chalk.hex('#31748f'); +const themeColor = chalk.hex("#31748f"); function greet() { if (!envOption.quiet) { //#region Calckey logo const v = `v${meta.version}`; - console.log(themeColor(' ___ _ _ ')); - console.log(themeColor(' / __\\__ _| | ___| | _____ _ _ ')); - console.log(themeColor(' / / / _` | |/ __| |/ / _ \ | | |')); - console.log(themeColor('/ /__| (_| | | (__| < __/ |_| |')); - console.log(themeColor('\\____/\\__,_|_|\\___|_|\\_\\___|\\__, |')); - console.log(themeColor(' (___/ ')); + console.log(themeColor(" ___ _ _ ")); + console.log(themeColor(" / __\\__ _| | ___| | _____ _ _ ")); + console.log(themeColor(" / / / _` | |/ __| |/ / _ | | |")); + console.log(themeColor("/ /__| (_| | | (__| < __/ |_| |")); + console.log(themeColor("\\____/\\__,_|_|\\___|_|\\_\\___|\\__, |")); + console.log(themeColor(" (___/ ")); //#endregion - console.log(' Calckey is an open-source decentralized microblogging platform.'); - console.log(chalk.rgb(255, 136, 0)(' If you like Calckey, please consider starring or contributing to the repo. https://codeberg.org/calckey/calckey')); + console.log( + " Calckey is an open-source decentralized microblogging platform.", + ); + console.log( + chalk.rgb( + 255, + 136, + 0, + )( + " If you like Calckey, please consider starring or contributing to the repo. https://codeberg.org/calckey/calckey", + ), + ); - console.log(''); - console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`); + console.log(""); + console.log( + chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`, + ); } - bootLogger.info('Welcome to Calckey!'); + bootLogger.info("Welcome to Calckey!"); bootLogger.info(`Calckey v${meta.version}`, null, true); } @@ -63,42 +77,50 @@ export async function masterMain() { config = loadConfigBoot(); await connectDb(); } catch (e) { - bootLogger.error('Fatal error occurred during initialization', null, true); + bootLogger.error("Fatal error occurred during initialization", null, true); process.exit(1); } - bootLogger.succ('Calckey initialized'); + bootLogger.succ("Calckey initialized"); if (!envOption.disableClustering) { await spawnWorkers(config.clusterLimit); } - bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); + bootLogger.succ( + `Now listening on port ${config.port} on ${config.url}`, + null, + true, + ); if (!envOption.noDaemons) { - import('../daemons/server-stats.js').then(x => x.default()); - import('../daemons/queue-stats.js').then(x => x.default()); - import('../daemons/janitor.js').then(x => x.default()); + import("../daemons/server-stats.js").then((x) => x.default()); + import("../daemons/queue-stats.js").then((x) => x.default()); + import("../daemons/janitor.js").then((x) => x.default()); } } function showEnvironment(): void { const env = process.env.NODE_ENV; - const logger = bootLogger.createSubLogger('env'); - logger.info(typeof env === 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`); + const logger = bootLogger.createSubLogger("env"); + logger.info( + typeof env === "undefined" ? "NODE_ENV is not set" : `NODE_ENV: ${env}`, + ); - if (env !== 'production') { - logger.warn('The environment is not in production mode.'); - logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true); + if (env !== "production") { + logger.warn("The environment is not in production mode."); + logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true); } } function showNodejsVersion(): void { - const nodejsLogger = bootLogger.createSubLogger('nodejs'); + const nodejsLogger = bootLogger.createSubLogger("nodejs"); nodejsLogger.info(`Version ${process.version} detected.`); - const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim(); + const minVersion = fs + .readFileSync(`${_dirname}/../../../../.node-version`, "utf-8") + .trim(); if (semver.lt(process.version, minVersion)) { nodejsLogger.error(`At least Node.js ${minVersion} required!`); process.exit(1); @@ -106,14 +128,14 @@ function showNodejsVersion(): void { } function loadConfigBoot(): Config { - const configLogger = bootLogger.createSubLogger('config'); + const configLogger = bootLogger.createSubLogger("config"); let config; try { config = loadConfig(); } catch (exception) { - if (exception.code === 'ENOENT') { - configLogger.error('Configuration file not found', null, true); + if (exception.code === "ENOENT") { + configLogger.error("Configuration file not found", null, true); process.exit(1); } else if (e instanceof Error) { configLogger.error(e.message); @@ -122,22 +144,24 @@ function loadConfigBoot(): Config { throw exception; } - configLogger.succ('Loaded'); + configLogger.succ("Loaded"); return config; } async function connectDb(): Promise { - const dbLogger = bootLogger.createSubLogger('db'); + const dbLogger = bootLogger.createSubLogger("db"); // Try to connect to DB try { - dbLogger.info('Connecting...'); + dbLogger.info("Connecting..."); await initDb(); - const v = await db.query('SHOW server_version').then(x => x[0].server_version); + const v = await db + .query("SHOW server_version") + .then((x) => x[0].server_version); dbLogger.succ(`Connected: v${v}`); } catch (e) { - dbLogger.error('Cannot connect', null, true); + dbLogger.error("Cannot connect", null, true); dbLogger.error(e); process.exit(1); } @@ -145,20 +169,20 @@ async function connectDb(): Promise { async function spawnWorkers(limit: number = 1) { const workers = Math.min(limit, os.cpus().length); - bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); + bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`); await Promise.all([...Array(workers)].map(spawnWorker)); - bootLogger.succ('All workers started'); + bootLogger.succ("All workers started"); } function spawnWorker(): Promise { - return new Promise(res => { + return new Promise((res) => { const worker = cluster.fork(); - worker.on('message', message => { - if (message === 'listenFailed') { - bootLogger.error(`The server Listen failed due to the previous error.`); + worker.on("message", (message) => { + if (message === "listenFailed") { + bootLogger.error("The server Listen failed due to the previous error."); process.exit(1); } - if (message !== 'ready') return; + if (message !== "ready") return; res(); }); }); diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts index 8038e2563..70442b096 100644 --- a/packages/backend/src/boot/worker.ts +++ b/packages/backend/src/boot/worker.ts @@ -1,5 +1,5 @@ -import cluster from 'node:cluster'; -import { initDb } from '../db/postgre.js'; +import cluster from "node:cluster"; +import { initDb } from "../db/postgre.js"; /** * Init worker process @@ -8,13 +8,13 @@ export async function workerMain() { await initDb(); // start server - await import('../server/index.js').then(x => x.default()); + await import("../server/index.js").then((x) => x.default()); // start job queue - import('../queue/index.js').then(x => x.default()); + import("../queue/index.js").then((x) => x.default()); if (cluster.isWorker) { // Send a 'ready' message to parent process - process.send!('ready'); + process.send!("ready"); } } diff --git a/packages/backend/src/config/index.ts b/packages/backend/src/config/index.ts index 3e53b0003..ae197b09c 100644 --- a/packages/backend/src/config/index.ts +++ b/packages/backend/src/config/index.ts @@ -1,3 +1,3 @@ -import load from './load.js'; +import load from "./load.js"; export default load(); diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts index c479ccc40..9b8ee5edb 100644 --- a/packages/backend/src/config/load.ts +++ b/packages/backend/src/config/load.ts @@ -2,11 +2,11 @@ * Config loader */ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import * as yaml from 'js-yaml'; -import type { Source, Mixin } from './types.js'; +import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import * as yaml from "js-yaml"; +import type { Source, Mixin } from "./types.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -19,14 +19,20 @@ const dir = `${_dirname}/../../../../.config`; /** * Path of configuration file */ -const path = process.env.NODE_ENV === 'test' - ? `${dir}/test.yml` - : `${dir}/default.yml`; +const path = + process.env.NODE_ENV === "test" ? `${dir}/test.yml` : `${dir}/default.yml`; export default function load() { - const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); - const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8')); - const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; + const meta = JSON.parse( + fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"), + ); + const clientManifest = JSON.parse( + fs.readFileSync( + `${_dirname}/../../../../built/_client_dist_/manifest.json`, + "utf-8", + ), + ); + const config = yaml.load(fs.readFileSync(path, "utf-8")) as Source; const mixin = {} as Mixin; @@ -34,19 +40,19 @@ export default function load() { config.url = url.origin; - config.port = config.port || parseInt(process.env.PORT || '', 10); + config.port = config.port || parseInt(process.env.PORT || "", 10); mixin.version = meta.version; mixin.host = url.host; mixin.hostname = url.hostname; - mixin.scheme = url.protocol.replace(/:$/, ''); - mixin.wsScheme = mixin.scheme.replace('http', 'ws'); + mixin.scheme = url.protocol.replace(/:$/, ""); + mixin.wsScheme = mixin.scheme.replace("http", "ws"); mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`; mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`; mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; mixin.userAgent = `Calckey/${meta.version} (${config.url})`; - mixin.clientEntry = clientManifest['src/init.ts']; + mixin.clientEntry = clientManifest["src/init.ts"]; if (!config.redis.prefix) config.redis.prefix = mixin.host; diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 9872b0eeb..0b9c4e8ba 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -47,7 +47,7 @@ export type Source = { id: string; - outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual'; + outgoingAddressFamily?: "ipv4" | "ipv6" | "dual"; deliverJobConcurrency?: number; inboxJobConcurrency?: number; @@ -81,7 +81,6 @@ export type Source = { user?: string; pass?: string; useImplicitSslTls?: boolean; - }; objectStorage: { managed?: boolean; diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 172edca0d..c56fed19c 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,6 +1,7 @@ -import config from '@/config/index.js'; +import config from "@/config/index.js"; -export const MAX_NOTE_TEXT_LENGTH = config.maxNoteLength != null ? config.maxNoteLength : 3000; +export const MAX_NOTE_TEXT_LENGTH = + config.maxNoteLength != null ? config.maxNoteLength : 3000; export const SECOND = 1000; export const SEC = 1000; @@ -17,39 +18,39 @@ export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days // SVGはXSSを生むので許可しない export const FILE_TYPE_BROWSERSAFE = [ // Images - 'image/png', - 'image/gif', - 'image/jpeg', - 'image/webp', - 'image/apng', - 'image/bmp', - 'image/tiff', - 'image/x-icon', + "image/png", + "image/gif", + "image/jpeg", + "image/webp", + "image/apng", + "image/bmp", + "image/tiff", + "image/x-icon", // OggS - 'audio/opus', - 'video/ogg', - 'audio/ogg', - 'application/ogg', + "audio/opus", + "video/ogg", + "audio/ogg", + "application/ogg", // ISO/IEC base media file format - 'video/quicktime', - 'video/mp4', - 'audio/mp4', - 'video/x-m4v', - 'audio/x-m4a', - 'video/3gpp', - 'video/3gpp2', + "video/quicktime", + "video/mp4", + "audio/mp4", + "video/x-m4v", + "audio/x-m4a", + "video/3gpp", + "video/3gpp2", - 'video/mpeg', - 'audio/mpeg', + "video/mpeg", + "audio/mpeg", - 'video/webm', - 'audio/webm', + "video/webm", + "audio/webm", - 'audio/aac', - 'audio/x-flac', - 'audio/vnd.wave', + "audio/aac", + "audio/x-flac", + "audio/vnd.wave", ]; /* https://github.com/sindresorhus/file-type/blob/main/supported.js diff --git a/packages/backend/src/daemons/janitor.ts b/packages/backend/src/daemons/janitor.ts index f2a1bfcc2..2050d54d4 100644 --- a/packages/backend/src/daemons/janitor.ts +++ b/packages/backend/src/daemons/janitor.ts @@ -1,13 +1,13 @@ // TODO: 消したい const interval = 30 * 60 * 1000; -import { AttestationChallenges } from '@/models/index.js'; -import { LessThan } from 'typeorm'; +import { AttestationChallenges } from "@/models/index.js"; +import { LessThan } from "typeorm"; /** * Clean up database occasionally */ -export default function() { +export default function () { async function tick() { await AttestationChallenges.delete({ createdAt: LessThan(new Date(new Date().getTime() - 5 * 60 * 1000)), diff --git a/packages/backend/src/daemons/queue-stats.ts b/packages/backend/src/daemons/queue-stats.ts index 1535abc6a..381b52a91 100644 --- a/packages/backend/src/daemons/queue-stats.ts +++ b/packages/backend/src/daemons/queue-stats.ts @@ -1,5 +1,5 @@ -import Xev from 'xev'; -import { deliverQueue, inboxQueue } from '../queue/queues.js'; +import Xev from "xev"; +import { deliverQueue, inboxQueue } from "../queue/queues.js"; const ev = new Xev(); @@ -8,21 +8,21 @@ const interval = 10000; /** * Report queue stats regularly */ -export default function() { +export default function () { const log = [] as any[]; - ev.on('requestQueueStatsLog', x => { + ev.on("requestQueueStatsLog", (x) => { ev.emit(`queueStatsLog:${x.id}`, log.slice(0, x.length || 50)); }); let activeDeliverJobs = 0; let activeInboxJobs = 0; - deliverQueue.on('global:active', () => { + deliverQueue.on("global:active", () => { activeDeliverJobs++; }); - inboxQueue.on('global:active', () => { + inboxQueue.on("global:active", () => { activeInboxJobs++; }); @@ -45,7 +45,7 @@ export default function() { }, }; - ev.emit('queueStats', stats); + ev.emit("queueStats", stats); log.unshift(stats); if (log.length > 200) log.pop(); diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts index faf4e6e4a..b0bf1288f 100644 --- a/packages/backend/src/daemons/server-stats.ts +++ b/packages/backend/src/daemons/server-stats.ts @@ -1,6 +1,6 @@ -import si from 'systeminformation'; -import Xev from 'xev'; -import * as osUtils from 'os-utils'; +import si from "systeminformation"; +import Xev from "xev"; +import * as osUtils from "os-utils"; const ev = new Xev(); @@ -12,10 +12,10 @@ const round = (num: number) => Math.round(num * 10) / 10; /** * Report server stats regularly */ -export default function() { +export default function () { const log = [] as any[]; - ev.on('requestServerStatsLog', x => { + ev.on("requestServerStatsLog", (x) => { ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50)); }); @@ -40,7 +40,7 @@ export default function() { w: round(Math.max(0, fsStats.wIO_sec ?? 0)), }, }; - ev.emit('serverStats', stats); + ev.emit("serverStats", stats); log.unshift(stats); if (log.length > 200) log.pop(); } diff --git a/packages/backend/src/db/elasticsearch.ts b/packages/backend/src/db/elasticsearch.ts index d98c5d180..2640e7f91 100644 --- a/packages/backend/src/db/elasticsearch.ts +++ b/packages/backend/src/db/elasticsearch.ts @@ -1,12 +1,12 @@ -import * as elasticsearch from '@elastic/elasticsearch'; -import config from '@/config/index.js'; +import * as elasticsearch from "@elastic/elasticsearch"; +import config from "@/config/index.js"; const index = { settings: { analysis: { analyzer: { ngram: { - tokenizer: 'ngram', + tokenizer: "ngram", }, }, }, @@ -14,16 +14,16 @@ const index = { mappings: { properties: { text: { - type: 'text', + type: "text", index: true, - analyzer: 'ngram', + analyzer: "ngram", }, userId: { - type: 'keyword', + type: "keyword", index: true, }, userHost: { - type: 'keyword', + type: "keyword", index: true, }, }, @@ -31,26 +31,35 @@ const index = { }; // Init ElasticSearch connection -const client = config.elasticsearch ? new elasticsearch.Client({ - node: `${config.elasticsearch.ssl ? 'https://' : 'http://'}${config.elasticsearch.host}:${config.elasticsearch.port}`, - auth: (config.elasticsearch.user && config.elasticsearch.pass) ? { - username: config.elasticsearch.user, - password: config.elasticsearch.pass, - } : undefined, - pingTimeout: 30000, -}) : null; +const client = config.elasticsearch + ? new elasticsearch.Client({ + node: `${config.elasticsearch.ssl ? "https://" : "http://"}${ + config.elasticsearch.host + }:${config.elasticsearch.port}`, + auth: + config.elasticsearch.user && config.elasticsearch.pass + ? { + username: config.elasticsearch.user, + password: config.elasticsearch.pass, + } + : undefined, + pingTimeout: 30000, + }) + : null; if (client) { - client.indices.exists({ - index: config.elasticsearch.index || 'misskey_note', - }).then(exist => { - if (!exist.body) { - client.indices.create({ - index: config.elasticsearch.index || 'misskey_note', - body: index, - }); - } - }); + client.indices + .exists({ + index: config.elasticsearch.index || "misskey_note", + }) + .then((exist) => { + if (!exist.body) { + client.indices.create({ + index: config.elasticsearch.index || "misskey_note", + body: index, + }); + } + }); } export default client; diff --git a/packages/backend/src/db/logger.ts b/packages/backend/src/db/logger.ts index 22f4c6b1b..28ec65dd2 100644 --- a/packages/backend/src/db/logger.ts +++ b/packages/backend/src/db/logger.ts @@ -1,3 +1,3 @@ -import Logger from '@/services/logger.js'; +import Logger from "@/services/logger.js"; -export const dbLogger = new Logger('db'); +export const dbLogger = new Logger("db"); diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 94d55e431..f95bd2594 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -1,87 +1,89 @@ // https://github.com/typeorm/typeorm/issues/2400 -import pg from 'pg'; +import pg from "pg"; pg.types.setTypeParser(20, Number); -import { Logger, DataSource } from 'typeorm'; -import * as highlight from 'cli-highlight'; -import config from '@/config/index.js'; +import type { Logger } from "typeorm"; +import { DataSource } from "typeorm"; +import * as highlight from "cli-highlight"; +import config from "@/config/index.js"; -import { User } from '@/models/entities/user.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFolder } from '@/models/entities/drive-folder.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { App } from '@/models/entities/app.js'; -import { PollVote } from '@/models/entities/poll-vote.js'; -import { Note } from '@/models/entities/note.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { NoteWatching } from '@/models/entities/note-watching.js'; -import { NoteThreadMuting } from '@/models/entities/note-thread-muting.js'; -import { NoteUnread } from '@/models/entities/note-unread.js'; -import { Notification } from '@/models/entities/notification.js'; -import { Meta } from '@/models/entities/meta.js'; -import { Following } from '@/models/entities/following.js'; -import { Instance } from '@/models/entities/instance.js'; -import { Muting } from '@/models/entities/muting.js'; -import { SwSubscription } from '@/models/entities/sw-subscription.js'; -import { Blocking } from '@/models/entities/blocking.js'; -import { UserList } from '@/models/entities/user-list.js'; -import { UserListJoining } from '@/models/entities/user-list-joining.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; -import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; -import { Hashtag } from '@/models/entities/hashtag.js'; -import { NoteFavorite } from '@/models/entities/note-favorite.js'; -import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; -import { RegistrationTicket } from '@/models/entities/registration-tickets.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { Signin } from '@/models/entities/signin.js'; -import { AuthSession } from '@/models/entities/auth-session.js'; -import { FollowRequest } from '@/models/entities/follow-request.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { UserNotePining } from '@/models/entities/user-note-pining.js'; -import { Poll } from '@/models/entities/poll.js'; -import { UserKeypair } from '@/models/entities/user-keypair.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { UserSecurityKey } from '@/models/entities/user-security-key.js'; -import { AttestationChallenge } from '@/models/entities/attestation-challenge.js'; -import { Page } from '@/models/entities/page.js'; -import { PageLike } from '@/models/entities/page-like.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; -import { GalleryLike } from '@/models/entities/gallery-like.js'; -import { ModerationLog } from '@/models/entities/moderation-log.js'; -import { UsedUsername } from '@/models/entities/used-username.js'; -import { Announcement } from '@/models/entities/announcement.js'; -import { AnnouncementRead } from '@/models/entities/announcement-read.js'; -import { Clip } from '@/models/entities/clip.js'; -import { ClipNote } from '@/models/entities/clip-note.js'; -import { Antenna } from '@/models/entities/antenna.js'; -import { AntennaNote } from '@/models/entities/antenna-note.js'; -import { PromoNote } from '@/models/entities/promo-note.js'; -import { PromoRead } from '@/models/entities/promo-read.js'; -import { Relay } from '@/models/entities/relay.js'; -import { MutedNote } from '@/models/entities/muted-note.js'; -import { Channel } from '@/models/entities/channel.js'; -import { ChannelFollowing } from '@/models/entities/channel-following.js'; -import { ChannelNotePining } from '@/models/entities/channel-note-pining.js'; -import { RegistryItem } from '@/models/entities/registry-item.js'; -import { Ad } from '@/models/entities/ad.js'; -import { PasswordResetRequest } from '@/models/entities/password-reset-request.js'; -import { UserPending } from '@/models/entities/user-pending.js'; -import { Webhook } from '@/models/entities/webhook.js'; -import { UserIp } from '@/models/entities/user-ip.js'; +import { User } from "@/models/entities/user.js"; +import { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFolder } from "@/models/entities/drive-folder.js"; +import { AccessToken } from "@/models/entities/access-token.js"; +import { App } from "@/models/entities/app.js"; +import { PollVote } from "@/models/entities/poll-vote.js"; +import { Note } from "@/models/entities/note.js"; +import { NoteReaction } from "@/models/entities/note-reaction.js"; +import { NoteWatching } from "@/models/entities/note-watching.js"; +import { NoteThreadMuting } from "@/models/entities/note-thread-muting.js"; +import { NoteUnread } from "@/models/entities/note-unread.js"; +import { Notification } from "@/models/entities/notification.js"; +import { Meta } from "@/models/entities/meta.js"; +import { Following } from "@/models/entities/following.js"; +import { Instance } from "@/models/entities/instance.js"; +import { Muting } from "@/models/entities/muting.js"; +import { SwSubscription } from "@/models/entities/sw-subscription.js"; +import { Blocking } from "@/models/entities/blocking.js"; +import { UserList } from "@/models/entities/user-list.js"; +import { UserListJoining } from "@/models/entities/user-list-joining.js"; +import { UserGroup } from "@/models/entities/user-group.js"; +import { UserGroupJoining } from "@/models/entities/user-group-joining.js"; +import { UserGroupInvitation } from "@/models/entities/user-group-invitation.js"; +import { Hashtag } from "@/models/entities/hashtag.js"; +import { NoteFavorite } from "@/models/entities/note-favorite.js"; +import { AbuseUserReport } from "@/models/entities/abuse-user-report.js"; +import { RegistrationTicket } from "@/models/entities/registration-tickets.js"; +import { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { Signin } from "@/models/entities/signin.js"; +import { AuthSession } from "@/models/entities/auth-session.js"; +import { FollowRequest } from "@/models/entities/follow-request.js"; +import { Emoji } from "@/models/entities/emoji.js"; +import { UserNotePining } from "@/models/entities/user-note-pining.js"; +import { Poll } from "@/models/entities/poll.js"; +import { UserKeypair } from "@/models/entities/user-keypair.js"; +import { UserPublickey } from "@/models/entities/user-publickey.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { UserSecurityKey } from "@/models/entities/user-security-key.js"; +import { AttestationChallenge } from "@/models/entities/attestation-challenge.js"; +import { Page } from "@/models/entities/page.js"; +import { PageLike } from "@/models/entities/page-like.js"; +import { GalleryPost } from "@/models/entities/gallery-post.js"; +import { GalleryLike } from "@/models/entities/gallery-like.js"; +import { ModerationLog } from "@/models/entities/moderation-log.js"; +import { UsedUsername } from "@/models/entities/used-username.js"; +import { Announcement } from "@/models/entities/announcement.js"; +import { AnnouncementRead } from "@/models/entities/announcement-read.js"; +import { Clip } from "@/models/entities/clip.js"; +import { ClipNote } from "@/models/entities/clip-note.js"; +import { Antenna } from "@/models/entities/antenna.js"; +import { AntennaNote } from "@/models/entities/antenna-note.js"; +import { PromoNote } from "@/models/entities/promo-note.js"; +import { PromoRead } from "@/models/entities/promo-read.js"; +import { Relay } from "@/models/entities/relay.js"; +import { MutedNote } from "@/models/entities/muted-note.js"; +import { Channel } from "@/models/entities/channel.js"; +import { ChannelFollowing } from "@/models/entities/channel-following.js"; +import { ChannelNotePining } from "@/models/entities/channel-note-pining.js"; +import { RegistryItem } from "@/models/entities/registry-item.js"; +import { Ad } from "@/models/entities/ad.js"; +import { PasswordResetRequest } from "@/models/entities/password-reset-request.js"; +import { UserPending } from "@/models/entities/user-pending.js"; +import { Webhook } from "@/models/entities/webhook.js"; +import { UserIp } from "@/models/entities/user-ip.js"; -import { entities as charts } from '@/services/chart/entities.js'; -import { envOption } from '../env.js'; -import { dbLogger } from './logger.js'; -import { redisClient } from './redis.js'; +import { entities as charts } from "@/services/chart/entities.js"; +import { envOption } from "../env.js"; +import { dbLogger } from "./logger.js"; +import { redisClient } from "./redis.js"; -const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); +const sqlLogger = dbLogger.createSubLogger("sql", "gray", false); class MyCustomLogger implements Logger { private highlight(sql: string) { return highlight.highlight(sql, { - language: 'sql', ignoreIllegals: true, + language: "sql", + ignoreIllegals: true, }); } @@ -178,10 +180,10 @@ export const entities = [ ...charts, ]; -const log = process.env.NODE_ENV !== 'production'; +const log = process.env.NODE_ENV !== "production"; export const db = new DataSource({ - type: 'postgres', + type: "postgres", host: config.db.host, port: config.db.port, username: config.db.user, @@ -191,24 +193,26 @@ export const db = new DataSource({ statement_timeout: 1000 * 10, ...config.db.extra, }, - synchronize: process.env.NODE_ENV === 'test', - dropSchema: process.env.NODE_ENV === 'test', - cache: !config.db.disableCache ? { - type: 'ioredis', - options: { - host: config.redis.host, - port: config.redis.port, - family: config.redis.family == null ? 0 : config.redis.family, - password: config.redis.pass, - keyPrefix: `${config.redis.prefix}:query:`, - db: config.redis.db || 0, - }, - } : false, + synchronize: process.env.NODE_ENV === "test", + dropSchema: process.env.NODE_ENV === "test", + cache: !config.db.disableCache + ? { + type: "ioredis", + options: { + host: config.redis.host, + port: config.redis.port, + family: config.redis.family == null ? 0 : config.redis.family, + password: config.redis.pass, + keyPrefix: `${config.redis.prefix}:query:`, + db: config.redis.db || 0, + }, + } + : false, logging: log, logger: log ? new MyCustomLogger() : undefined, maxQueryExecutionTime: 300, entities: entities, - migrations: ['../../migration/*.js'], + migrations: ["../../migration/*.js"], }); export async function initDb(force = false) { @@ -247,7 +251,7 @@ export async function resetDb() { if (i === 3) { throw e; } else { - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } } diff --git a/packages/backend/src/db/redis.ts b/packages/backend/src/db/redis.ts index 7d0843a59..6ad3de386 100644 --- a/packages/backend/src/db/redis.ts +++ b/packages/backend/src/db/redis.ts @@ -1,5 +1,5 @@ -import Redis from 'ioredis'; -import config from '@/config/index.js'; +import Redis from "ioredis"; +import config from "@/config/index.js"; export function createConnection() { return new Redis({ diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts index 1b678edc4..a788a0fba 100644 --- a/packages/backend/src/env.ts +++ b/packages/backend/src/env.ts @@ -10,11 +10,16 @@ const envOption = { }; for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { - if (process.env['MK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]) envOption[key] = true; + if ( + process.env[ + `MK_${key.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase()}` + ] + ) + envOption[key] = true; } -if (process.env.NODE_ENV === 'test') envOption.disableClustering = true; -if (process.env.NODE_ENV === 'test') envOption.quiet = true; -if (process.env.NODE_ENV === 'test') envOption.noDaemons = true; +if (process.env.NODE_ENV === "test") envOption.disableClustering = true; +if (process.env.NODE_ENV === "test") envOption.quiet = true; +if (process.env.NODE_ENV === "test") envOption.noDaemons = true; export { envOption }; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index bd9c0098b..278f630f7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -2,12 +2,12 @@ * Misskey Entry Point! */ -import { EventEmitter } from 'node:events'; -import boot from './boot/index.js'; +import { EventEmitter } from "node:events"; +import boot from "./boot/index.js"; Error.stackTraceLimit = Infinity; EventEmitter.defaultMaxListeners = 128; -boot().catch(err => { +boot().catch((err) => { console.error(err); }); diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts index 7751bac56..076061a4d 100644 --- a/packages/backend/src/mfm/from-html.ts +++ b/packages/backend/src/mfm/from-html.ts @@ -1,6 +1,6 @@ -import { URL } from 'node:url'; -import * as parse5 from 'parse5'; -import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; +import { URL } from "node:url"; +import * as parse5 from "parse5"; +import * as TreeAdapter from "../../node_modules/parse5/dist/tree-adapters/default.js"; const treeAdapter = TreeAdapter.defaultTreeAdapter; @@ -9,11 +9,11 @@ const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; export function 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'); + html = html.replace(/\r?\n/gi, "\n"); const dom = parse5.parseFragment(html); - let text = ''; + let text = ""; for (const n of dom.childNodes) { analyze(n); @@ -23,14 +23,14 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { function getText(node: TreeAdapter.Node): string { if (treeAdapter.isTextNode(node)) return node.value; - if (!treeAdapter.isElementNode(node)) return ''; - if (node.nodeName === 'br') return '\n'; + if (!treeAdapter.isElementNode(node)) return ""; + if (node.nodeName === "br") return "\n"; if (node.childNodes) { - return node.childNodes.map(n => getText(n)).join(''); + return node.childNodes.map((n) => getText(n)).join(""); } - return ''; + return ""; } function appendChildren(childNodes: TreeAdapter.ChildNode[]): void { @@ -51,42 +51,46 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { if (!treeAdapter.isElementNode(node)) return; switch (node.nodeName) { - case 'br': { - text += '\n'; + case "br": { + text += "\n"; break; } - case 'a': - { + case "a": { const txt = getText(node); - const rel = node.attrs.find(x => x.name === 'rel'); - const href = node.attrs.find(x => x.name === 'href'); + const rel = node.attrs.find((x) => x.name === "rel"); + const href = node.attrs.find((x) => x.name === "href"); // ハッシュタグ - if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) { + if ( + hashtagNames && + href && + hashtagNames.map((x) => x.toLowerCase()).includes(txt.toLowerCase()) + ) { text += txt; - // メンション - } else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) { - const part = txt.split('@'); + // メンション + } else if (txt.startsWith("@") && !(rel?.value.match(/^me /))) { + const part = txt.split("@"); if (part.length === 2 && href) { //#region ホスト名部分が省略されているので復元する - const acct = `${txt}@${(new URL(href.value)).hostname}`; + const acct = `${txt}@${new URL(href.value).hostname}`; text += acct; //#endregion } else if (part.length === 3) { text += txt; } - // その他 + // その他 } else { const generateLink = () => { - if (!href && !txt) { - return ''; + if (!(href || txt)) { + return ""; } if (!href) { return txt; } - if (!txt || txt === href.value) { // #6383: Missing text node + if (!txt || txt === href.value) { + // #6383: Missing text node if (href.value.match(urlRegexFull)) { return href.value; } else { @@ -94,7 +98,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { } } if (href.value.match(urlRegex) && !href.value.match(urlRegexFull)) { - return `[${txt}](<${href.value}>)`; // #6846 + return `[${txt}](<${href.value}>)`; // #6846 } else { return `[${txt}](${href.value})`; } @@ -105,55 +109,53 @@ export function fromHtml(html: string, hashtagNames?: string[]): string { break; } - case 'h1': - { - text += '【'; + case "h1": { + text += "【"; appendChildren(node.childNodes); - text += '】\n'; + text += "】\n"; break; } - case 'b': - case 'strong': - { - text += '**'; + case "b": + case "strong": { + text += "**"; appendChildren(node.childNodes); - text += '**'; + text += "**"; break; } - case 'small': - { - text += ''; + case "small": { + text += ""; appendChildren(node.childNodes); - text += ''; + text += ""; break; } - case 's': - case 'del': - { - text += '~~'; + case "s": + case "del": { + text += "~~"; appendChildren(node.childNodes); - text += '~~'; + text += "~~"; break; } - case 'i': - case 'em': - { - text += ''; + case "i": + case "em": { + text += ""; appendChildren(node.childNodes); - text += ''; + text += ""; break; } // block code (
)
-			case 'pre': {
-				if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
-					text += '\n```\n';
+			case "pre": {
+				if (
+					node.childNodes.length === 1 &&
+					node.childNodes[0].nodeName === "code"
+				) {
+					text += "\n```\n";
 					text += getText(node.childNodes[0]);
-					text += '\n```\n';
+					text += "\n```\n";
 				} else {
 					appendChildren(node.childNodes);
 				}
@@ -161,50 +163,48 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
 			}
 
 			// inline code ()
-			case 'code': {
-				text += '`';
+			case "code": {
+				text += "`";
 				appendChildren(node.childNodes);
-				text += '`';
+				text += "`";
 				break;
 			}
 
-			case 'blockquote': {
+			case "blockquote": {
 				const t = getText(node);
 				if (t) {
-					text += '\n> ';
-					text += t.split('\n').join('\n> ');
+					text += "\n> ";
+					text += t.split("\n").join("\n> ");
 				}
 				break;
 			}
 
-			case 'p':
-			case 'h2':
-			case 'h3':
-			case 'h4':
-			case 'h5':
-			case 'h6':
-			{
-				text += '\n\n';
+			case "p":
+			case "h2":
+			case "h3":
+			case "h4":
+			case "h5":
+			case "h6": {
+				text += "\n\n";
 				appendChildren(node.childNodes);
 				break;
 			}
 
 			// other block elements
-			case 'div':
-			case 'header':
-			case 'footer':
-			case 'article':
-			case 'li':
-			case 'dt':
-			case 'dd':
-			{
-				text += '\n';
+			case "div":
+			case "header":
+			case "footer":
+			case "article":
+			case "li":
+			case "dt":
+			case "dd": {
+				text += "\n";
 				appendChildren(node.childNodes);
 				break;
 			}
 
-			default:	// includes inline elements
-			{
+			default: {
+				// includes inline elements
 				appendChildren(node.childNodes);
 				break;
 			}
diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts
index 42747142b..8d8a4a888 100644
--- a/packages/backend/src/mfm/to-html.ts
+++ b/packages/backend/src/mfm/to-html.ts
@@ -1,65 +1,71 @@
-import { JSDOM } from 'jsdom';
-import * as mfm from 'mfm-js';
-import config from '@/config/index.js';
-import { intersperse } from '@/prelude/array.js';
-import { IMentionedRemoteUsers } from '@/models/entities/note.js';
+import { JSDOM } from "jsdom";
+import type * as mfm from "mfm-js";
+import config from "@/config/index.js";
+import { intersperse } from "@/prelude/array.js";
+import type { IMentionedRemoteUsers } from "@/models/entities/note.js";
 
-export function toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
+export function toHtml(
+	nodes: mfm.MfmNode[] | null,
+	mentionedRemoteUsers: IMentionedRemoteUsers = [],
+) {
 	if (nodes == null) {
 		return null;
 	}
 
-	const { window } = new JSDOM('');
+	const { window } = new JSDOM("");
 
 	const doc = window.document;
 
 	function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
 		if (children) {
-			for (const child of children.map(x => (handlers as any)[x.type](x))) targetElement.appendChild(child);
+			for (const child of children.map((x) => (handlers as any)[x.type](x)))
+				targetElement.appendChild(child);
 		}
 	}
 
-	const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any } = {
+	const handlers: {
+		[K in mfm.MfmNode["type"]]: (node: mfm.NodeType) => any;
+	} = {
 		bold(node) {
-			const el = doc.createElement('b');
+			const el = doc.createElement("b");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		small(node) {
-			const el = doc.createElement('small');
+			const el = doc.createElement("small");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		strike(node) {
-			const el = doc.createElement('del');
+			const el = doc.createElement("del");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		italic(node) {
-			const el = doc.createElement('i');
+			const el = doc.createElement("i");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		fn(node) {
-			const el = doc.createElement('i');
+			const el = doc.createElement("i");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		blockCode(node) {
-			const pre = doc.createElement('pre');
-			const inner = doc.createElement('code');
+			const pre = doc.createElement("pre");
+			const inner = doc.createElement("code");
 			inner.textContent = node.props.code;
 			pre.appendChild(inner);
 			return pre;
 		},
 
 		center(node) {
-			const el = doc.createElement('div');
+			const el = doc.createElement("div");
 			appendChildren(node.children, el);
 			return el;
 		},
@@ -73,81 +79,90 @@ export function toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMenti
 		},
 
 		hashtag(node) {
-			const a = doc.createElement('a');
+			const a = doc.createElement("a");
 			a.href = `${config.url}/tags/${node.props.hashtag}`;
 			a.textContent = `#${node.props.hashtag}`;
-			a.setAttribute('rel', 'tag');
+			a.setAttribute("rel", "tag");
 			return a;
 		},
 
 		inlineCode(node) {
-			const el = doc.createElement('code');
+			const el = doc.createElement("code");
 			el.textContent = node.props.code;
 			return el;
 		},
 
 		mathInline(node) {
-			const el = doc.createElement('code');
+			const el = doc.createElement("code");
 			el.textContent = node.props.formula;
 			return el;
 		},
 
 		mathBlock(node) {
-			const el = doc.createElement('code');
+			const el = doc.createElement("code");
 			el.textContent = node.props.formula;
 			return el;
 		},
 
 		link(node) {
-			const a = doc.createElement('a');
+			const a = doc.createElement("a");
 			a.href = node.props.url;
 			appendChildren(node.children, a);
 			return a;
 		},
 
 		mention(node) {
-			const a = doc.createElement('a');
+			const a = doc.createElement("a");
 			const { username, host, acct } = node.props;
-			const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
-			a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${config.url}/${acct}`;
-			a.className = 'u-url mention';
+			const remoteUserInfo = mentionedRemoteUsers.find(
+				(remoteUser) =>
+					remoteUser.username === username && remoteUser.host === host,
+			);
+			a.href = remoteUserInfo
+				? remoteUserInfo.url
+					? remoteUserInfo.url
+					: remoteUserInfo.uri
+				: `${config.url}/${acct}`;
+			a.className = "u-url mention";
 			a.textContent = acct;
 			return a;
 		},
 
 		quote(node) {
-			const el = doc.createElement('blockquote');
+			const el = doc.createElement("blockquote");
 			appendChildren(node.children, el);
 			return el;
 		},
 
 		text(node) {
-			const el = doc.createElement('span');
-			const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
+			const el = doc.createElement("span");
+			const nodes = node.props.text
+				.split(/\r\n|\r|\n/)
+				.map((x) => doc.createTextNode(x));
 
-			for (const x of intersperse('br', nodes)) {
-				el.appendChild(x === 'br' ? doc.createElement('br') : x);
+			for (const x of intersperse("br", nodes)) {
+				el.appendChild(x === "br" ? doc.createElement("br") : x);
 			}
 
 			return el;
 		},
 
 		url(node) {
-			const a = doc.createElement('a');
+			const a = doc.createElement("a");
 			a.href = node.props.url;
 			a.textContent = node.props.url;
 			return a;
 		},
 
 		search(node) {
-			const a = doc.createElement('a');
+			const a = doc.createElement("a");
 			a.href = `https://search.annoyingorange.xyz/search?q=${node.props.query}`;
 			a.textContent = node.props.content;
 			return a;
 		},
 
 		plain(node) {
-			const el = doc.createElement('span');
+			const el = doc.createElement("span");
 			appendChildren(node.children, el);
 			return el;
 		},
diff --git a/packages/backend/src/misc/acct.ts b/packages/backend/src/misc/acct.ts
index c32cee86c..5b7767a10 100644
--- a/packages/backend/src/misc/acct.ts
+++ b/packages/backend/src/misc/acct.ts
@@ -4,8 +4,8 @@ export type Acct = {
 };
 
 export function parse(acct: string): Acct {
-	if (acct.startsWith('@')) acct = acct.substr(1);
-	const split = acct.split('@', 2);
+	if (acct.startsWith("@")) acct = acct.substr(1);
+	const split = acct.split("@", 2);
 	return { username: split[0], host: split[1] || null };
 }
 
diff --git a/packages/backend/src/misc/antenna-cache.ts b/packages/backend/src/misc/antenna-cache.ts
index 97249c146..7f199c396 100644
--- a/packages/backend/src/misc/antenna-cache.ts
+++ b/packages/backend/src/misc/antenna-cache.ts
@@ -1,6 +1,6 @@
-import { Antennas } from '@/models/index.js';
-import { Antenna } from '@/models/entities/antenna.js';
-import { subscriber } from '@/db/redis.js';
+import { Antennas } from "@/models/index.js";
+import type { Antenna } from "@/models/entities/antenna.js";
+import { subscriber } from "@/db/redis.js";
 
 let antennasFetched = false;
 let antennas: Antenna[] = [];
@@ -14,20 +14,20 @@ export async function getAntennas() {
 	return antennas;
 }
 
-subscriber.on('message', async (_, data) => {
+subscriber.on("message", async (_, data) => {
 	const obj = JSON.parse(data);
 
-	if (obj.channel === 'internal') {
+	if (obj.channel === "internal") {
 		const { type, body } = obj.message;
 		switch (type) {
-			case 'antennaCreated':
+			case "antennaCreated":
 				antennas.push(body);
 				break;
-			case 'antennaUpdated':
-				antennas[antennas.findIndex(a => a.id === body.id)] = body;
+			case "antennaUpdated":
+				antennas[antennas.findIndex((a) => a.id === body.id)] = body;
 				break;
-			case 'antennaDeleted':
-				antennas = antennas.filter(a => a.id !== body.id);
+			case "antennaDeleted":
+				antennas = antennas.filter((a) => a.id !== body.id);
 				break;
 			default:
 				break;
diff --git a/packages/backend/src/misc/api-permissions.ts b/packages/backend/src/misc/api-permissions.ts
index 160cdf9fd..9e040262f 100644
--- a/packages/backend/src/misc/api-permissions.ts
+++ b/packages/backend/src/misc/api-permissions.ts
@@ -1,35 +1,35 @@
 export const kinds = [
-	'read:account',
-	'write:account',
-	'read:blocks',
-	'write:blocks',
-	'read:drive',
-	'write:drive',
-	'read:favorites',
-	'write:favorites',
-	'read:following',
-	'write:following',
-	'read:messaging',
-	'write:messaging',
-	'read:mutes',
-	'write:mutes',
-	'write:notes',
-	'read:notifications',
-	'write:notifications',
-	'read:reactions',
-	'write:reactions',
-	'write:votes',
-	'read:pages',
-	'write:pages',
-	'write:page-likes',
-	'read:page-likes',
-	'read:user-groups',
-	'write:user-groups',
-	'read:channels',
-	'write:channels',
-	'read:gallery',
-	'write:gallery',
-	'read:gallery-likes',
-	'write:gallery-likes',
+	"read:account",
+	"write:account",
+	"read:blocks",
+	"write:blocks",
+	"read:drive",
+	"write:drive",
+	"read:favorites",
+	"write:favorites",
+	"read:following",
+	"write:following",
+	"read:messaging",
+	"write:messaging",
+	"read:mutes",
+	"write:mutes",
+	"write:notes",
+	"read:notifications",
+	"write:notifications",
+	"read:reactions",
+	"write:reactions",
+	"write:votes",
+	"read:pages",
+	"write:pages",
+	"write:page-likes",
+	"read:page-likes",
+	"read:user-groups",
+	"write:user-groups",
+	"read:channels",
+	"write:channels",
+	"read:gallery",
+	"write:gallery",
+	"read:gallery-likes",
+	"write:gallery-likes",
 ];
 // IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).
diff --git a/packages/backend/src/misc/app-lock.ts b/packages/backend/src/misc/app-lock.ts
index b5089cc6a..05bcf5424 100644
--- a/packages/backend/src/misc/app-lock.ts
+++ b/packages/backend/src/misc/app-lock.ts
@@ -1,16 +1,15 @@
-import { redisClient } from '../db/redis.js';
-import { promisify } from 'node:util';
-import redisLock from 'redis-lock';
+import { redisClient } from "../db/redis.js";
+import { promisify } from "node:util";
+import redisLock from "redis-lock";
 
 /**
  * Retry delay (ms) for lock acquisition
  */
 const retryDelay = 100;
 
-const lock: (key: string, timeout?: number) => Promise<() => void>
-	= redisClient
+const lock: (key: string, timeout?: number) => Promise<() => void> = redisClient
 	? promisify(redisLock(redisClient, retryDelay))
-	: async () => () => { };
+	: async () => () => {};
 
 /**
  * Get AP Object lock
@@ -22,7 +21,10 @@ export function getApLock(uri: string, timeout = 30 * 1000) {
 	return lock(`ap-object:${uri}`, timeout);
 }
 
-export function getFetchInstanceMetadataLock(host: string, timeout = 30 * 1000) {
+export function getFetchInstanceMetadataLock(
+	host: string,
+	timeout = 30 * 1000,
+) {
 	return lock(`instance:${host}`, timeout);
 }
 
diff --git a/packages/backend/src/misc/before-shutdown.ts b/packages/backend/src/misc/before-shutdown.ts
index 93ac7a1f3..082041835 100644
--- a/packages/backend/src/misc/before-shutdown.ts
+++ b/packages/backend/src/misc/before-shutdown.ts
@@ -1,6 +1,6 @@
 // https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58
 
-'use strict';
+"use strict";
 
 /**
  * @callback BeforeShutdownListener
@@ -11,7 +11,7 @@
  * System signals the app will listen to initiate shutdown.
  * @const {string[]}
  */
-const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
+const SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
 
 /**
  * Time in milliseconds to wait before forcing shutdown.
@@ -31,7 +31,10 @@ const shutdownListeners: ((signalOrEvent: string) => void)[] = [];
  * @param  {string[]} signals System signals to listen to.
  * @param  {function(string)} fn Function to execute on shutdown.
  */
-const processOnce = (signals: string[], fn: (signalOrEvent: string) => void) => {
+const processOnce = (
+	signals: string[],
+	fn: (signalOrEvent: string) => void,
+) => {
 	for (const sig of signals) {
 		process.once(sig, fn);
 	}
@@ -44,7 +47,9 @@ const processOnce = (signals: string[], fn: (signalOrEvent: string) => void) =>
 const forceExitAfter = (timeout: number) => () => {
 	setTimeout(() => {
 		// Force shutdown after timeout
-		console.warn(`Could not close resources gracefully after ${timeout}ms: forcing shutdown`);
+		console.warn(
+			`Could not close resources gracefully after ${timeout}ms: forcing shutdown`,
+		);
 		return process.exit(1);
 	}, timeout).unref();
 };
@@ -56,7 +61,7 @@ const forceExitAfter = (timeout: number) => () => {
  * @param {string} signalOrEvent The exit signal or event name received on the process.
  */
 async function shutdownHandler(signalOrEvent: string) {
-	if (process.env.NODE_ENV === 'test') return process.exit(0);
+	if (process.env.NODE_ENV === "test") return process.exit(0);
 
 	console.warn(`Shutting down: received [${signalOrEvent}] signal`);
 
@@ -65,7 +70,11 @@ async function shutdownHandler(signalOrEvent: string) {
 			await listener(signalOrEvent);
 		} catch (err) {
 			if (err instanceof Error) {
-				console.warn(`A shutdown handler failed before completing with: ${err.message || err}`);
+				console.warn(
+					`A shutdown handler failed before completing with: ${
+						err.message || err
+					}`,
+				);
 			}
 		}
 	}
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index e5b911ed3..9abebc91c 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,8 +1,8 @@
 export class Cache {
-	public cache: Map;
+	public cache: Map;
 	private lifetime: number;
 
-	constructor(lifetime: Cache['lifetime']) {
+	constructor(lifetime: Cache["lifetime"]) {
 		this.cache = new Map();
 		this.lifetime = lifetime;
 	}
@@ -17,7 +17,7 @@ export class Cache {
 	public get(key: string | null): T | undefined {
 		const cached = this.cache.get(key);
 		if (cached == null) return undefined;
-		if ((Date.now() - cached.date) > this.lifetime) {
+		if (Date.now() - cached.date > this.lifetime) {
 			this.cache.delete(key);
 			return undefined;
 		}
@@ -32,7 +32,11 @@ export class Cache {
 	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
 	 */
-	public async fetch(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise {
+	public async fetch(
+		key: string | null,
+		fetcher: () => Promise,
+		validator?: (cachedValue: T) => boolean,
+	): Promise {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
@@ -56,7 +60,11 @@ export class Cache {
 	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
 	 */
-	public async fetchMaybe(key: string | null, fetcher: () => Promise, validator?: (cachedValue: T) => boolean): Promise {
+	public async fetchMaybe(
+		key: string | null,
+		fetcher: () => Promise,
+		validator?: (cachedValue: T) => boolean,
+	): Promise {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
diff --git a/packages/backend/src/misc/captcha.ts b/packages/backend/src/misc/captcha.ts
index 947ab27e2..8ea4abedb 100644
--- a/packages/backend/src/misc/captcha.ts
+++ b/packages/backend/src/misc/captcha.ts
@@ -1,51 +1,67 @@
-import fetch from 'node-fetch';
-import { URLSearchParams } from 'node:url';
-import { getAgentByUrl } from './fetch.js';
-import config from '@/config/index.js';
+import fetch from "node-fetch";
+import { URLSearchParams } from "node:url";
+import { getAgentByUrl } from "./fetch.js";
+import config from "@/config/index.js";
 
 export async function verifyRecaptcha(secret: string, response: string) {
-	const result = await getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(e => {
+	const result = await getCaptchaResponse(
+		"https://www.recaptcha.net/recaptcha/api/siteverify",
+		secret,
+		response,
+	).catch((e) => {
 		throw new Error(`recaptcha-request-failed: ${e.message}`);
 	});
 
 	if (result.success !== true) {
-		const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : '';
+		const errorCodes = result["error-codes"]
+			? result["error-codes"]?.join(", ")
+			: "";
 		throw new Error(`recaptcha-failed: ${errorCodes}`);
 	}
 }
 
 export async function verifyHcaptcha(secret: string, response: string) {
-	const result = await getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(e => {
+	const result = await getCaptchaResponse(
+		"https://hcaptcha.com/siteverify",
+		secret,
+		response,
+	).catch((e) => {
 		throw new Error(`hcaptcha-request-failed: ${e.message}`);
 	});
 
 	if (result.success !== true) {
-		const errorCodes = result['error-codes'] ? result['error-codes']?.join(', ') : '';
+		const errorCodes = result["error-codes"]
+			? result["error-codes"]?.join(", ")
+			: "";
 		throw new Error(`hcaptcha-failed: ${errorCodes}`);
 	}
 }
 
 type CaptchaResponse = {
 	success: boolean;
-	'error-codes'?: string[];
+	"error-codes"?: string[];
 };
 
-async function getCaptchaResponse(url: string, secret: string, response: string): Promise {
+async function getCaptchaResponse(
+	url: string,
+	secret: string,
+	response: string,
+): Promise {
 	const params = new URLSearchParams({
 		secret,
 		response,
 	});
 
 	const res = await fetch(url, {
-		method: 'POST',
+		method: "POST",
 		body: params,
 		headers: {
-			'User-Agent': config.userAgent,
+			"User-Agent": config.userAgent,
 		},
 		// TODO
 		//timeout: 10 * 1000,
 		agent: getAgentByUrl,
-	}).catch(e => {
+	}).catch((e) => {
 		throw new Error(`${e.message || e}`);
 	});
 
@@ -53,5 +69,5 @@ async function getCaptchaResponse(url: string, secret: string, response: string)
 		throw new Error(`${res.status}`);
 	}
 
-	return await res.json() as CaptchaResponse;
+	return (await res.json()) as CaptchaResponse;
 }
diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts
index d9cedee7d..aa38d9e27 100644
--- a/packages/backend/src/misc/check-hit-antenna.ts
+++ b/packages/backend/src/misc/check-hit-antenna.ts
@@ -1,90 +1,121 @@
-import { Antenna } from '@/models/entities/antenna.js';
-import { Note } from '@/models/entities/note.js';
-import { User } from '@/models/entities/user.js';
-import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js';
-import { getFullApAccount } from './convert-host.js';
-import * as Acct from '@/misc/acct.js';
-import { Packed } from './schema.js';
-import { Cache } from './cache.js';
+import type { Antenna } from "@/models/entities/antenna.js";
+import type { Note } from "@/models/entities/note.js";
+import type { User } from "@/models/entities/user.js";
+import {
+	UserListJoinings,
+	UserGroupJoinings,
+	Blockings,
+} from "@/models/index.js";
+import { getFullApAccount } from "./convert-host.js";
+import * as Acct from "@/misc/acct.js";
+import type { Packed } from "./schema.js";
+import { Cache } from "./cache.js";
 
-const blockingCache = new Cache(1000 * 60 * 5);
+const blockingCache = new Cache(1000 * 60 * 5);
 
 // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
 
 /**
  * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
  */
-export async function 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;
+export async function 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;
 
 	// アンテナ作成者がノート作成者にブロックされていたらスキップ
-	const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
-	if (blockings.some(blocking => blocking === antenna.userId)) return false;
+	const blockings = await blockingCache.fetch(noteUser.id, () =>
+		Blockings.findBy({ blockerId: noteUser.id }).then((res) =>
+			res.map((x) => x.blockeeId),
+		),
+	);
+	if (blockings.some((blocking) => blocking === antenna.userId)) return false;
 
-	if (note.visibility === 'followers') {
-		if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
-		if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
+	if (note.visibility === "followers") {
+		if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId))
+			return false;
+		if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId))
+			return false;
 	}
 
 	if (!antenna.withReplies && note.replyId != null) return false;
 
-	if (antenna.src === 'home') {
-		if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
-		if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
-	} else if (antenna.src === 'list') {
-		const listUsers = (await UserListJoinings.findBy({
-			userListId: antenna.userListId!,
-		})).map(x => x.userId);
+	if (antenna.src === "home") {
+		if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId))
+			return false;
+		if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId))
+			return false;
+	} else if (antenna.src === "list") {
+		const listUsers = (
+			await UserListJoinings.findBy({
+				userListId: antenna.userListId!,
+			})
+		).map((x) => x.userId);
 
 		if (!listUsers.includes(note.userId)) return false;
-	} else if (antenna.src === 'group') {
-		const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! });
+	} else if (antenna.src === "group") {
+		const joining = await UserGroupJoinings.findOneByOrFail({
+			id: antenna.userGroupJoiningId!,
+		});
 
-		const groupUsers = (await UserGroupJoinings.findBy({
-			userGroupId: joining.userGroupId,
-		})).map(x => x.userId);
+		const groupUsers = (
+			await UserGroupJoinings.findBy({
+				userGroupId: joining.userGroupId,
+			})
+		).map((x) => x.userId);
 
 		if (!groupUsers.includes(note.userId)) return false;
-	} else if (antenna.src === 'users') {
-		const accts = antenna.users.map(x => {
+	} else if (antenna.src === "users") {
+		const accts = antenna.users.map((x) => {
 			const { username, host } = Acct.parse(x);
 			return getFullApAccount(username, host).toLowerCase();
 		});
-		if (!accts.includes(getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false;
+		if (
+			!accts.includes(
+				getFullApAccount(noteUser.username, noteUser.host).toLowerCase(),
+			)
+		)
+			return false;
 	}
 
 	const keywords = antenna.keywords
 		// Clean up
-		.map(xs => xs.filter(x => x !== ''))
-		.filter(xs => xs.length > 0);
+		.map((xs) => xs.filter((x) => x !== ""))
+		.filter((xs) => xs.length > 0);
 
 	if (keywords.length > 0) {
 		if (note.text == null) return false;
 
-		const matched = keywords.some(and =>
-			and.every(keyword =>
+		const matched = keywords.some((and) =>
+			and.every((keyword) =>
 				antenna.caseSensitive
 					? note.text!.includes(keyword)
-					: note.text!.toLowerCase().includes(keyword.toLowerCase())
-			));
+					: note.text!.toLowerCase().includes(keyword.toLowerCase()),
+			),
+		);
 
 		if (!matched) return false;
 	}
 
 	const excludeKeywords = antenna.excludeKeywords
 		// Clean up
-		.map(xs => xs.filter(x => x !== ''))
-		.filter(xs => xs.length > 0);
+		.map((xs) => xs.filter((x) => x !== ""))
+		.filter((xs) => xs.length > 0);
 
 	if (excludeKeywords.length > 0) {
 		if (note.text == null) return false;
 
-		const matched = excludeKeywords.some(and =>
-			and.every(keyword =>
+		const matched = excludeKeywords.some((and) =>
+			and.every((keyword) =>
 				antenna.caseSensitive
 					? note.text!.includes(keyword)
-					: note.text!.toLowerCase().includes(keyword.toLowerCase())
-			));
+					: note.text!.toLowerCase().includes(keyword.toLowerCase()),
+			),
+		);
 
 		if (matched) return false;
 	}
diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts
index d7662820a..ffdf3caf8 100644
--- a/packages/backend/src/misc/check-word-mute.ts
+++ b/packages/backend/src/misc/check-word-mute.ts
@@ -1,28 +1,32 @@
-import RE2 from 're2';
-import { Note } from '@/models/entities/note.js';
-import { User } from '@/models/entities/user.js';
+import RE2 from "re2";
+import type { Note } from "@/models/entities/note.js";
+import type { User } from "@/models/entities/user.js";
 
 type NoteLike = {
-	userId: Note['userId'];
-	text: Note['text'];
+	userId: Note["userId"];
+	text: Note["text"];
 };
 
 type UserLike = {
-	id: User['id'];
+	id: User["id"];
 };
 
-export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array): Promise {
+export async function checkWordMute(
+	note: NoteLike,
+	me: UserLike | null | undefined,
+	mutedWords: Array,
+): Promise {
 	// 自分自身
-	if (me && (note.userId === me.id)) return false;
+	if (me && note.userId === me.id) return false;
 
 	if (mutedWords.length > 0) {
-		const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
+		const text = ((note.cw ?? "") + "\n" + (note.text ?? "")).trim();
 
-		if (text === '') return false;
+		if (text === "") return false;
 
-		const matched = mutedWords.some(filter => {
+		const matched = mutedWords.some((filter) => {
 			if (Array.isArray(filter)) {
-				return filter.every(keyword => text.includes(keyword));
+				return filter.every((keyword) => text.includes(keyword));
 			} else {
 				// represents RegExp
 				const regexp = filter.match(/^\/(.+)\/(.*)$/);
diff --git a/packages/backend/src/misc/clone.ts b/packages/backend/src/misc/clone.ts
index 16fad2412..4322e2e28 100644
--- a/packages/backend/src/misc/clone.ts
+++ b/packages/backend/src/misc/clone.ts
@@ -1,10 +1,16 @@
 // structredCloneが遅いため
 // SEE: http://var.blog.jp/archives/86038606.html
 
-type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
+type Cloneable =
+	| string
+	| number
+	| boolean
+	| null
+	| { [key: string]: Cloneable }
+	| Cloneable[];
 
 export function deepClone(x: T): T {
-	if (typeof x === 'object') {
+	if (typeof x === "object") {
 		if (x === null) return x;
 		if (Array.isArray(x)) return x.map(deepClone) as T;
 		const obj = {} as Record;
diff --git a/packages/backend/src/misc/content-disposition.ts b/packages/backend/src/misc/content-disposition.ts
index b2aec471d..25d6f5817 100644
--- a/packages/backend/src/misc/content-disposition.ts
+++ b/packages/backend/src/misc/content-disposition.ts
@@ -1,6 +1,9 @@
-import cd from 'content-disposition';
+import cd from "content-disposition";
 
-export function contentDisposition(type: 'inline' | 'attachment', filename: string): string {
-	const fallback = filename.replace(/[^\w.-]/g, '_');
+export function contentDisposition(
+	type: "inline" | "attachment",
+	filename: string,
+): string {
+	const fallback = filename.replace(/[^\w.-]/g, "_");
 	return cd(filename, { type, fallback });
 }
diff --git a/packages/backend/src/misc/convert-host.ts b/packages/backend/src/misc/convert-host.ts
index 7eb940a7e..856ce3c12 100644
--- a/packages/backend/src/misc/convert-host.ts
+++ b/packages/backend/src/misc/convert-host.ts
@@ -1,9 +1,11 @@
-import { URL } from 'node:url';
-import config from '@/config/index.js';
-import { toASCII } from 'punycode';
+import { URL } from "node:url";
+import config from "@/config/index.js";
+import { toASCII } from "punycode";
 
 export function getFullApAccount(username: string, host: string | null) {
-	return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
+	return host
+		? `${username}@${toPuny(host)}`
+		: `${username}@${toPuny(config.host)}`;
 }
 
 export function isSelfHost(host: string) {
diff --git a/packages/backend/src/misc/count-same-renotes.ts b/packages/backend/src/misc/count-same-renotes.ts
index b7f8ce90c..45a6c1d35 100644
--- a/packages/backend/src/misc/count-same-renotes.ts
+++ b/packages/backend/src/misc/count-same-renotes.ts
@@ -1,14 +1,18 @@
-import { Notes } from '@/models/index.js';
+import { Notes } from "@/models/index.js";
 
-export async function countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise {
+export async function countSameRenotes(
+	userId: string,
+	renoteId: string,
+	excludeNoteId: string | undefined,
+): Promise {
 	// 指定したユーザーの指定したノートのリノートがいくつあるか数える
-	const query = Notes.createQueryBuilder('note')
-		.where('note.userId = :userId', { userId })
-		.andWhere('note.renoteId = :renoteId', { renoteId });
+	const query = Notes.createQueryBuilder("note")
+		.where("note.userId = :userId", { userId })
+		.andWhere("note.renoteId = :renoteId", { renoteId });
 
 	// 指定した投稿を除く
 	if (excludeNoteId) {
-		query.andWhere('note.id != :excludeNoteId', { excludeNoteId });
+		query.andWhere("note.id != :excludeNoteId", { excludeNoteId });
 	}
 
 	return await query.getCount();
diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts
index fa88769de..16c85ee7b 100644
--- a/packages/backend/src/misc/create-temp.ts
+++ b/packages/backend/src/misc/create-temp.ts
@@ -1,4 +1,4 @@
-import * as tmp from 'tmp';
+import * as tmp from "tmp";
 
 export function createTemp(): Promise<[string, () => void]> {
 	return new Promise<[string, () => void]>((res, rej) => {
@@ -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/detect-url-mime.ts b/packages/backend/src/misc/detect-url-mime.ts
index cd143cf2f..9f0e4325d 100644
--- a/packages/backend/src/misc/detect-url-mime.ts
+++ b/packages/backend/src/misc/detect-url-mime.ts
@@ -1,6 +1,6 @@
-import { createTemp } from './create-temp.js';
-import { downloadUrl } from './download-url.js';
-import { detectType } from './get-file-info.js';
+import { createTemp } from "./create-temp.js";
+import { downloadUrl } from "./download-url.js";
+import { detectType } from "./get-file-info.js";
 
 export async function detectUrlMime(url: string) {
 	const [path, cleanup] = await createTemp();
diff --git a/packages/backend/src/misc/download-text-file.ts b/packages/backend/src/misc/download-text-file.ts
index c62c70ee3..9d3821b20 100644
--- a/packages/backend/src/misc/download-text-file.ts
+++ b/packages/backend/src/misc/download-text-file.ts
@@ -1,10 +1,10 @@
-import * as fs from 'node:fs';
-import * as util from 'node:util';
-import Logger from '@/services/logger.js';
-import { createTemp } from './create-temp.js';
-import { downloadUrl } from './download-url.js';
+import * as fs from "node:fs";
+import * as util from "node:util";
+import Logger from "@/services/logger.js";
+import { createTemp } from "./create-temp.js";
+import { downloadUrl } from "./download-url.js";
 
-const logger = new Logger('download-text-file');
+const logger = new Logger("download-text-file");
 
 export async function downloadTextFile(url: string): Promise {
 	// Create temp file
@@ -16,7 +16,7 @@ export async function downloadTextFile(url: string): Promise {
 		// write content at URL to temp file
 		await downloadUrl(url, path);
 
-		const text = await util.promisify(fs.readFile)(path, 'utf8');
+		const text = await util.promisify(fs.readFile)(path, "utf8");
 
 		return text;
 	} finally {
diff --git a/packages/backend/src/misc/download-url.ts b/packages/backend/src/misc/download-url.ts
index 7c57b140e..7fafb635b 100644
--- a/packages/backend/src/misc/download-url.ts
+++ b/packages/backend/src/misc/download-url.ts
@@ -1,18 +1,18 @@
-import * as fs from 'node:fs';
-import * as stream from 'node:stream';
-import * as util from 'node:util';
-import got, * as Got from 'got';
-import { httpAgent, httpsAgent, StatusError } from './fetch.js';
-import config from '@/config/index.js';
-import chalk from 'chalk';
-import Logger from '@/services/logger.js';
-import IPCIDR from 'ip-cidr';
-import PrivateIp from 'private-ip';
+import * as fs from "node:fs";
+import * as stream from "node:stream";
+import * as util from "node:util";
+import got, * as Got from "got";
+import { httpAgent, httpsAgent, StatusError } from "./fetch.js";
+import config from "@/config/index.js";
+import chalk from "chalk";
+import Logger from "@/services/logger.js";
+import IPCIDR from "ip-cidr";
+import PrivateIp from "private-ip";
 
 const pipeline = util.promisify(stream.pipeline);
 
 export async function downloadUrl(url: string, path: string): Promise {
-	const logger = new Logger('download');
+	const logger = new Logger("download");
 
 	logger.info(`Downloading ${chalk.cyan(url)} ...`);
 
@@ -20,55 +20,69 @@ export async function downloadUrl(url: string, path: string): Promise {
 	const operationTimeout = 60 * 1000;
 	const maxSize = config.maxFileSize || 262144000;
 
-	const req = got.stream(url, {
-		headers: {
-			'User-Agent': config.userAgent,
-		},
-		timeout: {
-			lookup: timeout,
-			connect: timeout,
-			secureConnect: timeout,
-			socket: timeout,	// read timeout
-			response: timeout,
-			send: timeout,
-			request: operationTimeout,	// whole operation timeout
-		},
-		agent: {
-			http: httpAgent,
-			https: httpsAgent,
-		},
-		http2: false,	// default
-		retry: {
-			limit: 0,
-		},
-	}).on('response', (res: Got.Response) => {
-		if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !config.proxy && res.ip) {
-			if (isPrivateIp(res.ip)) {
-				logger.warn(`Blocked address: ${res.ip}`);
-				req.destroy();
+	const req = got
+		.stream(url, {
+			headers: {
+				"User-Agent": config.userAgent,
+			},
+			timeout: {
+				lookup: timeout,
+				connect: timeout,
+				secureConnect: timeout,
+				socket: timeout, // read timeout
+				response: timeout,
+				send: timeout,
+				request: operationTimeout, // whole operation timeout
+			},
+			agent: {
+				http: httpAgent,
+				https: httpsAgent,
+			},
+			http2: false, // default
+			retry: {
+				limit: 0,
+			},
+		})
+		.on("response", (res: Got.Response) => {
+			if (
+				(process.env.NODE_ENV === "production" ||
+					process.env.NODE_ENV === "test") &&
+				!config.proxy &&
+				res.ip
+			) {
+				if (isPrivateIp(res.ip)) {
+					logger.warn(`Blocked address: ${res.ip}`);
+					req.destroy();
+				}
 			}
-		}
 
-		const contentLength = res.headers['content-length'];
-		if (contentLength != null) {
-			const size = Number(contentLength);
-			if (size > maxSize) {
-				logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
+			const contentLength = res.headers["content-length"];
+			if (contentLength != null) {
+				const size = Number(contentLength);
+				if (size > maxSize) {
+					logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`);
+					req.destroy();
+				}
+			}
+		})
+		.on("downloadProgress", (progress: Got.Progress) => {
+			if (progress.transferred > maxSize) {
+				logger.warn(
+					`maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`,
+				);
 				req.destroy();
 			}
-		}
-	}).on('downloadProgress', (progress: Got.Progress) => {
-		if (progress.transferred > maxSize) {
-			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);
+			throw new StatusError(
+				`${e.response.statusCode} ${e.response.statusMessage}`,
+				e.response.statusCode,
+				e.response.statusMessage,
+			);
 		} else {
 			throw e;
 		}
diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts
index ca224971c..573034f6b 100644
--- a/packages/backend/src/misc/emoji-regex.ts
+++ b/packages/backend/src/misc/emoji-regex.ts
@@ -1,4 +1,4 @@
-import twemoji from 'twemoji-parser/dist/lib/regex.js';
+import twemoji from "twemoji-parser/dist/lib/regex.js";
 const twemojiRegex = twemoji.default;
 
 export const emojiRegex = new RegExp(`(${twemojiRegex.source})`);
diff --git a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
index a0319d8dd..7de32e6d6 100644
--- a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
+++ b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
@@ -1,10 +1,10 @@
-import * as mfm from 'mfm-js';
-import { unique } from '@/prelude/array.js';
+import * as mfm from "mfm-js";
+import { unique } from "@/prelude/array.js";
 
 export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {
 	const emojiNodes = mfm.extract(nodes, (node) => {
-		return (node.type === 'emojiCode' && node.props.name.length <= 100);
+		return node.type === "emojiCode" && node.props.name.length <= 100;
 	});
 
-	return unique(emojiNodes.map(x => x.props.name));
+	return unique(emojiNodes.map((x) => x.props.name));
 }
diff --git a/packages/backend/src/misc/extract-hashtags.ts b/packages/backend/src/misc/extract-hashtags.ts
index 0b0418eef..826e36221 100644
--- a/packages/backend/src/misc/extract-hashtags.ts
+++ b/packages/backend/src/misc/extract-hashtags.ts
@@ -1,9 +1,9 @@
-import * as mfm from 'mfm-js';
-import { unique } from '@/prelude/array.js';
+import * as mfm from "mfm-js";
+import { unique } from "@/prelude/array.js";
 
 export function extractHashtags(nodes: mfm.MfmNode[]): string[] {
-	const hashtagNodes = mfm.extract(nodes, (node) => node.type === 'hashtag');
-	const hashtags = unique(hashtagNodes.map(x => x.props.hashtag));
+	const hashtagNodes = mfm.extract(nodes, (node) => node.type === "hashtag");
+	const hashtags = unique(hashtagNodes.map((x) => x.props.hashtag));
 
 	return hashtags;
 }
diff --git a/packages/backend/src/misc/extract-mentions.ts b/packages/backend/src/misc/extract-mentions.ts
index cc19b161a..259f78e57 100644
--- a/packages/backend/src/misc/extract-mentions.ts
+++ b/packages/backend/src/misc/extract-mentions.ts
@@ -1,11 +1,13 @@
 // test is located in test/extract-mentions
 
-import * as mfm from 'mfm-js';
+import * as mfm from "mfm-js";
 
-export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
+export function extractMentions(
+	nodes: mfm.MfmNode[],
+): mfm.MfmMention["props"][] {
 	// TODO: 重複を削除
-	const mentionNodes = mfm.extract(nodes, (node) => node.type === 'mention');
-	const mentions = mentionNodes.map(x => x.props);
+	const mentionNodes = mfm.extract(nodes, (node) => node.type === "mention");
+	const mentions = mentionNodes.map((x) => x.props);
 
 	return mentions;
 }
diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts
index 3e74118f0..32c45813c 100644
--- a/packages/backend/src/misc/fetch-meta.ts
+++ b/packages/backend/src/misc/fetch-meta.ts
@@ -1,16 +1,16 @@
-import { db } from '@/db/postgre.js';
-import { Meta } from '@/models/entities/meta.js';
+import { db } from "@/db/postgre.js";
+import { Meta } from "@/models/entities/meta.js";
 
 let cache: Meta;
 
 export async function fetchMeta(noCache = false): Promise {
 	if (!noCache && cache) return cache;
 
-	return await db.transaction(async transactionalEntityManager => {
+	return await db.transaction(async (transactionalEntityManager) => {
 		// New IDs are prioritized because multiple records may have been created due to past bugs.
 		const metas = await transactionalEntityManager.find(Meta, {
 			order: {
-				id: 'DESC',
+				id: "DESC",
 			},
 		});
 
@@ -25,11 +25,13 @@ export async function fetchMeta(noCache = false): Promise {
 				.upsert(
 					Meta,
 					{
-						id: 'x',
+						id: "x",
 					},
-					['id'],
+					["id"],
 				)
-				.then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]));
+				.then((x) =>
+					transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]),
+				);
 
 			cache = saved;
 			return saved;
@@ -38,7 +40,7 @@ export async function fetchMeta(noCache = false): Promise {
 }
 
 setInterval(() => {
-	fetchMeta(true).then(meta => {
+	fetchMeta(true).then((meta) => {
 		cache = meta;
 	});
 }, 1000 * 10);
diff --git a/packages/backend/src/misc/fetch-proxy-account.ts b/packages/backend/src/misc/fetch-proxy-account.ts
index b61bba264..a277db6fb 100644
--- a/packages/backend/src/misc/fetch-proxy-account.ts
+++ b/packages/backend/src/misc/fetch-proxy-account.ts
@@ -1,9 +1,11 @@
-import { fetchMeta } from './fetch-meta.js';
-import { ILocalUser } from '@/models/entities/user.js';
-import { Users } from '@/models/index.js';
+import { fetchMeta } from "./fetch-meta.js";
+import type { ILocalUser } from "@/models/entities/user.js";
+import { Users } from "@/models/index.js";
 
 export async function fetchProxyAccount(): Promise {
 	const meta = await fetchMeta();
 	if (meta.proxyAccountId == null) return null;
-	return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser;
+	return (await Users.findOneByOrFail({
+		id: meta.proxyAccountId,
+	})) as ILocalUser;
 }
diff --git a/packages/backend/src/misc/fetch.ts b/packages/backend/src/misc/fetch.ts
index af6bf2fca..0e673ba3a 100644
--- a/packages/backend/src/misc/fetch.ts
+++ b/packages/backend/src/misc/fetch.ts
@@ -1,40 +1,63 @@
-import * as http from 'node:http';
-import * as https from 'node:https';
-import { URL } from 'node:url';
-import CacheableLookup from 'cacheable-lookup';
-import fetch from 'node-fetch';
-import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
-import config from '@/config/index.js';
+import * as http from "node:http";
+import * as https from "node:https";
+import type { URL } from "node:url";
+import CacheableLookup from "cacheable-lookup";
+import fetch from "node-fetch";
+import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
+import config from "@/config/index.js";
 
-export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record) {
+export async function getJson(
+	url: string,
+	accept = "application/json, */*",
+	timeout = 10000,
+	headers?: Record,
+) {
 	const res = await getResponse({
 		url,
-		method: 'GET',
-		headers: Object.assign({
-			'User-Agent': config.userAgent,
-			Accept: accept,
-		}, headers || {}),
+		method: "GET",
+		headers: Object.assign(
+			{
+				"User-Agent": config.userAgent,
+				Accept: accept,
+			},
+			headers || {},
+		),
 		timeout,
 	});
 
 	return await res.json();
 }
 
-export async function getHtml(url: string, accept = 'text/html, */*', timeout = 10000, headers?: Record) {
+export async function getHtml(
+	url: string,
+	accept = "text/html, */*",
+	timeout = 10000,
+	headers?: Record,
+) {
 	const res = await getResponse({
 		url,
-		method: 'GET',
-		headers: Object.assign({
-			'User-Agent': config.userAgent,
-			Accept: accept,
-		}, headers || {}),
+		method: "GET",
+		headers: Object.assign(
+			{
+				"User-Agent": config.userAgent,
+				Accept: accept,
+			},
+			headers || {},
+		),
 		timeout,
 	});
 
 	return await res.text();
 }
 
-export async function getResponse(args: { url: string, method: string, body?: string, headers: Record, timeout?: number, size?: number }) {
+export async function getResponse(args: {
+	url: string;
+	method: string;
+	body?: string;
+	headers: Record;
+	timeout?: number;
+	size?: number;
+}) {
 	const timeout = args.timeout || 10 * 1000;
 
 	const controller = new AbortController();
@@ -53,16 +76,20 @@ export async function getResponse(args: { url: string, method: string, body?: st
 	});
 
 	if (!res.ok) {
-		throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
+		throw new StatusError(
+			`${res.status} ${res.statusText}`,
+			res.status,
+			res.statusText,
+		);
 	}
 
 	return res;
 }
 
 const cache = new CacheableLookup({
-	maxTtl: 3600,	// 1hours
-	errorTtl: 30,	// 30secs
-	lookup: false,	// nativeのdns.lookupにfallbackしない
+	maxTtl: 3600, // 1hours
+	errorTtl: 30, // 30secs
+	lookup: false, // nativeのdns.lookupにfallbackしない
 });
 
 /**
@@ -90,13 +117,13 @@ const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
  */
 export const httpAgent = config.proxy
 	? new HttpProxyAgent({
-		keepAlive: true,
-		keepAliveMsecs: 30 * 1000,
-		maxSockets,
-		maxFreeSockets: 256,
-		scheduling: 'lifo',
-		proxy: config.proxy,
-	})
+			keepAlive: true,
+			keepAliveMsecs: 30 * 1000,
+			maxSockets,
+			maxFreeSockets: 256,
+			scheduling: "lifo",
+			proxy: config.proxy,
+	  })
 	: _http;
 
 /**
@@ -104,13 +131,13 @@ export const httpAgent = config.proxy
  */
 export const httpsAgent = config.proxy
 	? new HttpsProxyAgent({
-		keepAlive: true,
-		keepAliveMsecs: 30 * 1000,
-		maxSockets,
-		maxFreeSockets: 256,
-		scheduling: 'lifo',
-		proxy: config.proxy,
-	})
+			keepAlive: true,
+			keepAliveMsecs: 30 * 1000,
+			maxSockets,
+			maxFreeSockets: 256,
+			scheduling: "lifo",
+			proxy: config.proxy,
+	  })
 	: _https;
 
 /**
@@ -120,9 +147,9 @@ export const httpsAgent = config.proxy
  */
 export function getAgentByUrl(url: URL, bypassProxy = false) {
 	if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
-		return url.protocol === 'http:' ? _http : _https;
+		return url.protocol === "http:" ? _http : _https;
 	} else {
-		return url.protocol === 'http:' ? httpAgent : httpsAgent;
+		return url.protocol === "http:" ? httpAgent : httpsAgent;
 	}
 }
 
@@ -133,9 +160,12 @@ export class StatusError extends Error {
 
 	constructor(message: string, statusCode: number, statusMessage?: string) {
 		super(message);
-		this.name = 'StatusError';
+		this.name = "StatusError";
 		this.statusCode = statusCode;
 		this.statusMessage = statusMessage;
-		this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
+		this.isClientError =
+			typeof this.statusCode === "number" &&
+			this.statusCode >= 400 &&
+			this.statusCode < 500;
 	}
 }
diff --git a/packages/backend/src/misc/gen-id.ts b/packages/backend/src/misc/gen-id.ts
index fcf476857..b7cc0965a 100644
--- a/packages/backend/src/misc/gen-id.ts
+++ b/packages/backend/src/misc/gen-id.ts
@@ -1,21 +1,27 @@
-import { ulid } from 'ulid';
-import { genAid } from './id/aid.js';
-import { genMeid } from './id/meid.js';
-import { genMeidg } from './id/meidg.js';
-import { genObjectId } from './id/object-id.js';
-import config from '@/config/index.js';
+import { ulid } from "ulid";
+import { genAid } from "./id/aid.js";
+import { genMeid } from "./id/meid.js";
+import { genMeidg } from "./id/meidg.js";
+import { genObjectId } from "./id/object-id.js";
+import config from "@/config/index.js";
 
 const metohd = config.id.toLowerCase();
 
 export function genId(date?: Date): string {
-	if (!date || (date > new Date())) date = new Date();
+	if (!date || date > new Date()) date = new Date();
 
 	switch (metohd) {
-		case 'aid': return genAid(date);
-		case 'meid': return genMeid(date);
-		case 'meidg': return genMeidg(date);
-		case 'ulid': return ulid(date.getTime());
-		case 'objectid': return genObjectId(date);
-		default: throw new Error('unrecognized id generation method');
+		case "aid":
+			return genAid(date);
+		case "meid":
+			return genMeid(date);
+		case "meidg":
+			return genMeidg(date);
+		case "ulid":
+			return ulid(date.getTime());
+		case "objectid":
+			return genObjectId(date);
+		default:
+			throw new Error("unrecognized id generation method");
 	}
 }
diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts
index 322ffee22..ba0f9a28b 100644
--- a/packages/backend/src/misc/gen-identicon.ts
+++ b/packages/backend/src/misc/gen-identicon.ts
@@ -3,37 +3,37 @@
  * 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";
+import * as p from "pureimage";
+import gen from "random-seed";
 
 const size = 128; // px
 const n = 5; // resolution
-const margin = (size / 4);
+const margin = size / 4;
 const colors = [
-	['#FF512F', '#DD2476'],
-	['#FF61D2', '#FE9090'],
-	['#72FFB6', '#10D164'],
-	['#FD8451', '#FFBD6F'],
-	['#305170', '#6DFC6B'],
-	['#00C0FF', '#4218B8'],
-	['#009245', '#FCEE21'],
-	['#0100EC', '#FB36F4'],
-	['#FDABDD', '#374A5A'],
-	['#38A2D7', '#561139'],
-	['#121C84', '#8278DA'],
-	['#5761B2', '#1FC5A8'],
-	['#FFDB01', '#0E197D'],
-	['#FF3E9D', '#0E1F40'],
-	['#766eff', '#00d4ff'],
-	['#9bff6e', '#00d4ff'],
-	['#ff6e94', '#00d4ff'],
-	['#ffa96e', '#00d4ff'],
-	['#ffa96e', '#ff009d'],
-	['#ffdd6e', '#ff009d'],
+	["#FF512F", "#DD2476"],
+	["#FF61D2", "#FE9090"],
+	["#72FFB6", "#10D164"],
+	["#FD8451", "#FFBD6F"],
+	["#305170", "#6DFC6B"],
+	["#00C0FF", "#4218B8"],
+	["#009245", "#FCEE21"],
+	["#0100EC", "#FB36F4"],
+	["#FDABDD", "#374A5A"],
+	["#38A2D7", "#561139"],
+	["#121C84", "#8278DA"],
+	["#5761B2", "#1FC5A8"],
+	["#FFDB01", "#0E197D"],
+	["#FF3E9D", "#0E1F40"],
+	["#766eff", "#00d4ff"],
+	["#9bff6e", "#00d4ff"],
+	["#ff6e94", "#00d4ff"],
+	["#ffa96e", "#00d4ff"],
+	["#ffa96e", "#ff009d"],
+	["#ffdd6e", "#ff009d"],
 ];
 
-const actualSize = size - (margin * 2);
+const actualSize = size - margin * 2;
 const cellSize = actualSize / n;
 const sideN = Math.floor(n / 2);
 
@@ -43,7 +43,7 @@ const sideN = Math.floor(n / 2);
 export function genIdenticon(seed: string, stream: WriteStream): Promise {
 	const rand = gen.create(seed);
 	const canvas = p.make(size, size, undefined);
-	const ctx = canvas.getContext('2d');
+	const ctx = canvas.getContext("2d");
 
 	const bgColors = colors[rand(colors.length)];
 
@@ -55,7 +55,7 @@ export function genIdenticon(seed: string, stream: WriteStream): Promise {
 	ctx.beginPath();
 	ctx.fillRect(0, 0, size, size);
 
-	ctx.fillStyle = '#ffffff';
+	ctx.fillStyle = "#ffffff";
 
 	// side bitmap (filled by false)
 	const side: boolean[][] = new Array(sideN);
@@ -80,17 +80,17 @@ export function genIdenticon(seed: string, stream: WriteStream): Promise {
 	// Draw
 	for (let x = 0; x < n; x++) {
 		for (let y = 0; y < n; y++) {
-			const isXCenter = x === ((n - 1) / 2);
+			const isXCenter = x === (n - 1) / 2;
 			if (isXCenter && !center[y]) continue;
 
-			const isLeftSide = x < ((n - 1) / 2);
+			const isLeftSide = x < (n - 1) / 2;
 			if (isLeftSide && !side[x][y]) continue;
 
-			const isRightSide = x > ((n - 1) / 2);
+			const isRightSide = x > (n - 1) / 2;
 			if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
 
-			const actualX = margin + (cellSize * x);
-			const actualY = margin + (cellSize * y);
+			const actualX = margin + cellSize * x;
+			const actualY = margin + cellSize * y;
 			ctx.beginPath();
 			ctx.fillRect(actualX, actualY, cellSize, cellSize);
 		}
diff --git a/packages/backend/src/misc/gen-key-pair.ts b/packages/backend/src/misc/gen-key-pair.ts
index e2ad59850..8ae4175e3 100644
--- a/packages/backend/src/misc/gen-key-pair.ts
+++ b/packages/backend/src/misc/gen-key-pair.ts
@@ -1,34 +1,40 @@
-import * as crypto from 'node:crypto';
-import * as util from 'node:util';
+import * as crypto from "node:crypto";
+import * as util from "node:util";
 
 const generateKeyPair = util.promisify(crypto.generateKeyPair);
 
 export async function genRsaKeyPair(modulusLength = 2048) {
-	return await generateKeyPair('rsa', {
+	return await generateKeyPair("rsa", {
 		modulusLength,
 		publicKeyEncoding: {
-			type: 'spki',
-			format: 'pem',
+			type: "spki",
+			format: "pem",
 		},
 		privateKeyEncoding: {
-			type: 'pkcs8',
-			format: 'pem',
+			type: "pkcs8",
+			format: "pem",
 			cipher: undefined,
 			passphrase: undefined,
 		},
 	});
 }
 
-export async function genEcKeyPair(namedCurve: 'prime256v1' | 'secp384r1' | 'secp521r1' | 'curve25519' = 'prime256v1') {
-	return await generateKeyPair('ec', {
+export async function genEcKeyPair(
+	namedCurve:
+		| "prime256v1"
+		| "secp384r1"
+		| "secp521r1"
+		| "curve25519" = "prime256v1",
+) {
+	return await generateKeyPair("ec", {
 		namedCurve,
 		publicKeyEncoding: {
-			type: 'spki',
-			format: 'pem',
+			type: "spki",
+			format: "pem",
 		},
 		privateKeyEncoding: {
-			type: 'pkcs8',
-			format: 'pem',
+			type: "pkcs8",
+			format: "pem",
 			cipher: undefined,
 			passphrase: undefined,
 		},
diff --git a/packages/backend/src/misc/get-file-info.ts b/packages/backend/src/misc/get-file-info.ts
index b4922779a..7ca105985 100644
--- a/packages/backend/src/misc/get-file-info.ts
+++ b/packages/backend/src/misc/get-file-info.ts
@@ -1,18 +1,18 @@
-import * as fs from 'node:fs';
-import * as crypto from 'node:crypto';
-import { join } from 'node:path';
-import * as stream from 'node:stream';
-import * as util from 'node:util';
-import { FSWatcher } from 'chokidar';
-import { fileTypeFromFile } from 'file-type';
-import FFmpeg from 'fluent-ffmpeg';
-import isSvg from 'is-svg';
-import probeImageSize from 'probe-image-size';
-import { type predictionType } from 'nsfwjs';
-import sharp from 'sharp';
-import { encode } from 'blurhash';
-import { detectSensitive } from '@/services/detect-sensitive.js';
-import { createTempDir } from './create-temp.js';
+import * as fs from "node:fs";
+import * as crypto from "node:crypto";
+import { join } from "node:path";
+import * as stream from "node:stream";
+import * as util from "node:util";
+import { FSWatcher } from "chokidar";
+import { fileTypeFromFile } from "file-type";
+import FFmpeg from "fluent-ffmpeg";
+import isSvg from "is-svg";
+import probeImageSize from "probe-image-size";
+import { type predictionType } from "nsfwjs";
+import sharp from "sharp";
+import { encode } from "blurhash";
+import { detectSensitive } from "@/services/detect-sensitive.js";
+import { createTempDir } from "./create-temp.js";
 
 const pipeline = util.promisify(stream.pipeline);
 
@@ -33,24 +33,27 @@ export type FileInfo = {
 };
 
 const TYPE_OCTET_STREAM = {
-	mime: 'application/octet-stream',
+	mime: "application/octet-stream",
 	ext: null,
 };
 
 const TYPE_SVG = {
-	mime: 'image/svg+xml',
-	ext: 'svg',
+	mime: "image/svg+xml",
+	ext: "svg",
 };
 
 /**
  * Get file information
  */
-export async function getFileInfo(path: string, opts: {
-	skipSensitiveDetection: boolean;
-	sensitiveThreshold?: number;
-	sensitiveThresholdForPorn?: number;
-	enableSensitiveMediaDetectionForVideos?: boolean;
-}): Promise {
+export async function getFileInfo(
+	path: string,
+	opts: {
+		skipSensitiveDetection: boolean;
+		sensitiveThreshold?: number;
+		sensitiveThresholdForPorn?: number;
+		enableSensitiveMediaDetectionForVideos?: boolean;
+	},
+): Promise {
 	const warnings = [] as string[];
 
 	const size = await getFileSize(path);
@@ -63,24 +66,37 @@ export async function getFileInfo(path: string, opts: {
 	let height: number | undefined;
 	let orientation: number | undefined;
 
-	if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop', 'image/avif'].includes(type.mime)) {
-		const imageSize = await detectImageSize(path).catch(e => {
+	if (
+		[
+			"image/jpeg",
+			"image/gif",
+			"image/png",
+			"image/apng",
+			"image/webp",
+			"image/bmp",
+			"image/tiff",
+			"image/svg+xml",
+			"image/vnd.adobe.photoshop",
+			"image/avif",
+		].includes(type.mime)
+	) {
+		const imageSize = await detectImageSize(path).catch((e) => {
 			warnings.push(`detectImageSize failed: ${e}`);
 			return undefined;
 		});
 
 		// うまく判定できない画像は octet-stream にする
 		if (!imageSize) {
-			warnings.push('cannot detect image dimensions');
+			warnings.push("cannot detect image dimensions");
 			type = TYPE_OCTET_STREAM;
-		} else if (imageSize.wUnits === 'px') {
+		} else if (imageSize.wUnits === "px") {
 			width = imageSize.width;
 			height = imageSize.height;
 			orientation = imageSize.orientation;
 
 			// 制限を超えている画像は octet-stream にする
 			if (imageSize.width > 16383 || imageSize.height > 16383) {
-				warnings.push('image dimensions exceeds limits');
+				warnings.push("image dimensions exceeds limits");
 				type = TYPE_OCTET_STREAM;
 			}
 		} else {
@@ -90,8 +106,18 @@ export async function getFileInfo(path: string, opts: {
 
 	let blurhash: string | undefined;
 
-	if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml', 'image/avif'].includes(type.mime)) {
-		blurhash = await getBlurhash(path).catch(e => {
+	if (
+		[
+			"image/jpeg",
+			"image/gif",
+			"image/png",
+			"image/apng",
+			"image/webp",
+			"image/svg+xml",
+			"image/avif",
+		].includes(type.mime)
+	) {
+		blurhash = await getBlurhash(path).catch((e) => {
 			warnings.push(`getBlurhash failed: ${e}`);
 			return undefined;
 		});
@@ -107,11 +133,14 @@ export async function getFileInfo(path: string, opts: {
 			opts.sensitiveThreshold ?? 0.5,
 			opts.sensitiveThresholdForPorn ?? 0.75,
 			opts.enableSensitiveMediaDetectionForVideos ?? false,
-		).then(value => {
-			[sensitive, porn] = value;
-		}, error => {
-			warnings.push(`detectSensitivity failed: ${error}`);
-		});
+		).then(
+			(value) => {
+				[sensitive, porn] = value;
+			},
+			(error) => {
+				warnings.push(`detectSensitivity failed: ${error}`);
+			},
+		);
 	}
 
 	return {
@@ -128,71 +157,100 @@ export async function getFileInfo(path: string, opts: {
 	};
 }
 
-async function detectSensitivity(source: string, mime: string, sensitiveThreshold: number, sensitiveThresholdForPorn: number, analyzeVideo: boolean): Promise<[sensitive: boolean, porn: boolean]> {
+async function detectSensitivity(
+	source: string,
+	mime: string,
+	sensitiveThreshold: number,
+	sensitiveThresholdForPorn: number,
+	analyzeVideo: boolean,
+): Promise<[sensitive: boolean, porn: boolean]> {
 	let sensitive = false;
 	let porn = false;
 
-	function judgePrediction(result: readonly predictionType[]): [sensitive: boolean, porn: boolean] {
+	function judgePrediction(
+		result: readonly predictionType[],
+	): [sensitive: boolean, porn: boolean] {
 		let sensitive = false;
 		let porn = false;
 
-		if ((result.find(x => x.className === 'Sexy')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
-		if ((result.find(x => x.className === 'Hentai')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
-		if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThreshold) sensitive = true;
+		if (
+			(result.find((x) => x.className === "Sexy")?.probability ?? 0) >
+			sensitiveThreshold
+		)
+			sensitive = true;
+		if (
+			(result.find((x) => x.className === "Hentai")?.probability ?? 0) >
+			sensitiveThreshold
+		)
+			sensitive = true;
+		if (
+			(result.find((x) => x.className === "Porn")?.probability ?? 0) >
+			sensitiveThreshold
+		)
+			sensitive = true;
 
-		if ((result.find(x => x.className === 'Porn')?.probability ?? 0) > sensitiveThresholdForPorn) porn = true;
+		if (
+			(result.find((x) => x.className === "Porn")?.probability ?? 0) >
+			sensitiveThresholdForPorn
+		)
+			porn = true;
 
 		return [sensitive, porn];
 	}
 
-	if (['image/jpeg', 'image/png', 'image/webp'].includes(mime)) {
+	if (["image/jpeg", "image/png", "image/webp"].includes(mime)) {
 		const result = await detectSensitive(source);
 		if (result) {
 			[sensitive, porn] = judgePrediction(result);
 		}
-	} else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
+	} else if (
+		analyzeVideo &&
+		(mime === "image/apng" || mime.startsWith("video/"))
+	) {
 		const [outDir, disposeOutDir] = await createTempDir();
 		try {
 			const command = FFmpeg()
 				.input(source)
 				.inputOptions([
-					'-skip_frame', 'nokey', // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
-					'-lowres', '3', // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
+					"-skip_frame",
+					"nokey", // 可能ならキーフレームのみを取得してほしいとする(そうなるとは限らない)
+					"-lowres",
+					"3", // 元の画質でデコードする必要はないので 1/8 画質でデコードしてもよいとする(そうなるとは限らない)
 				])
 				.noAudio()
 				.videoFilters([
 					{
-						filter: 'select', // フレームのフィルタリング
+						filter: "select", // フレームのフィルタリング
 						options: {
-							e: 'eq(pict_type,PICT_TYPE_I)', // I-Frame のみをフィルタする(VP9 とかはデコードしてみないとわからないっぽい)
+							e: "eq(pict_type,PICT_TYPE_I)", // I-Frame のみをフィルタする(VP9 とかはデコードしてみないとわからないっぽい)
 						},
 					},
 					{
-						filter: 'blackframe', // 暗いフレームの検出
+						filter: "blackframe", // 暗いフレームの検出
 						options: {
-							amount: '0', // 暗さに関わらず全てのフレームで測定値を取る
+							amount: "0", // 暗さに関わらず全てのフレームで測定値を取る
 						},
 					},
 					{
-						filter: 'metadata',
+						filter: "metadata",
 						options: {
-							mode: 'select', // フレーム選択モード
-							key: 'lavfi.blackframe.pblack', // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
-							value: '50',
-							function: 'less', // 50% 未満のフレームを選択する(50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
+							mode: "select", // フレーム選択モード
+							key: "lavfi.blackframe.pblack", // フレームにおける暗部の百分率(前のフィルタからのメタデータを参照する)
+							value: "50",
+							function: "less", // 50% 未満のフレームを選択する(50% 以上暗部があるフレームだと誤検知を招くかもしれないので)
 						},
 					},
 					{
-						filter: 'scale',
+						filter: "scale",
 						options: {
 							w: 299,
 							h: 299,
 						},
 					},
 				])
-				.format('image2')
-				.output(join(outDir, '%d.png'))
-				.outputOptions(['-vsync', '0']); // 可変フレームレートにすることで穴埋めをさせない
+				.format("image2")
+				.output(join(outDir, "%d.png"))
+				.outputOptions(["-vsync", "0"]); // 可変フレームレートにすることで穴埋めをさせない
 			const results: ReturnType[] = [];
 			let frameIndex = 0;
 			let targetIndex = 0;
@@ -213,8 +271,12 @@ async function detectSensitivity(source: string, mime: string, sensitiveThreshol
 					fs.promises.unlink(path);
 				}
 			}
-			sensitive = results.filter(x => x[0]).length >= Math.ceil(results.length * sensitiveThreshold);
-			porn = results.filter(x => x[1]).length >= Math.ceil(results.length * sensitiveThresholdForPorn);
+			sensitive =
+				results.filter((x) => x[0]).length >=
+				Math.ceil(results.length * sensitiveThreshold);
+			porn =
+				results.filter((x) => x[1]).length >=
+				Math.ceil(results.length * sensitiveThresholdForPorn);
 		} finally {
 			disposeOutDir();
 		}
@@ -223,35 +285,41 @@ async function detectSensitivity(source: string, mime: string, sensitiveThreshol
 	return [sensitive, porn];
 }
 
-async function* asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator {
+async function* asyncIterateFrames(
+	cwd: string,
+	command: FFmpeg.FfmpegCommand,
+): AsyncGenerator {
 	const watcher = new FSWatcher({
 		cwd,
 		disableGlobbing: true,
 	});
 	let finished = false;
-	command.once('end', () => {
+	command.once("end", () => {
 		finished = true;
 		watcher.close();
 	});
 	command.run();
-	for (let i = 1; true; i++) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
+	for (let i = 1; true; i++) {
+		// eslint-disable-line @typescript-eslint/no-unnecessary-condition
 		const current = `${i}.png`;
 		const next = `${i + 1}.png`;
 		const framePath = join(cwd, current);
 		if (await exists(join(cwd, next))) {
 			yield framePath;
-		} else if (!finished) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
+		} else if (!finished) {
+			// eslint-disable-line @typescript-eslint/no-unnecessary-condition
 			watcher.add(next);
 			await new Promise((resolve, reject) => {
-				watcher.on('add', function onAdd(path) {
-					if (path === next) { // 次フレームの書き出しが始まっているなら、現在フレームの書き出しは終わっている
+				watcher.on("add", function onAdd(path) {
+					if (path === next) {
+						// 次フレームの書き出しが始まっているなら、現在フレームの書き出しは終わっている
 						watcher.unwatch(current);
-						watcher.off('add', onAdd);
+						watcher.off("add", onAdd);
 						resolve();
 					}
 				});
-				command.once('end', resolve); // 全てのフレームを処理し終わったなら、最終フレームである現在フレームの書き出しは終わっている
-				command.once('error', reject);
+				command.once("end", resolve); // 全てのフレームを処理し終わったなら、最終フレームである現在フレームの書き出しは終わっている
+				command.once("error", reject);
 			});
 			yield framePath;
 		} else if (await exists(framePath)) {
@@ -263,7 +331,10 @@ async function* asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand):
 }
 
 function exists(path: string): Promise {
-	return fs.promises.access(path).then(() => true, () => false);
+	return fs.promises.access(path).then(
+		() => true,
+		() => false,
+	);
 }
 
 /**
@@ -283,7 +354,7 @@ export async function detectType(path: string): Promise<{
 
 	if (type) {
 		// XMLはSVGかもしれない
-		if (type.mime === 'application/xml' && await checkSvg(path)) {
+		if (type.mime === "application/xml" && (await checkSvg(path))) {
 			return TYPE_SVG;
 		}
 
@@ -327,7 +398,7 @@ export async function getFileSize(path: string): Promise {
  * Calculate MD5 hash
  */
 async function calcHash(path: string): Promise {
-	const hash = crypto.createHash('md5').setEncoding('hex');
+	const hash = crypto.createHash("md5").setEncoding("hex");
 	await pipeline(fs.createReadStream(path), hash);
 	return hash.read();
 }
@@ -356,7 +427,7 @@ function getBlurhash(path: string): Promise {
 		sharp(path)
 			.raw()
 			.ensureAlpha()
-			.resize(64, 64, { fit: 'inside' })
+			.resize(64, 64, { fit: "inside" })
 			.toBuffer((err, buffer, { width, height }) => {
 				if (err) return reject(err);
 
diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts
index 379325bb1..caf832f90 100644
--- a/packages/backend/src/misc/get-ip-hash.ts
+++ b/packages/backend/src/misc/get-ip-hash.ts
@@ -1,9 +1,9 @@
-import IPCIDR from 'ip-cidr';
+import IPCIDR from "ip-cidr";
 
 export function getIpHash(ip: string) {
 	// because a single person may control many IPv6 addresses,
 	// only a /64 subnet prefix of any IP will be taken into account.
 	// (this means for IPv4 the entire address is used)
 	const prefix = IPCIDR.createAddress(ip).mask(64);
-	return 'ip-' + BigInt('0b' + prefix).toString(36);
+	return `ip-${BigInt(`0b${prefix}`).toString(36)}`;
 }
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index c39cdcc4b..446e3fc14 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -1,21 +1,21 @@
-import { Packed } from './schema.js';
+import type { Packed } from "./schema.js";
 
 /**
  * 投稿を表す文字列を取得します。
  * @param {*} note (packされた)投稿
  */
-export const getNoteSummary = (note: Packed<'Note'>): string => {
+export const getNoteSummary = (note: Packed<"Note">): string => {
 	if (note.deletedAt) {
-		return `❌`;
+		return "❌";
 	}
 
-	let summary = '';
+	let summary = "";
 
 	// 本文
 	if (note.cw != null) {
 		summary += note.cw;
 	} else {
-		summary += note.text ? note.text : '';
+		summary += note.text ? note.text : "";
 	}
 
 	// ファイルが添付されているとき
@@ -25,7 +25,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
 
 	// 投票が添付されているとき
 	if (note.poll) {
-		summary += ` (📊)`;
+		summary += " (📊)";
 	}
 
 	/*
diff --git a/packages/backend/src/misc/get-reaction-emoji.ts b/packages/backend/src/misc/get-reaction-emoji.ts
index c2e0b9858..71521c4ae 100644
--- a/packages/backend/src/misc/get-reaction-emoji.ts
+++ b/packages/backend/src/misc/get-reaction-emoji.ts
@@ -1,16 +1,28 @@
-export default function(reaction: string): string {
+export default function (reaction: string): string {
 	switch (reaction) {
-		case 'like': return '👍';
-		case 'love': return '❤️';
-		case 'laugh': return '😆';
-		case 'hmm': return '🤔';
-		case 'surprise': return '😮';
-		case 'congrats': return '🎉';
-		case 'angry': return '💢';
-		case 'confused': return '😥';
-		case 'rip': return '😇';
-		case 'pudding': return '🍮';
-		case 'star': return '⭐';
-		default: return reaction;
+		case "like":
+			return "👍";
+		case "love":
+			return "❤️";
+		case "laugh":
+			return "😆";
+		case "hmm":
+			return "🤔";
+		case "surprise":
+			return "😮";
+		case "congrats":
+			return "🎉";
+		case "angry":
+			return "💢";
+		case "confused":
+			return "😥";
+		case "rip":
+			return "😇";
+		case "pudding":
+			return "🍮";
+		case "star":
+			return "⭐";
+		default:
+			return reaction;
 	}
 }
diff --git a/packages/backend/src/misc/hard-limits.ts b/packages/backend/src/misc/hard-limits.ts
index 1039f7335..4ba90293c 100644
--- a/packages/backend/src/misc/hard-limits.ts
+++ b/packages/backend/src/misc/hard-limits.ts
@@ -1,4 +1,3 @@
-
 // If you change DB_* values, you must also change the DB schema.
 
 /**
diff --git a/packages/backend/src/misc/i18n.ts b/packages/backend/src/misc/i18n.ts
index 4fa398763..742bdb0f6 100644
--- a/packages/backend/src/misc/i18n.ts
+++ b/packages/backend/src/misc/i18n.ts
@@ -13,7 +13,7 @@ export class I18n> {
 	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
 	public t(key: string, args?: Record): string {
 		try {
-			let str = key.split('.').reduce((o, i) => o[i], this.locale) as string;
+			let str = key.split(".").reduce((o, i) => o[i], this.locale) as string;
 
 			if (args) {
 				for (const [k, v] of Object.entries(args)) {
diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts
index 87e688826..a12360360 100644
--- a/packages/backend/src/misc/id/aid.ts
+++ b/packages/backend/src/misc/id/aid.ts
@@ -1,7 +1,7 @@
 // AID
 // 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
 
-import * as crypto from 'node:crypto';
+import * as crypto from "node:crypto";
 
 const TIME2000 = 946684800000;
 let counter = crypto.randomBytes(2).readUInt16LE(0);
@@ -10,16 +10,16 @@ function getTime(time: number) {
 	time = time - TIME2000;
 	if (time < 0) time = 0;
 
-	return time.toString(36).padStart(8, '0');
+	return time.toString(36).padStart(8, "0");
 }
 
 function getNoise() {
-	return counter.toString(36).padStart(2, '0').slice(-2);
+	return counter.toString(36).padStart(2, "0").slice(-2);
 }
 
 export function genAid(date: Date): string {
 	const t = date.getTime();
-	if (isNaN(t)) throw 'Failed to create AID: Invalid Date';
+	if (isNaN(t)) throw "Failed to create AID: Invalid Date";
 	counter++;
 	return getTime(t) + getNoise();
 }
diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts
index 30bbdf169..ee78eb8d1 100644
--- a/packages/backend/src/misc/id/meid.ts
+++ b/packages/backend/src/misc/id/meid.ts
@@ -1,4 +1,4 @@
-const CHARS = '0123456789abcdef';
+const CHARS = "0123456789abcdef";
 
 function getTime(time: number) {
 	if (time < 0) time = 0;
@@ -12,7 +12,7 @@ function getTime(time: number) {
 }
 
 function getRandom() {
-	let str = '';
+	let str = "";
 
 	for (let i = 0; i < 12; i++) {
 		str += CHARS[Math.floor(Math.random() * CHARS.length)];
diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts
index d4aaaea1b..4fd39a8b4 100644
--- a/packages/backend/src/misc/id/meidg.ts
+++ b/packages/backend/src/misc/id/meidg.ts
@@ -1,4 +1,4 @@
-const CHARS = '0123456789abcdef';
+const CHARS = "0123456789abcdef";
 
 //  4bit Fixed hex value 'g'
 // 44bit UNIX Time ms in Hex
@@ -14,7 +14,7 @@ function getTime(time: number) {
 }
 
 function getRandom() {
-	let str = '';
+	let str = "";
 
 	for (let i = 0; i < 12; i++) {
 		str += CHARS[Math.floor(Math.random() * CHARS.length)];
@@ -24,5 +24,5 @@ function getRandom() {
 }
 
 export function genMeidg(date: Date): string {
-	return 'g' + getTime(date.getTime()) + getRandom();
+	return `g${getTime(date.getTime())}${getRandom()}`;
 }
diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts
index 392ea4330..45822f0ac 100644
--- a/packages/backend/src/misc/id/object-id.ts
+++ b/packages/backend/src/misc/id/object-id.ts
@@ -1,4 +1,4 @@
-const CHARS = '0123456789abcdef';
+const CHARS = "0123456789abcdef";
 
 function getTime(time: number) {
 	if (time < 0) time = 0;
@@ -12,7 +12,7 @@ function getTime(time: number) {
 }
 
 function getRandom() {
-	let str = '';
+	let str = "";
 
 	for (let i = 0; i < 16; i++) {
 		str += CHARS[Math.floor(Math.random() * CHARS.length)];
diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts
index 2d7c6bd0c..be6eb5bd8 100644
--- a/packages/backend/src/misc/identifiable-error.ts
+++ b/packages/backend/src/misc/identifiable-error.ts
@@ -7,7 +7,7 @@ export class IdentifiableError extends Error {
 
 	constructor(id: string, message?: string) {
 		super(message);
-		this.message = message || '';
+		this.message = message || "";
 		this.id = id;
 	}
 }
diff --git a/packages/backend/src/misc/is-duplicate-key-value-error.ts b/packages/backend/src/misc/is-duplicate-key-value-error.ts
index 04ff191e4..a89023cc1 100644
--- a/packages/backend/src/misc/is-duplicate-key-value-error.ts
+++ b/packages/backend/src/misc/is-duplicate-key-value-error.ts
@@ -1,3 +1,5 @@
 export function isDuplicateKeyValueError(e: unknown | Error): boolean {
-	return (e as any).message && (e as Error).message.startsWith('duplicate key value');
+	return (
+		(e as Error).message?.startsWith("duplicate key value")
+	);
 }
diff --git a/packages/backend/src/misc/is-instance-muted.ts b/packages/backend/src/misc/is-instance-muted.ts
index a74ba524e..1547d4555 100644
--- a/packages/backend/src/misc/is-instance-muted.ts
+++ b/packages/backend/src/misc/is-instance-muted.ts
@@ -1,15 +1,21 @@
-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;
+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;
 
 	return false;
 }
 
-export function isUserFromMutedInstance(notif: Packed<'Notification'>, mutedInstances: Set): boolean {
-	if (mutedInstances.has(notif?.user?.host ?? '')) return true;
+export function isUserFromMutedInstance(
+	notif: Packed<"Notification">,
+	mutedInstances: Set,
+): boolean {
+	if (mutedInstances.has(notif?.user?.host ?? "")) return true;
 
 	return false;
 }
diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts
index 9d6e28f15..a8ba62ec2 100644
--- a/packages/backend/src/misc/is-mime-image.ts
+++ b/packages/backend/src/misc/is-mime-image.ts
@@ -1,8 +1,20 @@
-import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 
 const dictionary = {
-	'safe-file': FILE_TYPE_BROWSERSAFE,
-	'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml', 'image/avif'],
+	"safe-file": FILE_TYPE_BROWSERSAFE,
+	"sharp-convertible-image": [
+		"image/jpeg",
+		"image/png",
+		"image/gif",
+		"image/apng",
+		"image/vnd.mozilla.apng",
+		"image/webp",
+		"image/svg+xml",
+		"image/avif",
+	],
 };
 
-export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime);
+export const isMimeImage = (
+	mime: string,
+	type: keyof typeof dictionary,
+): boolean => dictionary[type].includes(mime);
diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts
index 779f548b0..fe83a56a5 100644
--- a/packages/backend/src/misc/is-quote.ts
+++ b/packages/backend/src/misc/is-quote.ts
@@ -1,5 +1,10 @@
-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));
+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/is-user-related.ts b/packages/backend/src/misc/is-user-related.ts
index dc7bfbf0a..62cbed7e2 100644
--- a/packages/backend/src/misc/is-user-related.ts
+++ b/packages/backend/src/misc/is-user-related.ts
@@ -1,7 +1,8 @@
 export function isUserRelated(note: any, ids: Set): boolean {
 	if (ids.has(note.userId)) return true; // note author is muted
-	if (note.mentions && note.mentions.some((user: string) => ids.has(user))) return true; // any of mentioned users are muted
-	if (note.reply && isUserRelated(note.reply, ids))  return true; // also check reply target
+	if (note.mentions?.some((user: string) => ids.has(user)))
+		return true; // any of mentioned users are muted
+	if (note.reply && isUserRelated(note.reply, ids)) return true; // also check reply target
 	if (note.renote && isUserRelated(note.renote, ids)) return true; // also check renote target
 	return false;
 }
diff --git a/packages/backend/src/misc/keypair-store.ts b/packages/backend/src/misc/keypair-store.ts
index 1183b9a78..4551bfd98 100644
--- a/packages/backend/src/misc/keypair-store.ts
+++ b/packages/backend/src/misc/keypair-store.ts
@@ -1,10 +1,12 @@
-import { UserKeypairs } from '@/models/index.js';
-import { User } from '@/models/entities/user.js';
-import { UserKeypair } from '@/models/entities/user-keypair.js';
-import { Cache } from './cache.js';
+import { UserKeypairs } from "@/models/index.js";
+import type { User } from "@/models/entities/user.js";
+import type { UserKeypair } from "@/models/entities/user-keypair.js";
+import { Cache } from "./cache.js";
 
 const cache = new Cache(Infinity);
 
-export async function getUserKeypair(userId: User['id']): Promise {
-	return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId }));
+export async function getUserKeypair(userId: User["id"]): Promise {
+	return await cache.fetch(userId, () =>
+		UserKeypairs.findOneByOrFail({ userId: userId }),
+	);
 }
diff --git a/packages/backend/src/misc/langmap.ts b/packages/backend/src/misc/langmap.ts
index 5ee85e6c0..106130d3c 100644
--- a/packages/backend/src/misc/langmap.ts
+++ b/packages/backend/src/misc/langmap.ts
@@ -1,666 +1,666 @@
 // TODO: sharedに置いてフロントエンドのと統合したい
 export const langmap = {
-	'ach': {
-		nativeName: 'Lwo',
+	ach: {
+		nativeName: "Lwo",
 	},
-	'ady': {
-		nativeName: 'Адыгэбзэ',
+	ady: {
+		nativeName: "Адыгэбзэ",
 	},
-	'af': {
-		nativeName: 'Afrikaans',
+	af: {
+		nativeName: "Afrikaans",
 	},
-	'af-NA': {
-		nativeName: 'Afrikaans (Namibia)',
+	"af-NA": {
+		nativeName: "Afrikaans (Namibia)",
 	},
-	'af-ZA': {
-		nativeName: 'Afrikaans (South Africa)',
+	"af-ZA": {
+		nativeName: "Afrikaans (South Africa)",
 	},
-	'ak': {
-		nativeName: 'Tɕɥi',
+	ak: {
+		nativeName: "Tɕɥi",
 	},
-	'ar': {
-		nativeName: 'العربية',
+	ar: {
+		nativeName: "العربية",
 	},
-	'ar-AR': {
-		nativeName: 'العربية',
+	"ar-AR": {
+		nativeName: "العربية",
 	},
-	'ar-MA': {
-		nativeName: 'العربية',
+	"ar-MA": {
+		nativeName: "العربية",
 	},
-	'ar-SA': {
-		nativeName: 'العربية (السعودية)',
+	"ar-SA": {
+		nativeName: "العربية (السعودية)",
 	},
-	'ay-BO': {
-		nativeName: 'Aymar aru',
+	"ay-BO": {
+		nativeName: "Aymar aru",
 	},
-	'az': {
-		nativeName: 'Azərbaycan dili',
+	az: {
+		nativeName: "Azərbaycan dili",
 	},
-	'az-AZ': {
-		nativeName: 'Azərbaycan dili',
+	"az-AZ": {
+		nativeName: "Azərbaycan dili",
 	},
-	'be-BY': {
-		nativeName: 'Беларуская',
+	"be-BY": {
+		nativeName: "Беларуская",
 	},
-	'bg': {
-		nativeName: 'Български',
+	bg: {
+		nativeName: "Български",
 	},
-	'bg-BG': {
-		nativeName: 'Български',
+	"bg-BG": {
+		nativeName: "Български",
 	},
-	'bn': {
-		nativeName: 'বাংলা',
+	bn: {
+		nativeName: "বাংলা",
 	},
-	'bn-IN': {
-		nativeName: 'বাংলা (ভারত)',
+	"bn-IN": {
+		nativeName: "বাংলা (ভারত)",
 	},
-	'bn-BD': {
-		nativeName: 'বাংলা(বাংলাদেশ)',
+	"bn-BD": {
+		nativeName: "বাংলা(বাংলাদেশ)",
 	},
-	'br': {
-		nativeName: 'Brezhoneg',
+	br: {
+		nativeName: "Brezhoneg",
 	},
-	'bs-BA': {
-		nativeName: 'Bosanski',
+	"bs-BA": {
+		nativeName: "Bosanski",
 	},
-	'ca': {
-		nativeName: 'Català',
+	ca: {
+		nativeName: "Català",
 	},
-	'ca-ES': {
-		nativeName: 'Català',
+	"ca-ES": {
+		nativeName: "Català",
 	},
-	'cak': {
-		nativeName: 'Maya Kaqchikel',
+	cak: {
+		nativeName: "Maya Kaqchikel",
 	},
-	'ck-US': {
-		nativeName: 'ᏣᎳᎩ (tsalagi)',
+	"ck-US": {
+		nativeName: "ᏣᎳᎩ (tsalagi)",
 	},
-	'cs': {
-		nativeName: 'Čeština',
+	cs: {
+		nativeName: "Čeština",
 	},
-	'cs-CZ': {
-		nativeName: 'Čeština',
+	"cs-CZ": {
+		nativeName: "Čeština",
 	},
-	'cy': {
-		nativeName: 'Cymraeg',
+	cy: {
+		nativeName: "Cymraeg",
 	},
-	'cy-GB': {
-		nativeName: 'Cymraeg',
+	"cy-GB": {
+		nativeName: "Cymraeg",
 	},
-	'da': {
-		nativeName: 'Dansk',
+	da: {
+		nativeName: "Dansk",
 	},
-	'da-DK': {
-		nativeName: 'Dansk',
+	"da-DK": {
+		nativeName: "Dansk",
 	},
-	'de': {
-		nativeName: 'Deutsch',
+	de: {
+		nativeName: "Deutsch",
 	},
-	'de-AT': {
-		nativeName: 'Deutsch (Österreich)',
+	"de-AT": {
+		nativeName: "Deutsch (Österreich)",
 	},
-	'de-DE': {
-		nativeName: 'Deutsch (Deutschland)',
+	"de-DE": {
+		nativeName: "Deutsch (Deutschland)",
 	},
-	'de-CH': {
-		nativeName: 'Deutsch (Schweiz)',
+	"de-CH": {
+		nativeName: "Deutsch (Schweiz)",
 	},
-	'dsb': {
-		nativeName: 'Dolnoserbšćina',
+	dsb: {
+		nativeName: "Dolnoserbšćina",
 	},
-	'el': {
-		nativeName: 'Ελληνικά',
+	el: {
+		nativeName: "Ελληνικά",
 	},
-	'el-GR': {
-		nativeName: 'Ελληνικά',
+	"el-GR": {
+		nativeName: "Ελληνικά",
 	},
-	'en': {
-		nativeName: 'English',
+	en: {
+		nativeName: "English",
 	},
-	'en-GB': {
-		nativeName: 'English (UK)',
+	"en-GB": {
+		nativeName: "English (UK)",
 	},
-	'en-AU': {
-		nativeName: 'English (Australia)',
+	"en-AU": {
+		nativeName: "English (Australia)",
 	},
-	'en-CA': {
-		nativeName: 'English (Canada)',
+	"en-CA": {
+		nativeName: "English (Canada)",
 	},
-	'en-IE': {
-		nativeName: 'English (Ireland)',
+	"en-IE": {
+		nativeName: "English (Ireland)",
 	},
-	'en-IN': {
-		nativeName: 'English (India)',
+	"en-IN": {
+		nativeName: "English (India)",
 	},
-	'en-PI': {
-		nativeName: 'English (Pirate)',
+	"en-PI": {
+		nativeName: "English (Pirate)",
 	},
-	'en-SG': {
-		nativeName: 'English (Singapore)',
+	"en-SG": {
+		nativeName: "English (Singapore)",
 	},
-	'en-UD': {
-		nativeName: 'English (Upside Down)',
+	"en-UD": {
+		nativeName: "English (Upside Down)",
 	},
-	'en-US': {
-		nativeName: 'English (US)',
+	"en-US": {
+		nativeName: "English (US)",
 	},
-	'en-ZA': {
-		nativeName: 'English (South Africa)',
+	"en-ZA": {
+		nativeName: "English (South Africa)",
 	},
-	'en@pirate': {
-		nativeName: 'English (Pirate)',
+	"en@pirate": {
+		nativeName: "English (Pirate)",
 	},
-	'eo': {
-		nativeName: 'Esperanto',
+	eo: {
+		nativeName: "Esperanto",
 	},
-	'eo-EO': {
-		nativeName: 'Esperanto',
+	"eo-EO": {
+		nativeName: "Esperanto",
 	},
-	'es': {
-		nativeName: 'Español',
+	es: {
+		nativeName: "Español",
 	},
-	'es-AR': {
-		nativeName: 'Español (Argentine)',
+	"es-AR": {
+		nativeName: "Español (Argentine)",
 	},
-	'es-419': {
-		nativeName: 'Español (Latinoamérica)',
+	"es-419": {
+		nativeName: "Español (Latinoamérica)",
 	},
-	'es-CL': {
-		nativeName: 'Español (Chile)',
+	"es-CL": {
+		nativeName: "Español (Chile)",
 	},
-	'es-CO': {
-		nativeName: 'Español (Colombia)',
+	"es-CO": {
+		nativeName: "Español (Colombia)",
 	},
-	'es-EC': {
-		nativeName: 'Español (Ecuador)',
+	"es-EC": {
+		nativeName: "Español (Ecuador)",
 	},
-	'es-ES': {
-		nativeName: 'Español (España)',
+	"es-ES": {
+		nativeName: "Español (España)",
 	},
-	'es-LA': {
-		nativeName: 'Español (Latinoamérica)',
+	"es-LA": {
+		nativeName: "Español (Latinoamérica)",
 	},
-	'es-NI': {
-		nativeName: 'Español (Nicaragua)',
+	"es-NI": {
+		nativeName: "Español (Nicaragua)",
 	},
-	'es-MX': {
-		nativeName: 'Español (México)',
+	"es-MX": {
+		nativeName: "Español (México)",
 	},
-	'es-US': {
-		nativeName: 'Español (Estados Unidos)',
+	"es-US": {
+		nativeName: "Español (Estados Unidos)",
 	},
-	'es-VE': {
-		nativeName: 'Español (Venezuela)',
+	"es-VE": {
+		nativeName: "Español (Venezuela)",
 	},
-	'et': {
-		nativeName: 'eesti keel',
+	et: {
+		nativeName: "eesti keel",
 	},
-	'et-EE': {
-		nativeName: 'Eesti (Estonia)',
+	"et-EE": {
+		nativeName: "Eesti (Estonia)",
 	},
-	'eu': {
-		nativeName: 'Euskara',
+	eu: {
+		nativeName: "Euskara",
 	},
-	'eu-ES': {
-		nativeName: 'Euskara',
+	"eu-ES": {
+		nativeName: "Euskara",
 	},
-	'fa': {
-		nativeName: 'فارسی',
+	fa: {
+		nativeName: "فارسی",
 	},
-	'fa-IR': {
-		nativeName: 'فارسی',
+	"fa-IR": {
+		nativeName: "فارسی",
 	},
-	'fb-LT': {
-		nativeName: 'Leet Speak',
+	"fb-LT": {
+		nativeName: "Leet Speak",
 	},
-	'ff': {
-		nativeName: 'Fulah',
+	ff: {
+		nativeName: "Fulah",
 	},
-	'fi': {
-		nativeName: 'Suomi',
+	fi: {
+		nativeName: "Suomi",
 	},
-	'fi-FI': {
-		nativeName: 'Suomi',
+	"fi-FI": {
+		nativeName: "Suomi",
 	},
-	'fo': {
-		nativeName: 'Føroyskt',
+	fo: {
+		nativeName: "Føroyskt",
 	},
-	'fo-FO': {
-		nativeName: 'Føroyskt (Færeyjar)',
+	"fo-FO": {
+		nativeName: "Føroyskt (Færeyjar)",
 	},
-	'fr': {
-		nativeName: 'Français',
+	fr: {
+		nativeName: "Français",
 	},
-	'fr-CA': {
-		nativeName: 'Français (Canada)',
+	"fr-CA": {
+		nativeName: "Français (Canada)",
 	},
-	'fr-FR': {
-		nativeName: 'Français (France)',
+	"fr-FR": {
+		nativeName: "Français (France)",
 	},
-	'fr-BE': {
-		nativeName: 'Français (Belgique)',
+	"fr-BE": {
+		nativeName: "Français (Belgique)",
 	},
-	'fr-CH': {
-		nativeName: 'Français (Suisse)',
+	"fr-CH": {
+		nativeName: "Français (Suisse)",
 	},
-	'fy-NL': {
-		nativeName: 'Frysk',
+	"fy-NL": {
+		nativeName: "Frysk",
 	},
-	'ga': {
-		nativeName: 'Gaeilge',
+	ga: {
+		nativeName: "Gaeilge",
 	},
-	'ga-IE': {
-		nativeName: 'Gaeilge',
+	"ga-IE": {
+		nativeName: "Gaeilge",
 	},
-	'gd': {
-		nativeName: 'Gàidhlig',
+	gd: {
+		nativeName: "Gàidhlig",
 	},
-	'gl': {
-		nativeName: 'Galego',
+	gl: {
+		nativeName: "Galego",
 	},
-	'gl-ES': {
-		nativeName: 'Galego',
+	"gl-ES": {
+		nativeName: "Galego",
 	},
-	'gn-PY': {
-		nativeName: 'Avañe\'ẽ',
+	"gn-PY": {
+		nativeName: "Avañe'ẽ",
 	},
-	'gu-IN': {
-		nativeName: 'ગુજરાતી',
+	"gu-IN": {
+		nativeName: "ગુજરાતી",
 	},
-	'gv': {
-		nativeName: 'Gaelg',
+	gv: {
+		nativeName: "Gaelg",
 	},
-	'gx-GR': {
-		nativeName: 'Ἑλληνική ἀρχαία',
+	"gx-GR": {
+		nativeName: "Ἑλληνική ἀρχαία",
 	},
-	'he': {
-		nativeName: 'עברית‏',
+	he: {
+		nativeName: "עברית‏",
 	},
-	'he-IL': {
-		nativeName: 'עברית‏',
+	"he-IL": {
+		nativeName: "עברית‏",
 	},
-	'hi': {
-		nativeName: 'हिन्दी',
+	hi: {
+		nativeName: "हिन्दी",
 	},
-	'hi-IN': {
-		nativeName: 'हिन्दी',
+	"hi-IN": {
+		nativeName: "हिन्दी",
 	},
-	'hr': {
-		nativeName: 'Hrvatski',
+	hr: {
+		nativeName: "Hrvatski",
 	},
-	'hr-HR': {
-		nativeName: 'Hrvatski',
+	"hr-HR": {
+		nativeName: "Hrvatski",
 	},
-	'hsb': {
-		nativeName: 'Hornjoserbšćina',
+	hsb: {
+		nativeName: "Hornjoserbšćina",
 	},
-	'ht': {
-		nativeName: 'Kreyòl',
+	ht: {
+		nativeName: "Kreyòl",
 	},
-	'hu': {
-		nativeName: 'Magyar',
+	hu: {
+		nativeName: "Magyar",
 	},
-	'hu-HU': {
-		nativeName: 'Magyar',
+	"hu-HU": {
+		nativeName: "Magyar",
 	},
-	'hy': {
-		nativeName: 'Հայերեն',
+	hy: {
+		nativeName: "Հայերեն",
 	},
-	'hy-AM': {
-		nativeName: 'Հայերեն (Հայաստան)',
+	"hy-AM": {
+		nativeName: "Հայերեն (Հայաստան)",
 	},
-	'id': {
-		nativeName: 'Bahasa Indonesia',
+	id: {
+		nativeName: "Bahasa Indonesia",
 	},
-	'id-ID': {
-		nativeName: 'Bahasa Indonesia',
+	"id-ID": {
+		nativeName: "Bahasa Indonesia",
 	},
-	'is': {
-		nativeName: 'Íslenska',
+	is: {
+		nativeName: "Íslenska",
 	},
-	'is-IS': {
-		nativeName: 'Íslenska (Iceland)',
+	"is-IS": {
+		nativeName: "Íslenska (Iceland)",
 	},
-	'it': {
-		nativeName: 'Italiano',
+	it: {
+		nativeName: "Italiano",
 	},
-	'it-IT': {
-		nativeName: 'Italiano',
+	"it-IT": {
+		nativeName: "Italiano",
 	},
-	'ja': {
-		nativeName: '日本語',
+	ja: {
+		nativeName: "日本語",
 	},
-	'ja-JP': {
-		nativeName: '日本語 (日本)',
+	"ja-JP": {
+		nativeName: "日本語 (日本)",
 	},
-	'jv-ID': {
-		nativeName: 'Basa Jawa',
+	"jv-ID": {
+		nativeName: "Basa Jawa",
 	},
-	'ka-GE': {
-		nativeName: 'ქართული',
+	"ka-GE": {
+		nativeName: "ქართული",
 	},
-	'kk-KZ': {
-		nativeName: 'Қазақша',
+	"kk-KZ": {
+		nativeName: "Қазақша",
 	},
-	'km': {
-		nativeName: 'ភាសាខ្មែរ',
+	km: {
+		nativeName: "ភាសាខ្មែរ",
 	},
-	'kl': {
-		nativeName: 'kalaallisut',
+	kl: {
+		nativeName: "kalaallisut",
 	},
-	'km-KH': {
-		nativeName: 'ភាសាខ្មែរ',
+	"km-KH": {
+		nativeName: "ភាសាខ្មែរ",
 	},
-	'kab': {
-		nativeName: 'Taqbaylit',
+	kab: {
+		nativeName: "Taqbaylit",
 	},
-	'kn': {
-		nativeName: 'ಕನ್ನಡ',
+	kn: {
+		nativeName: "ಕನ್ನಡ",
 	},
-	'kn-IN': {
-		nativeName: 'ಕನ್ನಡ (India)',
+	"kn-IN": {
+		nativeName: "ಕನ್ನಡ (India)",
 	},
-	'ko': {
-		nativeName: '한국어',
+	ko: {
+		nativeName: "한국어",
 	},
-	'ko-KR': {
-		nativeName: '한국어 (한국)',
+	"ko-KR": {
+		nativeName: "한국어 (한국)",
 	},
-	'ku-TR': {
-		nativeName: 'Kurdî',
+	"ku-TR": {
+		nativeName: "Kurdî",
 	},
-	'kw': {
-		nativeName: 'Kernewek',
+	kw: {
+		nativeName: "Kernewek",
 	},
-	'la': {
-		nativeName: 'Latin',
+	la: {
+		nativeName: "Latin",
 	},
-	'la-VA': {
-		nativeName: 'Latin',
+	"la-VA": {
+		nativeName: "Latin",
 	},
-	'lb': {
-		nativeName: 'Lëtzebuergesch',
+	lb: {
+		nativeName: "Lëtzebuergesch",
 	},
-	'li-NL': {
-		nativeName: 'Lèmbörgs',
+	"li-NL": {
+		nativeName: "Lèmbörgs",
 	},
-	'lt': {
-		nativeName: 'Lietuvių',
+	lt: {
+		nativeName: "Lietuvių",
 	},
-	'lt-LT': {
-		nativeName: 'Lietuvių',
+	"lt-LT": {
+		nativeName: "Lietuvių",
 	},
-	'lv': {
-		nativeName: 'Latviešu',
+	lv: {
+		nativeName: "Latviešu",
 	},
-	'lv-LV': {
-		nativeName: 'Latviešu',
+	"lv-LV": {
+		nativeName: "Latviešu",
 	},
-	'mai': {
-		nativeName: 'मैथिली, মৈথিলী',
+	mai: {
+		nativeName: "मैथिली, মৈথিলী",
 	},
-	'mg-MG': {
-		nativeName: 'Malagasy',
+	"mg-MG": {
+		nativeName: "Malagasy",
 	},
-	'mk': {
-		nativeName: 'Македонски',
+	mk: {
+		nativeName: "Македонски",
 	},
-	'mk-MK': {
-		nativeName: 'Македонски (Македонски)',
+	"mk-MK": {
+		nativeName: "Македонски (Македонски)",
 	},
-	'ml': {
-		nativeName: 'മലയാളം',
+	ml: {
+		nativeName: "മലയാളം",
 	},
-	'ml-IN': {
-		nativeName: 'മലയാളം',
+	"ml-IN": {
+		nativeName: "മലയാളം",
 	},
-	'mn-MN': {
-		nativeName: 'Монгол',
+	"mn-MN": {
+		nativeName: "Монгол",
 	},
-	'mr': {
-		nativeName: 'मराठी',
+	mr: {
+		nativeName: "मराठी",
 	},
-	'mr-IN': {
-		nativeName: 'मराठी',
+	"mr-IN": {
+		nativeName: "मराठी",
 	},
-	'ms': {
-		nativeName: 'Bahasa Melayu',
+	ms: {
+		nativeName: "Bahasa Melayu",
 	},
-	'ms-MY': {
-		nativeName: 'Bahasa Melayu',
+	"ms-MY": {
+		nativeName: "Bahasa Melayu",
 	},
-	'mt': {
-		nativeName: 'Malti',
+	mt: {
+		nativeName: "Malti",
 	},
-	'mt-MT': {
-		nativeName: 'Malti',
+	"mt-MT": {
+		nativeName: "Malti",
 	},
-	'my': {
-		nativeName: 'ဗမာစကာ',
+	my: {
+		nativeName: "ဗမာစကာ",
 	},
-	'no': {
-		nativeName: 'Norsk',
+	no: {
+		nativeName: "Norsk",
 	},
-	'nb': {
-		nativeName: 'Norsk (bokmål)',
+	nb: {
+		nativeName: "Norsk (bokmål)",
 	},
-	'nb-NO': {
-		nativeName: 'Norsk (bokmål)',
+	"nb-NO": {
+		nativeName: "Norsk (bokmål)",
 	},
-	'ne': {
-		nativeName: 'नेपाली',
+	ne: {
+		nativeName: "नेपाली",
 	},
-	'ne-NP': {
-		nativeName: 'नेपाली',
+	"ne-NP": {
+		nativeName: "नेपाली",
 	},
-	'nl': {
-		nativeName: 'Nederlands',
+	nl: {
+		nativeName: "Nederlands",
 	},
-	'nl-BE': {
-		nativeName: 'Nederlands (België)',
+	"nl-BE": {
+		nativeName: "Nederlands (België)",
 	},
-	'nl-NL': {
-		nativeName: 'Nederlands (Nederland)',
+	"nl-NL": {
+		nativeName: "Nederlands (Nederland)",
 	},
-	'nn-NO': {
-		nativeName: 'Norsk (nynorsk)',
+	"nn-NO": {
+		nativeName: "Norsk (nynorsk)",
 	},
-	'oc': {
-		nativeName: 'Occitan',
+	oc: {
+		nativeName: "Occitan",
 	},
-	'or-IN': {
-		nativeName: 'ଓଡ଼ିଆ',
+	"or-IN": {
+		nativeName: "ଓଡ଼ିଆ",
 	},
-	'pa': {
-		nativeName: 'ਪੰਜਾਬੀ',
+	pa: {
+		nativeName: "ਪੰਜਾਬੀ",
 	},
-	'pa-IN': {
-		nativeName: 'ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)',
+	"pa-IN": {
+		nativeName: "ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)",
 	},
-	'pl': {
-		nativeName: 'Polski',
+	pl: {
+		nativeName: "Polski",
 	},
-	'pl-PL': {
-		nativeName: 'Polski',
+	"pl-PL": {
+		nativeName: "Polski",
 	},
-	'ps-AF': {
-		nativeName: 'پښتو',
+	"ps-AF": {
+		nativeName: "پښتو",
 	},
-	'pt': {
-		nativeName: 'Português',
+	pt: {
+		nativeName: "Português",
 	},
-	'pt-BR': {
-		nativeName: 'Português (Brasil)',
+	"pt-BR": {
+		nativeName: "Português (Brasil)",
 	},
-	'pt-PT': {
-		nativeName: 'Português (Portugal)',
+	"pt-PT": {
+		nativeName: "Português (Portugal)",
 	},
-	'qu-PE': {
-		nativeName: 'Qhichwa',
+	"qu-PE": {
+		nativeName: "Qhichwa",
 	},
-	'rm-CH': {
-		nativeName: 'Rumantsch',
+	"rm-CH": {
+		nativeName: "Rumantsch",
 	},
-	'ro': {
-		nativeName: 'Română',
+	ro: {
+		nativeName: "Română",
 	},
-	'ro-RO': {
-		nativeName: 'Română',
+	"ro-RO": {
+		nativeName: "Română",
 	},
-	'ru': {
-		nativeName: 'Русский',
+	ru: {
+		nativeName: "Русский",
 	},
-	'ru-RU': {
-		nativeName: 'Русский',
+	"ru-RU": {
+		nativeName: "Русский",
 	},
-	'sa-IN': {
-		nativeName: 'संस्कृतम्',
+	"sa-IN": {
+		nativeName: "संस्कृतम्",
 	},
-	'se-NO': {
-		nativeName: 'Davvisámegiella',
+	"se-NO": {
+		nativeName: "Davvisámegiella",
 	},
-	'sh': {
-		nativeName: 'српскохрватски',
+	sh: {
+		nativeName: "српскохрватски",
 	},
-	'si-LK': {
-		nativeName: 'සිංහල',
+	"si-LK": {
+		nativeName: "සිංහල",
 	},
-	'sk': {
-		nativeName: 'Slovenčina',
+	sk: {
+		nativeName: "Slovenčina",
 	},
-	'sk-SK': {
-		nativeName: 'Slovenčina (Slovakia)',
+	"sk-SK": {
+		nativeName: "Slovenčina (Slovakia)",
 	},
-	'sl': {
-		nativeName: 'Slovenščina',
+	sl: {
+		nativeName: "Slovenščina",
 	},
-	'sl-SI': {
-		nativeName: 'Slovenščina',
+	"sl-SI": {
+		nativeName: "Slovenščina",
 	},
-	'so-SO': {
-		nativeName: 'Soomaaliga',
+	"so-SO": {
+		nativeName: "Soomaaliga",
 	},
-	'sq': {
-		nativeName: 'Shqip',
+	sq: {
+		nativeName: "Shqip",
 	},
-	'sq-AL': {
-		nativeName: 'Shqip',
+	"sq-AL": {
+		nativeName: "Shqip",
 	},
-	'sr': {
-		nativeName: 'Српски',
+	sr: {
+		nativeName: "Српски",
 	},
-	'sr-RS': {
-		nativeName: 'Српски (Serbia)',
+	"sr-RS": {
+		nativeName: "Српски (Serbia)",
 	},
-	'su': {
-		nativeName: 'Basa Sunda',
+	su: {
+		nativeName: "Basa Sunda",
 	},
-	'sv': {
-		nativeName: 'Svenska',
+	sv: {
+		nativeName: "Svenska",
 	},
-	'sv-SE': {
-		nativeName: 'Svenska',
+	"sv-SE": {
+		nativeName: "Svenska",
 	},
-	'sw': {
-		nativeName: 'Kiswahili',
+	sw: {
+		nativeName: "Kiswahili",
 	},
-	'sw-KE': {
-		nativeName: 'Kiswahili',
+	"sw-KE": {
+		nativeName: "Kiswahili",
 	},
-	'ta': {
-		nativeName: 'தமிழ்',
+	ta: {
+		nativeName: "தமிழ்",
 	},
-	'ta-IN': {
-		nativeName: 'தமிழ்',
+	"ta-IN": {
+		nativeName: "தமிழ்",
 	},
-	'te': {
-		nativeName: 'తెలుగు',
+	te: {
+		nativeName: "తెలుగు",
 	},
-	'te-IN': {
-		nativeName: 'తెలుగు',
+	"te-IN": {
+		nativeName: "తెలుగు",
 	},
-	'tg': {
-		nativeName: 'забо́ни тоҷикӣ́',
+	tg: {
+		nativeName: "забо́ни тоҷикӣ́",
 	},
-	'tg-TJ': {
-		nativeName: 'тоҷикӣ',
+	"tg-TJ": {
+		nativeName: "тоҷикӣ",
 	},
-	'th': {
-		nativeName: 'ภาษาไทย',
+	th: {
+		nativeName: "ภาษาไทย",
 	},
-	'th-TH': {
-		nativeName: 'ภาษาไทย (ประเทศไทย)',
+	"th-TH": {
+		nativeName: "ภาษาไทย (ประเทศไทย)",
 	},
-	'fil': {
-		nativeName: 'Filipino',
+	fil: {
+		nativeName: "Filipino",
 	},
-	'tlh': {
-		nativeName: 'tlhIngan-Hol',
+	tlh: {
+		nativeName: "tlhIngan-Hol",
 	},
-	'tr': {
-		nativeName: 'Türkçe',
+	tr: {
+		nativeName: "Türkçe",
 	},
-	'tr-TR': {
-		nativeName: 'Türkçe',
+	"tr-TR": {
+		nativeName: "Türkçe",
 	},
-	'tt-RU': {
-		nativeName: 'татарча',
+	"tt-RU": {
+		nativeName: "татарча",
 	},
-	'uk': {
-		nativeName: 'Українська',
+	uk: {
+		nativeName: "Українська",
 	},
-	'uk-UA': {
-		nativeName: 'Українська',
+	"uk-UA": {
+		nativeName: "Українська",
 	},
-	'ur': {
-		nativeName: 'اردو',
+	ur: {
+		nativeName: "اردو",
 	},
-	'ur-PK': {
-		nativeName: 'اردو',
+	"ur-PK": {
+		nativeName: "اردو",
 	},
-	'uz': {
-		nativeName: 'O\'zbek',
+	uz: {
+		nativeName: "O'zbek",
 	},
-	'uz-UZ': {
-		nativeName: 'O\'zbek',
+	"uz-UZ": {
+		nativeName: "O'zbek",
 	},
-	'vi': {
-		nativeName: 'Tiếng Việt',
+	vi: {
+		nativeName: "Tiếng Việt",
 	},
-	'vi-VN': {
-		nativeName: 'Tiếng Việt',
+	"vi-VN": {
+		nativeName: "Tiếng Việt",
 	},
-	'xh-ZA': {
-		nativeName: 'isiXhosa',
+	"xh-ZA": {
+		nativeName: "isiXhosa",
 	},
-	'yi': {
-		nativeName: 'ייִדיש',
+	yi: {
+		nativeName: "ייִדיש",
 	},
-	'yi-DE': {
-		nativeName: 'ייִדיש (German)',
+	"yi-DE": {
+		nativeName: "ייִדיש (German)",
 	},
-	'zh': {
-		nativeName: '中文',
+	zh: {
+		nativeName: "中文",
 	},
-	'zh-Hans': {
-		nativeName: '中文简体',
+	"zh-Hans": {
+		nativeName: "中文简体",
 	},
-	'zh-Hant': {
-		nativeName: '中文繁體',
+	"zh-Hant": {
+		nativeName: "中文繁體",
 	},
-	'zh-CN': {
-		nativeName: '中文(中国大陆)',
+	"zh-CN": {
+		nativeName: "中文(中国大陆)",
 	},
-	'zh-HK': {
-		nativeName: '中文(香港)',
+	"zh-HK": {
+		nativeName: "中文(香港)",
 	},
-	'zh-SG': {
-		nativeName: '中文(新加坡)',
+	"zh-SG": {
+		nativeName: "中文(新加坡)",
 	},
-	'zh-TW': {
-		nativeName: '中文(台灣)',
+	"zh-TW": {
+		nativeName: "中文(台灣)",
 	},
-	'zu-ZA': {
-		nativeName: 'isiZulu',
+	"zu-ZA": {
+		nativeName: "isiZulu",
 	},
 };
diff --git a/packages/backend/src/misc/normalize-for-search.ts b/packages/backend/src/misc/normalize-for-search.ts
index 200540566..6882a1243 100644
--- a/packages/backend/src/misc/normalize-for-search.ts
+++ b/packages/backend/src/misc/normalize-for-search.ts
@@ -2,5 +2,5 @@ export function normalizeForSearch(tag: string): string {
 	// ref.
 	// - https://analytics-note.xyz/programming/unicode-normalization-forms/
 	// - https://maku77.github.io/js/string/normalize.html
-	return tag.normalize('NFKC').toLowerCase();
+	return tag.normalize("NFKC").toLowerCase();
 }
diff --git a/packages/backend/src/misc/nyaize.ts b/packages/backend/src/misc/nyaize.ts
index 500d1db2c..7ec26c1eb 100644
--- a/packages/backend/src/misc/nyaize.ts
+++ b/packages/backend/src/misc/nyaize.ts
@@ -1,15 +1,21 @@
 export function nyaize(text: string): string {
-	return text
-		// ja-JP
-		.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
-		// en-US
-		.replace(/(?<=n)a/gi, x => x === 'A' ? 'YA' : 'ya')
-		.replace(/(?<=morn)ing/gi, x => x === 'ING' ? 'YAN' : 'yan')
-		.replace(/(?<=every)one/gi, x => x === 'ONE' ? 'NYAN' : 'nyan')
-		// ko-KR
-		.replace(/[나-낳]/g, match => String.fromCharCode(
-			match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0)
-		))
-		.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
-		.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');
+	return (
+		text
+			// ja-JP
+			.replace(/な/g, "にゃ")
+			.replace(/ナ/g, "ニャ")
+			.replace(/ナ/g, "ニャ")
+			// en-US
+			.replace(/(?<=n)a/gi, (x) => (x === "A" ? "YA" : "ya"))
+			.replace(/(?<=morn)ing/gi, (x) => (x === "ING" ? "YAN" : "yan"))
+			.replace(/(?<=every)one/gi, (x) => (x === "ONE" ? "NYAN" : "nyan"))
+			// ko-KR
+			.replace(/[나-낳]/g, (match) =>
+				String.fromCharCode(
+					match.charCodeAt(0)! + "냐".charCodeAt(0) - "나".charCodeAt(0),
+				),
+			)
+			.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
+			.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
+	);
 }
diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts
index 6a185d09f..3f20f9f10 100644
--- a/packages/backend/src/misc/populate-emojis.ts
+++ b/packages/backend/src/misc/populate-emojis.ts
@@ -1,12 +1,12 @@
-import { In, IsNull } from 'typeorm';
-import { Emojis } from '@/models/index.js';
-import { Emoji } from '@/models/entities/emoji.js';
-import { Note } from '@/models/entities/note.js';
-import { Cache } from './cache.js';
-import { isSelfHost, toPunyNullable } from './convert-host.js';
-import { decodeReaction } from './reaction-lib.js';
-import config from '@/config/index.js';
-import { query } from '@/prelude/url.js';
+import { In, IsNull } from "typeorm";
+import { Emojis } from "@/models/index.js";
+import type { Emoji } from "@/models/entities/emoji.js";
+import type { Note } from "@/models/entities/note.js";
+import { Cache } from "./cache.js";
+import { isSelfHost, toPunyNullable } from "./convert-host.js";
+import { decodeReaction } from "./reaction-lib.js";
+import config from "@/config/index.js";
+import { query } from "@/prelude/url.js";
 
 const cache = new Cache(1000 * 60 * 60 * 12);
 
@@ -18,12 +18,19 @@ type PopulatedEmoji = {
 	url: string;
 };
 
-function normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
+function normalizeHost(
+	src: string | undefined,
+	noteUserHost: string | null,
+): string | null {
 	// クエリに使うホスト
-	let host = src === '.' ? null	// .はローカルホスト (ここがマッチするのはリアクションのみ)
-		: src === undefined ? noteUserHost	// ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
-		: isSelfHost(src) ? null	// 自ホスト指定
-		: (src || noteUserHost);	// 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
+	let host =
+		src === "."
+			? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
+			: src === undefined
+			? noteUserHost // ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
+			: isSelfHost(src)
+			? null // 自ホスト指定
+			: src || noteUserHost; // 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
 
 	host = toPunyNullable(host);
 
@@ -48,14 +55,18 @@ function parseEmojiStr(emojiName: string, noteUserHost: string | null) {
  * @param noteUserHost ノートやユーザープロフィールの所有者のホスト
  * @returns 絵文字情報, nullは未マッチを意味する
  */
-export async function populateEmoji(emojiName: string, noteUserHost: string | null): Promise {
+export async function populateEmoji(
+	emojiName: string,
+	noteUserHost: string | null,
+): Promise {
 	const { name, host } = parseEmojiStr(emojiName, noteUserHost);
 	if (name == null) return null;
 
-	const queryOrNull = async () => (await Emojis.findOneBy({
-		name,
-		host: host ?? IsNull(),
-	})) || null;
+	const queryOrNull = async () =>
+		(await Emojis.findOneBy({
+			name,
+			host: host ?? IsNull(),
+		})) || null;
 
 	const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
 
@@ -63,7 +74,11 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
 
 	const isLocal = emoji.host == null;
 	const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため
-	const url = isLocal ? emojiUrl : `${config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`;
+	const url = isLocal
+		? emojiUrl
+		: `${config.url}/proxy/${encodeURIComponent(
+				new URL(emojiUrl).pathname,
+		  )}?${query({ url: emojiUrl })}`;
 
 	return {
 		name: emojiName,
@@ -74,51 +89,76 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
 /**
  * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される)
  */
-export async function populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise {
-	const emojis = await Promise.all(emojiNames.map(x => populateEmoji(x, noteUserHost)));
+export async function populateEmojis(
+	emojiNames: string[],
+	noteUserHost: string | null,
+): Promise {
+	const emojis = await Promise.all(
+		emojiNames.map((x) => populateEmoji(x, noteUserHost)),
+	);
 	return emojis.filter((x): x is PopulatedEmoji => x != null);
 }
 
 export function aggregateNoteEmojis(notes: Note[]) {
-	let emojis: { name: string | null; host: string | null; }[] = [];
+	let emojis: { name: string | null; host: string | null }[] = [];
 	for (const note of notes) {
-		emojis = emojis.concat(note.emojis
-			.map(e => parseEmojiStr(e, note.userHost)));
+		emojis = emojis.concat(
+			note.emojis.map((e) => parseEmojiStr(e, note.userHost)),
+		);
 		if (note.renote) {
-			emojis = emojis.concat(note.renote.emojis
-				.map(e => parseEmojiStr(e, note.renote!.userHost)));
+			emojis = emojis.concat(
+				note.renote.emojis.map((e) => parseEmojiStr(e, note.renote!.userHost)),
+			);
 			if (note.renote.user) {
-				emojis = emojis.concat(note.renote.user.emojis
-					.map(e => parseEmojiStr(e, note.renote!.userHost)));
+				emojis = emojis.concat(
+					note.renote.user.emojis.map((e) =>
+						parseEmojiStr(e, note.renote!.userHost),
+					),
+				);
 			}
 		}
-		const customReactions = Object.keys(note.reactions).map(x => decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
+		const customReactions = Object.keys(note.reactions)
+			.map((x) => 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 => parseEmojiStr(e, note.userHost)));
+			emojis = emojis.concat(
+				note.user.emojis.map((e) => parseEmojiStr(e, note.userHost)),
+			);
 		}
 	}
-	return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[];
+	return emojis.filter((x) => x.name != null) as {
+		name: string;
+		host: string | null;
+	}[];
 }
 
 /**
  * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
  */
-export async function prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise {
-	const notCachedEmojis = emojis.filter(emoji => cache.get(`${emoji.name} ${emoji.host}`) == null);
+export async function prefetchEmojis(
+	emojis: { name: string; host: string | null }[],
+): Promise {
+	const notCachedEmojis = emojis.filter(
+		(emoji) => cache.get(`${emoji.name} ${emoji.host}`) == null,
+	);
 	const emojisQuery: any[] = [];
-	const hosts = new Set(notCachedEmojis.map(e => e.host));
+	const hosts = new Set(notCachedEmojis.map((e) => e.host));
 	for (const host of hosts) {
 		emojisQuery.push({
-			name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
+			name: In(
+				notCachedEmojis.filter((e) => e.host === host).map((e) => e.name),
+			),
 			host: host ?? IsNull(),
 		});
 	}
-	const _emojis = emojisQuery.length > 0 ? await Emojis.find({
-		where: emojisQuery,
-		select: ['name', 'host', 'originalUrl', 'publicUrl'],
-	}) : [];
+	const _emojis =
+		emojisQuery.length > 0
+			? await Emojis.find({
+					where: emojisQuery,
+					select: ["name", "host", "originalUrl", "publicUrl"],
+			  })
+			: [];
 	for (const emoji of _emojis) {
 		cache.set(`${emoji.name} ${emoji.host}`, emoji);
 	}
diff --git a/packages/backend/src/misc/reaction-lib.ts b/packages/backend/src/misc/reaction-lib.ts
index f24b9b293..61798333b 100644
--- a/packages/backend/src/misc/reaction-lib.ts
+++ b/packages/backend/src/misc/reaction-lib.ts
@@ -1,22 +1,22 @@
 /* eslint-disable key-spacing */
-import { emojiRegex } from './emoji-regex.js';
-import { fetchMeta } from './fetch-meta.js';
-import { Emojis } from '@/models/index.js';
-import { toPunyNullable } from './convert-host.js';
-import { IsNull } from 'typeorm';
+import { emojiRegex } from "./emoji-regex.js";
+import { fetchMeta } from "./fetch-meta.js";
+import { Emojis } from "@/models/index.js";
+import { toPunyNullable } from "./convert-host.js";
+import { IsNull } from "typeorm";
 
 const legacies: Record = {
-	'like':     '👍',
-	'love':     '❤️', // ここに記述する場合は異体字セレクタを入れない <- not that good because modern browsers just display it as the red heart so just convert it to it to not end up with two seperate reactions of "the same emoji" for the user
-	'laugh':    '😆',
-	'hmm':      '🤔',
-	'surprise': '😮',
-	'congrats': '🎉',
-	'angry':    '💢',
-	'confused': '😥',
-	'rip':      '😇',
-	'pudding':  '🍮',
-	'star':     '⭐',
+	like: "👍",
+	love: "❤️", // ここに記述する場合は異体字セレクタを入れない <- not that good because modern browsers just display it as the red heart so just convert it to it to not end up with two seperate reactions of "the same emoji" for the user
+	laugh: "😆",
+	hmm: "🤔",
+	surprise: "😮",
+	congrats: "🎉",
+	angry: "💢",
+	confused: "😥",
+	rip: "😇",
+	pudding: "🍮",
+	star: "⭐",
 };
 
 export async function getFallbackReaction(): Promise {
@@ -54,7 +54,10 @@ export function convertLegacyReactions(reactions: Record) {
 	return _reactions2;
 }
 
-export async function toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise {
+export async function toDbReaction(
+	reaction?: string | null,
+	reacterHost?: string | null,
+): Promise {
 	if (reaction == null) return await getFallbackReaction();
 
 	reacterHost = toPunyNullable(reacterHost);
@@ -111,7 +114,7 @@ export function decodeReaction(str: string): DecodedReaction {
 		const host = custom[2] || null;
 
 		return {
-			reaction: `:${name}@${host || '.'}:`,	// ローカル分は@以降を省略するのではなく.にする
+			reaction: `:${name}@${host || "."}:`, // ローカル分は@以降を省略するのではなく.にする
 			name,
 			host,
 		};
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index fdecc278d..35637e6ed 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -6,29 +6,29 @@ import {
 	packedMeDetailedSchema,
 	packedUserDetailedSchema,
 	packedUserSchema,
-} from '@/models/schema/user.js';
-import { packedNoteSchema } from '@/models/schema/note.js';
-import { packedUserListSchema } from '@/models/schema/user-list.js';
-import { packedAppSchema } from '@/models/schema/app.js';
-import { packedMessagingMessageSchema } from '@/models/schema/messaging-message.js';
-import { packedNotificationSchema } from '@/models/schema/notification.js';
-import { packedDriveFileSchema } from '@/models/schema/drive-file.js';
-import { packedDriveFolderSchema } from '@/models/schema/drive-folder.js';
-import { packedFollowingSchema } from '@/models/schema/following.js';
-import { packedMutingSchema } from '@/models/schema/muting.js';
-import { packedBlockingSchema } from '@/models/schema/blocking.js';
-import { packedNoteReactionSchema } from '@/models/schema/note-reaction.js';
-import { packedHashtagSchema } from '@/models/schema/hashtag.js';
-import { packedPageSchema } from '@/models/schema/page.js';
-import { packedUserGroupSchema } from '@/models/schema/user-group.js';
-import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite.js';
-import { packedChannelSchema } from '@/models/schema/channel.js';
-import { packedAntennaSchema } from '@/models/schema/antenna.js';
-import { packedClipSchema } from '@/models/schema/clip.js';
-import { packedFederationInstanceSchema } from '@/models/schema/federation-instance.js';
-import { packedQueueCountSchema } from '@/models/schema/queue.js';
-import { packedGalleryPostSchema } from '@/models/schema/gallery-post.js';
-import { packedEmojiSchema } from '@/models/schema/emoji.js';
+} from "@/models/schema/user.js";
+import { packedNoteSchema } from "@/models/schema/note.js";
+import { packedUserListSchema } from "@/models/schema/user-list.js";
+import { packedAppSchema } from "@/models/schema/app.js";
+import { packedMessagingMessageSchema } from "@/models/schema/messaging-message.js";
+import { packedNotificationSchema } from "@/models/schema/notification.js";
+import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
+import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
+import { packedFollowingSchema } from "@/models/schema/following.js";
+import { packedMutingSchema } from "@/models/schema/muting.js";
+import { packedBlockingSchema } from "@/models/schema/blocking.js";
+import { packedNoteReactionSchema } from "@/models/schema/note-reaction.js";
+import { packedHashtagSchema } from "@/models/schema/hashtag.js";
+import { packedPageSchema } from "@/models/schema/page.js";
+import { packedUserGroupSchema } from "@/models/schema/user-group.js";
+import { packedNoteFavoriteSchema } from "@/models/schema/note-favorite.js";
+import { packedChannelSchema } from "@/models/schema/channel.js";
+import { packedAntennaSchema } from "@/models/schema/antenna.js";
+import { packedClipSchema } from "@/models/schema/clip.js";
+import { packedFederationInstanceSchema } from "@/models/schema/federation-instance.js";
+import { packedQueueCountSchema } from "@/models/schema/queue.js";
+import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
+import { packedEmojiSchema } from "@/models/schema/emoji.js";
 
 export const refs = {
 	UserLite: packedUserLiteSchema,
@@ -65,23 +65,37 @@ export const refs = {
 
 export type Packed = SchemaType;
 
-type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
-type StringDefToType =
-	T extends 'null' ? null :
-	T extends 'boolean' ? boolean :
-	T extends 'integer' ? number :
-	T extends 'number' ? number :
-	T extends 'string' ? string | Date :
-	T extends 'array' ? ReadonlyArray :
-	T extends 'object' ? Record :
-	any;
+type TypeStringef =
+	| "null"
+	| "boolean"
+	| "integer"
+	| "number"
+	| "string"
+	| "array"
+	| "object"
+	| "any";
+type StringDefToType = T extends "null"
+	? null
+	: T extends "boolean"
+	? boolean
+	: T extends "integer"
+	? number
+	: T extends "number"
+	? number
+	: T extends "string"
+	? string | Date
+	: T extends "array"
+	? ReadonlyArray
+	: T extends "object"
+	? Record
+	: any;
 
 // https://swagger.io/specification/?sbsearch=optional#schema-object
 type OfSchema = {
 	readonly anyOf?: ReadonlyArray;
 	readonly oneOf?: ReadonlyArray;
 	readonly allOf?: ReadonlyArray;
-}
+};
 
 export interface Schema extends OfSchema {
 	readonly type?: TypeStringef;
@@ -89,13 +103,17 @@ export interface Schema extends OfSchema {
 	readonly optional?: boolean;
 	readonly items?: Schema;
 	readonly properties?: Obj;
-	readonly required?: ReadonlyArray, string>>;
+	readonly required?: ReadonlyArray<
+		Extract, string>
+	>;
 	readonly description?: string;
 	readonly example?: any;
 	readonly format?: string;
 	readonly ref?: keyof typeof refs;
 	readonly enum?: ReadonlyArray;
-	readonly default?: (this['type'] extends TypeStringef ? StringDefToType : any) | null;
+	readonly default?:
+		| (this["type"] extends TypeStringef ? StringDefToType : any)
+		| null;
 	readonly maxLength?: number;
 	readonly minLength?: number;
 	readonly maximum?: number;
@@ -104,12 +122,18 @@ export interface Schema extends OfSchema {
 }
 
 type RequiredPropertyNames = {
-	[K in keyof s]:
-		// K is not optional
-		s[K]['optional'] extends false ? K :
-		// K has default value
-		s[K]['default'] extends null | string | number | boolean | Record ? K :
-		never
+	[K in keyof s]: // K is not optional
+	s[K]["optional"] extends false
+		? K
+		: // K has default value
+		s[K]["default"] extends
+				| null
+				| string
+				| number
+				| boolean
+				| Record
+		? K
+		: never;
 }[keyof s];
 
 export type Obj = Record;
@@ -117,56 +141,80 @@ export type Obj = Record;
 // https://github.com/misskey-dev/misskey/issues/8535
 // To avoid excessive stack depth error,
 // deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
-export type ObjType =
-	UnionToIntersection<
-		{ -readonly [R in RequiredPropertyNames]-?: SchemaType } &
-		{ -readonly [R in RequiredProps]-?: SchemaType } &
-		{ -readonly [P in keyof s]?: SchemaType }
-	>;
+export type ObjType<
+	s extends Obj,
+	RequiredProps extends keyof s,
+> = UnionToIntersection<
+	{
+		-readonly [R in RequiredPropertyNames]-?: SchemaType;
+	} & {
+		-readonly [R in RequiredProps]-?: SchemaType;
+	} & {
+		-readonly [P in keyof s]?: SchemaType;
+	}
+>;
 
 type NullOrUndefined

= - | (p['nullable'] extends true ? null : never) - | (p['optional'] extends true ? undefined : never) + | (p["nullable"] extends true ? null : never) + | (p["optional"] extends true ? undefined : never) | T; // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection -// Get intersection from union -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +// Get intersection from union +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void + ? I + : never; // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 // To get union, we use `Foo extends any ? Hoge : never` -type UnionSchemaType = X extends any ? SchemaType : never; -type ArrayUnion = T extends any ? Array : never; +type UnionSchemaType< + a extends readonly any[], + X extends Schema = a[number], +> = X extends any ? SchemaType : never; +type ArrayUnion = T extends any ? Array : never; -export type SchemaTypeDef

= - p['type'] extends 'null' ? null : - p['type'] extends 'integer' ? number : - p['type'] extends 'number' ? number : - p['type'] extends 'string' ? ( - p['enum'] extends readonly string[] ? - p['enum'][number] : - p['format'] extends 'date-time' ? string : // Dateにする?? - string - ) : - p['type'] extends 'boolean' ? boolean : - p['type'] extends 'object' ? ( - p['ref'] extends keyof typeof refs ? Packed : - p['properties'] extends NonNullable ? ObjType[number]> : - p['anyOf'] extends ReadonlyArray ? UnionSchemaType & Partial>> : - p['allOf'] extends ReadonlyArray ? UnionToIntersection> : - any - ) : - p['type'] extends 'array' ? ( - p['items'] extends OfSchema ? ( - p['items']['anyOf'] extends ReadonlyArray ? UnionSchemaType>[] : - p['items']['oneOf'] extends ReadonlyArray ? ArrayUnion>> : - p['items']['allOf'] extends ReadonlyArray ? UnionToIntersection>>[] : - never - ) : - p['items'] extends NonNullable ? SchemaTypeDef[] : - any[] - ) : - p['oneOf'] extends ReadonlyArray ? UnionSchemaType : - any; +export type SchemaTypeDef

= p["type"] extends "null" + ? null + : p["type"] extends "integer" + ? number + : p["type"] extends "number" + ? number + : p["type"] extends "string" + ? p["enum"] extends readonly string[] + ? p["enum"][number] + : p["format"] extends "date-time" + ? string + : // Dateにする?? + string + : p["type"] extends "boolean" + ? boolean + : p["type"] extends "object" + ? p["ref"] extends keyof typeof refs + ? Packed + : p["properties"] extends NonNullable + ? ObjType[number]> + : p["anyOf"] extends ReadonlyArray + ? UnionSchemaType & + Partial>> + : p["allOf"] extends ReadonlyArray + ? UnionToIntersection> + : any + : p["type"] extends "array" + ? p["items"] extends OfSchema + ? p["items"]["anyOf"] extends ReadonlyArray + ? UnionSchemaType>[] + : p["items"]["oneOf"] extends ReadonlyArray + ? ArrayUnion>> + : p["items"]["allOf"] extends ReadonlyArray + ? UnionToIntersection>>[] + : never + : p["items"] extends NonNullable + ? SchemaTypeDef[] + : any[] + : p["oneOf"] extends ReadonlyArray + ? UnionSchemaType + : any; export type SchemaType

= NullOrUndefined>; diff --git a/packages/backend/src/misc/secure-rndstr.ts b/packages/backend/src/misc/secure-rndstr.ts index 8d4fcb1ba..7f5754e1c 100644 --- a/packages/backend/src/misc/secure-rndstr.ts +++ b/packages/backend/src/misc/secure-rndstr.ts @@ -1,16 +1,19 @@ -import * as crypto from 'node:crypto'; +import * as crypto from "node:crypto"; -const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'; -const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; +const L_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"; +const LU_CHARS = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; export function secureRndstr(length = 32, useLU = true): string { const chars = useLU ? LU_CHARS : L_CHARS; const chars_len = chars.length; - let str = ''; + let str = ""; for (let i = 0; i < length; i++) { - let rand = Math.floor((crypto.randomBytes(1).readUInt8(0) / 0xFF) * chars_len); + let rand = Math.floor( + (crypto.randomBytes(1).readUInt8(0) / 0xff) * chars_len, + ); if (rand === chars_len) { rand = chars_len - 1; } diff --git a/packages/backend/src/misc/should-block-instance.ts b/packages/backend/src/misc/should-block-instance.ts index ddd25eeee..6e4623242 100644 --- a/packages/backend/src/misc/should-block-instance.ts +++ b/packages/backend/src/misc/should-block-instance.ts @@ -1,6 +1,6 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Instance } from '@/models/entities/instance.js'; -import { Meta } from '@/models/entities/meta.js'; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import type { Instance } from "@/models/entities/instance.js"; +import type { Meta } from "@/models/entities/meta.js"; /** * Returns whether a specific host (punycoded) should be blocked. @@ -9,7 +9,12 @@ import { Meta } from '@/models/entities/meta.js'; * @param meta a resolved Meta table * @returns whether the given host should be blocked */ -export async function shouldBlockInstance(host: Instance['host'], meta?: Meta): Promise { - const { blockedHosts } = meta ?? await fetchMeta(); - return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost)); +export async function shouldBlockInstance( + host: Instance["host"], + meta?: Meta, +): Promise { + const { blockedHosts } = meta ?? (await fetchMeta()); + return blockedHosts.some( + (blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`), + ); } diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts index bc71cfbe9..d3a28cbd3 100644 --- a/packages/backend/src/misc/show-machine-info.ts +++ b/packages/backend/src/misc/show-machine-info.ts @@ -1,13 +1,17 @@ -import * as os from 'node:os'; -import sysUtils from 'systeminformation'; -import Logger from '@/services/logger.js'; +import * as os from "node:os"; +import sysUtils from "systeminformation"; +import type Logger from "@/services/logger.js"; export async function showMachineInfo(parentLogger: Logger) { - const logger = parentLogger.createSubLogger('machine'); + const logger = parentLogger.createSubLogger("machine"); logger.debug(`Hostname: ${os.hostname()}`); logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`); const mem = await sysUtils.mem(); const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1); const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1); - logger.debug(`CPU: ${os.cpus().length} core MEM: ${totalmem}GB (available: ${availmem}GB)`); + logger.debug( + `CPU: ${ + os.cpus().length + } core MEM: ${totalmem}GB (available: ${availmem}GB)`, + ); } diff --git a/packages/backend/src/misc/skipped-instances.ts b/packages/backend/src/misc/skipped-instances.ts index f51e77000..785393022 100644 --- a/packages/backend/src/misc/skipped-instances.ts +++ b/packages/backend/src/misc/skipped-instances.ts @@ -1,9 +1,9 @@ -import { Brackets } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Instances } from '@/models/index.js'; -import type { Instance } from '@/models/entities/instance.js'; -import { DAY } from '@/const.js'; -import { shouldBlockInstance } from './should-block-instance.js'; +import { Brackets } from "typeorm"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Instances } from "@/models/index.js"; +import type { Instance } from "@/models/entities/instance.js"; +import { DAY } from "@/const.js"; +import { shouldBlockInstance } from "./should-block-instance.js"; // Threshold from last contact after which an instance will be considered // "dead" and should no longer get activities delivered to it. @@ -11,32 +11,38 @@ const deadThreshold = 7 * DAY; /** * Returns the subset of hosts which should be skipped. - * + * * @param hosts array of punycoded instance hosts * @returns array of punycoed instance hosts that should be skipped (subset of hosts parameter) */ -export async function skippedInstances(hosts: Instance['host'][]): Promise { +export async function skippedInstances( + hosts: Instance["host"][], +): Promise { // first check for blocked instances since that info may already be in memory const meta = await fetchMeta(); - const shouldSkip = await Promise.all(hosts.map(host => shouldBlockInstance(host, meta))); + const shouldSkip = await Promise.all( + hosts.map((host) => shouldBlockInstance(host, meta)), + ); const skipped = hosts.filter((_, i) => shouldSkip[i]); - + // if possible return early and skip accessing the database - if (skipped.length === hosts.length) return hosts; + if (skipped.length === hosts.length) return hosts; const deadTime = new Date(Date.now() - deadThreshold); return skipped.concat( - await Instances.createQueryBuilder('instance') - .where('instance.host in (:...hosts)', { + await Instances.createQueryBuilder("instance") + .where("instance.host in (:...hosts)", { // don't check hosts again that we already know are suspended // also avoids adding duplicates to the list - hosts: hosts.filter(host => !skipped.includes(host)), + hosts: hosts.filter((host) => !skipped.includes(host)), }) - .andWhere(new Brackets(qb => { qb - .where('instance.isSuspended'); - })) - .select('host') + .andWhere( + new Brackets((qb) => { + qb.where("instance.isSuspended"); + }), + ) + .select("host") .getRawMany(), ); } @@ -49,7 +55,9 @@ export async function skippedInstances(hosts: Instance['host'][]): Promise { +export async function shouldSkipInstance( + host: Instance["host"], +): Promise { const skipped = await skippedInstances([host]); return skipped.length > 0; } diff --git a/packages/backend/src/misc/truncate.ts b/packages/backend/src/misc/truncate.ts index cb120331a..6bc58941a 100644 --- a/packages/backend/src/misc/truncate.ts +++ b/packages/backend/src/misc/truncate.ts @@ -1,8 +1,14 @@ -import { substring } from 'stringz'; +import { substring } from "stringz"; export function truncate(input: string, size: number): string; -export function truncate(input: string | undefined, size: number): string | undefined; -export function truncate(input: string | undefined, size: number): string | undefined { +export function truncate( + input: string | undefined, + size: number, +): string | undefined; +export function truncate( + input: string | undefined, + size: number, +): string | undefined { if (!input) { return input; } else { diff --git a/packages/backend/src/misc/webhook-cache.ts b/packages/backend/src/misc/webhook-cache.ts index 5e72dbd92..1eda5eaec 100644 --- a/packages/backend/src/misc/webhook-cache.ts +++ b/packages/backend/src/misc/webhook-cache.ts @@ -1,6 +1,6 @@ -import { Webhooks } from '@/models/index.js'; -import { Webhook } from '@/models/entities/webhook.js'; -import { subscriber } from '@/db/redis.js'; +import { Webhooks } from "@/models/index.js"; +import type { Webhook } from "@/models/entities/webhook.js"; +import { subscriber } from "@/db/redis.js"; let webhooksFetched = false; let webhooks: Webhook[] = []; @@ -16,31 +16,31 @@ export async function getActiveWebhooks() { return webhooks; } -subscriber.on('message', async (_, data) => { +subscriber.on("message", async (_, data) => { const obj = JSON.parse(data); - if (obj.channel === 'internal') { + if (obj.channel === "internal") { const { type, body } = obj.message; switch (type) { - case 'webhookCreated': + case "webhookCreated": if (body.active) { webhooks.push(body); } break; - case 'webhookUpdated': + case "webhookUpdated": if (body.active) { - const i = webhooks.findIndex(a => a.id === body.id); + const i = webhooks.findIndex((a) => a.id === body.id); if (i > -1) { webhooks[i] = body; } else { webhooks.push(body); } } else { - webhooks = webhooks.filter(a => a.id !== body.id); + webhooks = webhooks.filter((a) => a.id !== body.id); } break; - case 'webhookDeleted': - webhooks = webhooks.filter(a => a.id !== body.id); + case "webhookDeleted": + webhooks = webhooks.filter((a) => a.id !== body.id); break; default: break; diff --git a/packages/backend/src/models/entities/abuse-user-report.ts b/packages/backend/src/models/entities/abuse-user-report.ts index 6ac563552..655fdd3ca 100644 --- a/packages/backend/src/models/entities/abuse-user-report.ts +++ b/packages/backend/src/models/entities/abuse-user-report.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class AbuseUserReport { @@ -15,7 +22,7 @@ export class AbuseUserReport { @Index() @Column(id()) - public targetUserId: User['id']; + public targetUserId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -25,7 +32,7 @@ export class AbuseUserReport { @Index() @Column(id()) - public reporterId: User['id']; + public reporterId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -37,7 +44,7 @@ export class AbuseUserReport { ...id(), nullable: true, }) - public assigneeId: User['id'] | null; + public assigneeId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts index c6e2141a4..83d7bbda8 100644 --- a/packages/backend/src/models/entities/access-token.ts +++ b/packages/backend/src/models/entities/access-token.ts @@ -1,7 +1,14 @@ -import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { User } from './user.js'; -import { App } from './app.js'; -import { id } from '../id.js'; +import { + Entity, + PrimaryColumn, + Index, + Column, + ManyToOne, + JoinColumn, +} from "typeorm"; +import { User } from "./user.js"; +import { App } from "./app.js"; +import { id } from "../id.js"; @Entity() export class AccessToken { @@ -39,7 +46,7 @@ export class AccessToken { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -51,7 +58,7 @@ export class AccessToken { ...id(), nullable: true, }) - public appId: App['id'] | null; + public appId: App["id"] | null; @ManyToOne(type => App, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/ad.ts b/packages/backend/src/models/entities/ad.ts index 36b758f20..fa4297365 100644 --- a/packages/backend/src/models/entities/ad.ts +++ b/packages/backend/src/models/entities/ad.ts @@ -1,5 +1,5 @@ -import { Entity, Index, Column, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; +import { Entity, Index, Column, PrimaryColumn } from "typeorm"; +import { id } from "../id.js"; @Entity() export class Ad { diff --git a/packages/backend/src/models/entities/announcement-read.ts b/packages/backend/src/models/entities/announcement-read.ts index e4d256a86..87d0f0e9e 100644 --- a/packages/backend/src/models/entities/announcement-read.ts +++ b/packages/backend/src/models/entities/announcement-read.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Announcement } from './announcement.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Announcement } from "./announcement.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'announcementId'], { unique: true }) @@ -16,7 +23,7 @@ export class AnnouncementRead { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -26,7 +33,7 @@ export class AnnouncementRead { @Index() @Column(id()) - public announcementId: Announcement['id']; + public announcementId: Announcement["id"]; @ManyToOne(type => Announcement, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/announcement.ts b/packages/backend/src/models/entities/announcement.ts index beb2f8246..9d45af014 100644 --- a/packages/backend/src/models/entities/announcement.ts +++ b/packages/backend/src/models/entities/announcement.ts @@ -1,5 +1,5 @@ -import { Entity, Index, Column, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; +import { Entity, Index, Column, PrimaryColumn } from "typeorm"; +import { id } from "../id.js"; @Entity() export class Announcement { diff --git a/packages/backend/src/models/entities/antenna-note.ts b/packages/backend/src/models/entities/antenna-note.ts index fcca493fe..c47c796bb 100644 --- a/packages/backend/src/models/entities/antenna-note.ts +++ b/packages/backend/src/models/entities/antenna-note.ts @@ -1,7 +1,14 @@ -import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { Note } from './note.js'; -import { Antenna } from './antenna.js'; -import { id } from '../id.js'; +import { + Entity, + Index, + JoinColumn, + Column, + ManyToOne, + PrimaryColumn, +} from "typeorm"; +import { Note } from "./note.js"; +import { Antenna } from "./antenna.js"; +import { id } from "../id.js"; @Entity() @Index(['noteId', 'antennaId'], { unique: true }) @@ -14,7 +21,7 @@ export class AntennaNote { ...id(), comment: 'The note ID.', }) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -27,7 +34,7 @@ export class AntennaNote { ...id(), comment: 'The antenna ID.', }) - public antennaId: Antenna['id']; + public antennaId: Antenna["id"]; @ManyToOne(type => Antenna, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/antenna.ts b/packages/backend/src/models/entities/antenna.ts index 6c8bb13e5..45d9553e4 100644 --- a/packages/backend/src/models/entities/antenna.ts +++ b/packages/backend/src/models/entities/antenna.ts @@ -1,8 +1,15 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { UserList } from './user-list.js'; -import { UserGroupJoining } from './user-group-joining.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { UserList } from "./user-list.js"; +import { UserGroupJoining } from "./user-group-joining.js"; @Entity() export class Antenna { @@ -19,7 +26,7 @@ export class Antenna { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -34,13 +41,13 @@ export class Antenna { public name: string; @Column('enum', { enum: ['home', 'all', 'users', 'list', 'group'] }) - public src: 'home' | 'all' | 'users' | 'list' | 'group'; + public src: "home" | "all" | "users" | "list" | "group"; @Column({ ...id(), nullable: true, }) - public userListId: UserList['id'] | null; + public userListId: UserList["id"] | null; @ManyToOne(type => UserList, { onDelete: 'CASCADE', @@ -52,7 +59,7 @@ export class Antenna { ...id(), nullable: true, }) - public userGroupJoiningId: UserGroupJoining['id'] | null; + public userGroupJoiningId: UserGroupJoining["id"] | null; @ManyToOne(type => UserGroupJoining, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/app.ts b/packages/backend/src/models/entities/app.ts index 46c11548a..bb33eede4 100644 --- a/packages/backend/src/models/entities/app.ts +++ b/packages/backend/src/models/entities/app.ts @@ -1,6 +1,6 @@ -import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { Entity, PrimaryColumn, Column, Index, ManyToOne } from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class App { @@ -19,7 +19,7 @@ export class App { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/attestation-challenge.ts b/packages/backend/src/models/entities/attestation-challenge.ts index c40df2329..7a87d42be 100644 --- a/packages/backend/src/models/entities/attestation-challenge.ts +++ b/packages/backend/src/models/entities/attestation-challenge.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + JoinColumn, + Column, + ManyToOne, + Index, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class AttestationChallenge { @@ -9,7 +16,7 @@ export class AttestationChallenge { @Index() @PrimaryColumn(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts index 295d1b486..b762f8462 100644 --- a/packages/backend/src/models/entities/auth-session.ts +++ b/packages/backend/src/models/entities/auth-session.ts @@ -1,7 +1,14 @@ -import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { User } from './user.js'; -import { App } from './app.js'; -import { id } from '../id.js'; +import { + Entity, + PrimaryColumn, + Index, + Column, + ManyToOne, + JoinColumn, +} from "typeorm"; +import { User } from "./user.js"; +import { App } from "./app.js"; +import { id } from "../id.js"; @Entity() export class AuthSession { @@ -23,7 +30,7 @@ export class AuthSession { ...id(), nullable: true, }) - public userId: User['id'] | null; + public userId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -33,7 +40,7 @@ export class AuthSession { public user: User | null; @Column(id()) - public appId: App['id']; + public appId: App["id"]; @ManyToOne(type => App, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/blocking.ts b/packages/backend/src/models/entities/blocking.ts index 4ac73a00b..3a44a4d65 100644 --- a/packages/backend/src/models/entities/blocking.ts +++ b/packages/backend/src/models/entities/blocking.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['blockerId', 'blockeeId'], { unique: true }) @@ -19,7 +26,7 @@ export class Blocking { ...id(), comment: 'The blockee user ID.', }) - public blockeeId: User['id']; + public blockeeId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -32,7 +39,7 @@ export class Blocking { ...id(), comment: 'The blocker user ID.', }) - public blockerId: User['id']; + public blockerId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/channel-following.ts b/packages/backend/src/models/entities/channel-following.ts index 029dd6cf1..04ec193e1 100644 --- a/packages/backend/src/models/entities/channel-following.ts +++ b/packages/backend/src/models/entities/channel-following.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { Channel } from './channel.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { Channel } from "./channel.js"; @Entity() @Index(['followerId', 'followeeId'], { unique: true }) @@ -20,7 +27,7 @@ export class ChannelFollowing { ...id(), comment: 'The followee channel ID.', }) - public followeeId: Channel['id']; + public followeeId: Channel["id"]; @ManyToOne(type => Channel, { onDelete: 'CASCADE', @@ -33,7 +40,7 @@ export class ChannelFollowing { ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/channel-note-pining.ts b/packages/backend/src/models/entities/channel-note-pining.ts index 23be3b69d..bd13f4ca3 100644 --- a/packages/backend/src/models/entities/channel-note-pining.ts +++ b/packages/backend/src/models/entities/channel-note-pining.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { Note } from './note.js'; -import { Channel } from './channel.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { Note } from "./note.js"; +import { Channel } from "./channel.js"; +import { id } from "../id.js"; @Entity() @Index(['channelId', 'noteId'], { unique: true }) @@ -16,7 +23,7 @@ export class ChannelNotePining { @Index() @Column(id()) - public channelId: Channel['id']; + public channelId: Channel["id"]; @ManyToOne(type => Channel, { onDelete: 'CASCADE', @@ -25,7 +32,7 @@ export class ChannelNotePining { public channel: Channel | null; @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/channel.ts b/packages/backend/src/models/entities/channel.ts index abf6668bd..7f9851dbf 100644 --- a/packages/backend/src/models/entities/channel.ts +++ b/packages/backend/src/models/entities/channel.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { DriveFile } from './drive-file.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { DriveFile } from "./drive-file.js"; @Entity() export class Channel { @@ -26,7 +33,7 @@ export class Channel { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'SET NULL', @@ -51,7 +58,7 @@ export class Channel { nullable: true, comment: 'The ID of banner Channel.', }) - public bannerId: DriveFile['id'] | null; + public bannerId: DriveFile["id"] | null; @ManyToOne(type => DriveFile, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/clip-note.ts b/packages/backend/src/models/entities/clip-note.ts index 6f3688550..bc51daaf4 100644 --- a/packages/backend/src/models/entities/clip-note.ts +++ b/packages/backend/src/models/entities/clip-note.ts @@ -1,7 +1,14 @@ -import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { Note } from './note.js'; -import { Clip } from './clip.js'; -import { id } from '../id.js'; +import { + Entity, + Index, + JoinColumn, + Column, + ManyToOne, + PrimaryColumn, +} from "typeorm"; +import { Note } from "./note.js"; +import { Clip } from "./clip.js"; +import { id } from "../id.js"; @Entity() @Index(['noteId', 'clipId'], { unique: true }) @@ -14,7 +21,7 @@ export class ClipNote { ...id(), comment: 'The note ID.', }) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -27,7 +34,7 @@ export class ClipNote { ...id(), comment: 'The clip ID.', }) - public clipId: Clip['id']; + public clipId: Clip["id"]; @ManyToOne(type => Clip, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts index 1386684c3..10591cbee 100644 --- a/packages/backend/src/models/entities/clip.ts +++ b/packages/backend/src/models/entities/clip.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class Clip { @@ -17,7 +24,7 @@ export class Clip { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index d410b1d42..1fa91b1a9 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './user.js'; -import { DriveFolder } from './drive-folder.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { id } from "../id.js"; +import { User } from "./user.js"; +import { DriveFolder } from "./drive-folder.js"; @Entity() @Index(['userId', 'folderId', 'id']) @@ -21,7 +28,7 @@ export class DriveFile { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'SET NULL', @@ -77,7 +84,12 @@ export class DriveFile { default: {}, comment: 'The any properties of the DriveFile. For example, it includes image width/height.', }) - public properties: { width?: number; height?: number; orientation?: number; avgColor?: string }; + public properties: { + width?: number; + height?: number; + orientation?: number; + avgColor?: string; + }; @Column('boolean') public storedInternal: boolean; @@ -141,7 +153,7 @@ export class DriveFile { nullable: true, comment: 'The parent folder ID. If null, it means the DriveFile is located in root.', }) - public folderId: DriveFolder['id'] | null; + public folderId: DriveFolder["id"] | null; @ManyToOne(type => DriveFolder, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/drive-folder.ts b/packages/backend/src/models/entities/drive-folder.ts index d4022c6eb..77031ce4e 100644 --- a/packages/backend/src/models/entities/drive-folder.ts +++ b/packages/backend/src/models/entities/drive-folder.ts @@ -1,6 +1,13 @@ -import { JoinColumn, ManyToOne, Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + JoinColumn, + ManyToOne, + Entity, + PrimaryColumn, + Index, + Column, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class DriveFolder { @@ -25,7 +32,7 @@ export class DriveFolder { nullable: true, comment: 'The owner ID.', }) - public userId: User['id'] | null; + public userId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -39,7 +46,7 @@ export class DriveFolder { nullable: true, comment: 'The parent folder ID. If null, it means the DriveFolder is located in root.', }) - public parentId: DriveFolder['id'] | null; + public parentId: DriveFolder["id"] | null; @ManyToOne(type => DriveFolder, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index 7332dd185..f251de897 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -1,5 +1,5 @@ -import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { PrimaryColumn, Entity, Index, Column } from "typeorm"; +import { id } from "../id.js"; @Entity() @Index(['name', 'host'], { unique: true }) diff --git a/packages/backend/src/models/entities/follow-request.ts b/packages/backend/src/models/entities/follow-request.ts index 89946f6d3..658fed5a5 100644 --- a/packages/backend/src/models/entities/follow-request.ts +++ b/packages/backend/src/models/entities/follow-request.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['followerId', 'followeeId'], { unique: true }) @@ -18,7 +25,7 @@ export class FollowRequest { ...id(), comment: 'The followee user ID.', }) - public followeeId: User['id']; + public followeeId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -31,7 +38,7 @@ export class FollowRequest { ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/following.ts b/packages/backend/src/models/entities/following.ts index b283ca7e8..11f633fcd 100644 --- a/packages/backend/src/models/entities/following.ts +++ b/packages/backend/src/models/entities/following.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['followerId', 'followeeId'], { unique: true }) @@ -19,7 +26,7 @@ export class Following { ...id(), comment: 'The followee user ID.', }) - public followeeId: User['id']; + public followeeId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -32,7 +39,7 @@ export class Following { ...id(), comment: 'The follower user ID.', }) - public followerId: User['id']; + public followerId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/gallery-like.ts b/packages/backend/src/models/entities/gallery-like.ts index 4ce166d19..e74e3c3ce 100644 --- a/packages/backend/src/models/entities/gallery-like.ts +++ b/packages/backend/src/models/entities/gallery-like.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { GalleryPost } from './gallery-post.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { GalleryPost } from "./gallery-post.js"; @Entity() @Index(['userId', 'postId'], { unique: true }) @@ -14,7 +21,7 @@ export class GalleryLike { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -23,7 +30,7 @@ export class GalleryLike { public user: User | null; @Column(id()) - public postId: GalleryPost['id']; + public postId: GalleryPost["id"]; @ManyToOne(type => GalleryPost, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/gallery-post.ts b/packages/backend/src/models/entities/gallery-post.ts index 774cb946e..a79bb8835 100644 --- a/packages/backend/src/models/entities/gallery-post.ts +++ b/packages/backend/src/models/entities/gallery-post.ts @@ -1,7 +1,14 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { DriveFile } from './drive-file.js'; +import { + Entity, + Index, + JoinColumn, + Column, + PrimaryColumn, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import type { DriveFile } from "./drive-file.js"; @Entity() export class GalleryPost { @@ -35,7 +42,7 @@ export class GalleryPost { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -48,7 +55,7 @@ export class GalleryPost { ...id(), array: true, default: '{}', }) - public fileIds: DriveFile['id'][]; + public fileIds: DriveFile["id"][]; @Index() @Column('boolean', { diff --git a/packages/backend/src/models/entities/hashtag.ts b/packages/backend/src/models/entities/hashtag.ts index 6bd991f62..06fa004be 100644 --- a/packages/backend/src/models/entities/hashtag.ts +++ b/packages/backend/src/models/entities/hashtag.ts @@ -1,6 +1,6 @@ -import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { Entity, PrimaryColumn, Index, Column } from "typeorm"; +import type { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class Hashtag { @@ -17,7 +17,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedUserIds: User['id'][]; + public mentionedUserIds: User["id"][]; @Index() @Column('integer', { @@ -29,7 +29,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedLocalUserIds: User['id'][]; + public mentionedLocalUserIds: User["id"][]; @Index() @Column('integer', { @@ -41,7 +41,7 @@ export class Hashtag { ...id(), array: true, }) - public mentionedRemoteUserIds: User['id'][]; + public mentionedRemoteUserIds: User["id"][]; @Index() @Column('integer', { @@ -53,7 +53,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedUserIds: User['id'][]; + public attachedUserIds: User["id"][]; @Index() @Column('integer', { @@ -65,7 +65,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedLocalUserIds: User['id'][]; + public attachedLocalUserIds: User["id"][]; @Index() @Column('integer', { @@ -77,7 +77,7 @@ export class Hashtag { ...id(), array: true, }) - public attachedRemoteUserIds: User['id'][]; + public attachedRemoteUserIds: User["id"][]; @Index() @Column('integer', { diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts index 7ea923438..2b118455d 100644 --- a/packages/backend/src/models/entities/instance.ts +++ b/packages/backend/src/models/entities/instance.ts @@ -1,5 +1,5 @@ -import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { Entity, PrimaryColumn, Index, Column } from "typeorm"; +import { id } from "../id.js"; @Entity() export class Instance { diff --git a/packages/backend/src/models/entities/messaging-message.ts b/packages/backend/src/models/entities/messaging-message.ts index 099fb7aa0..9cf197fa3 100644 --- a/packages/backend/src/models/entities/messaging-message.ts +++ b/packages/backend/src/models/entities/messaging-message.ts @@ -1,8 +1,15 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { DriveFile } from './drive-file.js'; -import { id } from '../id.js'; -import { UserGroup } from './user-group.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { DriveFile } from "./drive-file.js"; +import { id } from "../id.js"; +import { UserGroup } from "./user-group.js"; @Entity() export class MessagingMessage { @@ -20,7 +27,7 @@ export class MessagingMessage { ...id(), comment: 'The sender user ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -33,7 +40,7 @@ export class MessagingMessage { ...id(), nullable: true, comment: 'The recipient user ID.', }) - public recipientId: User['id'] | null; + public recipientId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -46,7 +53,7 @@ export class MessagingMessage { ...id(), nullable: true, comment: 'The recipient group ID.', }) - public groupId: UserGroup['id'] | null; + public groupId: UserGroup["id"] | null; @ManyToOne(type => UserGroup, { onDelete: 'CASCADE', @@ -73,13 +80,13 @@ export class MessagingMessage { ...id(), array: true, default: '{}', }) - public reads: User['id'][]; + public reads: User["id"][]; @Column({ ...id(), nullable: true, }) - public fileId: DriveFile['id'] | null; + public fileId: DriveFile["id"] | null; @ManyToOne(type => DriveFile, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 46cde0505..26a7c9c19 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -1,7 +1,7 @@ -import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './user.js'; -import type { Clip } from './clip.js'; +import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from "typeorm"; +import { id } from "../id.js"; +import { User } from "./user.js"; +import type { Clip } from "./clip.js"; @Entity() export class Meta { @@ -121,7 +121,7 @@ export class Meta { ...id(), nullable: true, }) - public pinnedClipId: Clip['id'] | null; + public pinnedClipId: Clip["id"] | null; @Column('varchar', { length: 512, @@ -176,7 +176,7 @@ export class Meta { ...id(), nullable: true, }) - public proxyAccountId: User['id'] | null; + public proxyAccountId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'SET NULL', @@ -227,13 +227,18 @@ export class Meta { enum: ['none', 'all', 'local', 'remote'], default: 'none', }) - public sensitiveMediaDetection: 'none' | 'all' | 'local' | 'remote'; + public sensitiveMediaDetection: "none" | "all" | "local" | "remote"; @Column('enum', { enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'], default: 'medium', }) - public sensitiveMediaDetectionSensitivity: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh'; + public sensitiveMediaDetectionSensitivity: + | "medium" + | "low" + | "high" + | "veryLow" + | "veryHigh"; @Column('boolean', { default: false, diff --git a/packages/backend/src/models/entities/moderation-log.ts b/packages/backend/src/models/entities/moderation-log.ts index c99e55078..cc745e0d2 100644 --- a/packages/backend/src/models/entities/moderation-log.ts +++ b/packages/backend/src/models/entities/moderation-log.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class ModerationLog { @@ -14,7 +21,7 @@ export class ModerationLog { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/muted-note.ts b/packages/backend/src/models/entities/muted-note.ts index 96a4fa8e3..11a6ae95d 100644 --- a/packages/backend/src/models/entities/muted-note.ts +++ b/packages/backend/src/models/entities/muted-note.ts @@ -1,8 +1,15 @@ -import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { mutedNoteReasons } from '../../types.js'; +import { + Entity, + Index, + JoinColumn, + Column, + ManyToOne, + PrimaryColumn, +} from "typeorm"; +import { Note } from "./note.js"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { mutedNoteReasons } from "../../types.js"; @Entity() @Index(['noteId', 'userId'], { unique: true }) @@ -15,7 +22,7 @@ export class MutedNote { ...id(), comment: 'The note ID.', }) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -28,7 +35,7 @@ export class MutedNote { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts index 8f9e69063..561bcfb95 100644 --- a/packages/backend/src/models/entities/muting.ts +++ b/packages/backend/src/models/entities/muting.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['muterId', 'muteeId'], { unique: true }) @@ -25,7 +32,7 @@ export class Muting { ...id(), comment: 'The mutee user ID.', }) - public muteeId: User['id']; + public muteeId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -38,7 +45,7 @@ export class Muting { ...id(), comment: 'The muter user ID.', }) - public muterId: User['id']; + public muterId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/note-favorite.ts b/packages/backend/src/models/entities/note-favorite.ts index fe065b77a..ab12d8b1b 100644 --- a/packages/backend/src/models/entities/note-favorite.ts +++ b/packages/backend/src/models/entities/note-favorite.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { Note } from "./note.js"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -16,7 +23,7 @@ export class NoteFavorite { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -25,7 +32,7 @@ export class NoteFavorite { public user: User | null; @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/note-reaction.ts b/packages/backend/src/models/entities/note-reaction.ts index d7bc60989..0e51c33b1 100644 --- a/packages/backend/src/models/entities/note-reaction.ts +++ b/packages/backend/src/models/entities/note-reaction.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Note } from './note.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Note } from "./note.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -17,7 +24,7 @@ export class NoteReaction { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -27,7 +34,7 @@ export class NoteReaction { @Index() @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/note-thread-muting.ts b/packages/backend/src/models/entities/note-thread-muting.ts index 8c5f7bbab..2985b195f 100644 --- a/packages/backend/src/models/entities/note-thread-muting.ts +++ b/packages/backend/src/models/entities/note-thread-muting.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Note } from './note.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Note } from "./note.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'threadId'], { unique: true }) @@ -17,7 +24,7 @@ export class NoteThreadMuting { @Column({ ...id(), }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/note-unread.ts b/packages/backend/src/models/entities/note-unread.ts index a7acf254d..d5bba7221 100644 --- a/packages/backend/src/models/entities/note-unread.ts +++ b/packages/backend/src/models/entities/note-unread.ts @@ -1,8 +1,15 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Note } from './note.js'; -import { id } from '../id.js'; -import { Channel } from './channel.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Note } from "./note.js"; +import { id } from "../id.js"; +import type { Channel } from "./channel.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -12,7 +19,7 @@ export class NoteUnread { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -22,7 +29,7 @@ export class NoteUnread { @Index() @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -50,7 +57,7 @@ export class NoteUnread { ...id(), comment: '[Denormalized]', }) - public noteUserId: User['id']; + public noteUserId: User["id"]; @Index() @Column({ @@ -58,6 +65,6 @@ export class NoteUnread { nullable: true, comment: '[Denormalized]', }) - public noteChannelId: Channel['id'] | null; + public noteChannelId: Channel["id"] | null; //#endregion } diff --git a/packages/backend/src/models/entities/note-watching.ts b/packages/backend/src/models/entities/note-watching.ts index ed82e7dfe..7ac3e8e29 100644 --- a/packages/backend/src/models/entities/note-watching.ts +++ b/packages/backend/src/models/entities/note-watching.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Note } from './note.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Note } from "./note.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -20,7 +27,7 @@ export class NoteWatching { ...id(), comment: 'The watcher ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -33,7 +40,7 @@ export class NoteWatching { ...id(), comment: 'The target Note ID.', }) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -47,6 +54,6 @@ export class NoteWatching { ...id(), comment: '[Denormalized]', }) - public noteUserId: Note['userId']; + public noteUserId: Note["userId"]; //#endregion } diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index 0ffeb85f6..fd6b170c0 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -1,9 +1,16 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { DriveFile } from './drive-file.js'; -import { id } from '../id.js'; -import { noteVisibilities } from '../../types.js'; -import { Channel } from './channel.js'; +import { + Entity, + Index, + JoinColumn, + Column, + PrimaryColumn, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import type { DriveFile } from "./drive-file.js"; +import { id } from "../id.js"; +import { noteVisibilities } from "../../types.js"; +import { Channel } from "./channel.js"; @Entity() @Index('IDX_NOTE_TAGS', { synchronize: false }) @@ -25,7 +32,7 @@ export class Note { nullable: true, comment: 'The ID of reply target.', }) - public replyId: Note['id'] | null; + public replyId: Note["id"] | null; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -39,7 +46,7 @@ export class Note { nullable: true, comment: 'The ID of renote target.', }) - public renoteId: Note['id'] | null; + public renoteId: Note["id"] | null; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -73,7 +80,7 @@ export class Note { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -133,7 +140,7 @@ export class Note { ...id(), array: true, default: '{}', }) - public fileIds: DriveFile['id'][]; + public fileIds: DriveFile["id"][]; @Index() @Column('varchar', { @@ -146,14 +153,14 @@ export class Note { ...id(), array: true, default: '{}', }) - public visibleUserIds: User['id'][]; + public visibleUserIds: User["id"][]; @Index() @Column({ ...id(), array: true, default: '{}', }) - public mentions: User['id'][]; + public mentions: User["id"][]; @Column('text', { default: '[]', @@ -182,7 +189,7 @@ export class Note { nullable: true, comment: 'The ID of source channel.', }) - public channelId: Channel['id'] | null; + public channelId: Channel["id"] | null; @ManyToOne(type => Channel, { onDelete: 'CASCADE', @@ -203,7 +210,7 @@ export class Note { nullable: true, comment: '[Denormalized]', }) - public replyUserId: User['id'] | null; + public replyUserId: User["id"] | null; @Column('varchar', { length: 128, nullable: true, @@ -216,7 +223,7 @@ export class Note { nullable: true, comment: '[Denormalized]', }) - public renoteUserId: User['id'] | null; + public renoteUserId: User["id"] | null; @Column('varchar', { length: 128, nullable: true, diff --git a/packages/backend/src/models/entities/notification.ts b/packages/backend/src/models/entities/notification.ts index 2cf8a1939..2c55e988f 100644 --- a/packages/backend/src/models/entities/notification.ts +++ b/packages/backend/src/models/entities/notification.ts @@ -1,11 +1,18 @@ -import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { Note } from './note.js'; -import { FollowRequest } from './follow-request.js'; -import { UserGroupInvitation } from './user-group-invitation.js'; -import { AccessToken } from './access-token.js'; -import { notificationTypes } from '@/types.js'; +import { + Entity, + Index, + JoinColumn, + ManyToOne, + Column, + PrimaryColumn, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { Note } from "./note.js"; +import { FollowRequest } from "./follow-request.js"; +import { UserGroupInvitation } from "./user-group-invitation.js"; +import { AccessToken } from "./access-token.js"; +import { notificationTypes } from "@/types.js"; @Entity() export class Notification { @@ -26,7 +33,7 @@ export class Notification { ...id(), comment: 'The ID of recipient user of the Notification.', }) - public notifieeId: User['id']; + public notifieeId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -43,7 +50,7 @@ export class Notification { nullable: true, comment: 'The ID of sender user of the Notification.', }) - public notifierId: User['id'] | null; + public notifierId: User["id"] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -87,7 +94,7 @@ export class Notification { ...id(), nullable: true, }) - public noteId: Note['id'] | null; + public noteId: Note["id"] | null; @ManyToOne(type => Note, { onDelete: 'CASCADE', @@ -99,7 +106,7 @@ export class Notification { ...id(), nullable: true, }) - public followRequestId: FollowRequest['id'] | null; + public followRequestId: FollowRequest["id"] | null; @ManyToOne(type => FollowRequest, { onDelete: 'CASCADE', @@ -111,7 +118,7 @@ export class Notification { ...id(), nullable: true, }) - public userGroupInvitationId: UserGroupInvitation['id'] | null; + public userGroupInvitationId: UserGroupInvitation["id"] | null; @ManyToOne(type => UserGroupInvitation, { onDelete: 'CASCADE', @@ -163,7 +170,7 @@ export class Notification { ...id(), nullable: true, }) - public appAccessTokenId: AccessToken['id'] | null; + public appAccessTokenId: AccessToken["id"] | null; @ManyToOne(type => AccessToken, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/page-like.ts b/packages/backend/src/models/entities/page-like.ts index 17f4ebf52..75f4dc49b 100644 --- a/packages/backend/src/models/entities/page-like.ts +++ b/packages/backend/src/models/entities/page-like.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { Page } from './page.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { Page } from "./page.js"; @Entity() @Index(['userId', 'pageId'], { unique: true }) @@ -14,7 +21,7 @@ export class PageLike { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -23,7 +30,7 @@ export class PageLike { public user: User | null; @Column(id()) - public pageId: Page['id']; + public pageId: Page["id"]; @ManyToOne(type => Page, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/page.ts b/packages/backend/src/models/entities/page.ts index fac59479e..5fe9f5208 100644 --- a/packages/backend/src/models/entities/page.ts +++ b/packages/backend/src/models/entities/page.ts @@ -1,7 +1,14 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; -import { DriveFile } from './drive-file.js'; +import { + Entity, + Index, + JoinColumn, + Column, + PrimaryColumn, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; +import { DriveFile } from "./drive-file.js"; @Entity() @Index(['userId', 'name'], { unique: true }) @@ -58,7 +65,7 @@ export class Page { ...id(), comment: 'The ID of author.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -70,7 +77,7 @@ export class Page { ...id(), nullable: true, }) - public eyeCatchingImageId: DriveFile['id'] | null; + public eyeCatchingImageId: DriveFile["id"] | null; @ManyToOne(type => DriveFile, { onDelete: 'CASCADE', @@ -100,14 +107,14 @@ export class Page { * specified ... visibleUserIds で指定したユーザーのみ */ @Column('enum', { enum: ['public', 'followers', 'specified'] }) - public visibility: 'public' | 'followers' | 'specified'; + public visibility: "public" | "followers" | "specified"; @Index() @Column({ ...id(), array: true, default: '{}', }) - public visibleUserIds: User['id'][]; + public visibleUserIds: User["id"][]; @Column('integer', { default: 0, diff --git a/packages/backend/src/models/entities/password-reset-request.ts b/packages/backend/src/models/entities/password-reset-request.ts index 05e62cc5a..4c681d4f7 100644 --- a/packages/backend/src/models/entities/password-reset-request.ts +++ b/packages/backend/src/models/entities/password-reset-request.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; -import { id } from '../id.js'; -import { User } from './user.js'; +import { + PrimaryColumn, + Entity, + Index, + Column, + ManyToOne, + JoinColumn, +} from "typeorm"; +import { id } from "../id.js"; +import { User } from "./user.js"; @Entity() export class PasswordResetRequest { @@ -20,7 +27,7 @@ export class PasswordResetRequest { @Column({ ...id(), }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/poll-vote.ts b/packages/backend/src/models/entities/poll-vote.ts index fca1cd009..0649951cf 100644 --- a/packages/backend/src/models/entities/poll-vote.ts +++ b/packages/backend/src/models/entities/poll-vote.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { Note } from './note.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { Note } from "./note.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId', 'choice'], { unique: true }) @@ -17,7 +24,7 @@ export class PollVote { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -27,7 +34,7 @@ export class PollVote { @Index() @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/poll.ts b/packages/backend/src/models/entities/poll.ts index d833dd7bc..28a70b3c7 100644 --- a/packages/backend/src/models/entities/poll.ts +++ b/packages/backend/src/models/entities/poll.ts @@ -1,13 +1,20 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { noteVisibilities } from '../../types.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + OneToOne, +} from "typeorm"; +import { id } from "../id.js"; +import { Note } from "./note.js"; +import type { User } from "./user.js"; +import { noteVisibilities } from "../../types.js"; @Entity() export class Poll { @PrimaryColumn(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @OneToOne(type => Note, { onDelete: 'CASCADE', @@ -45,7 +52,7 @@ export class Poll { ...id(), comment: '[Denormalized]', }) - public userId: User['id']; + public userId: User["id"]; @Index() @Column('varchar', { diff --git a/packages/backend/src/models/entities/promo-note.ts b/packages/backend/src/models/entities/promo-note.ts index d110b81e9..4daacd246 100644 --- a/packages/backend/src/models/entities/promo-note.ts +++ b/packages/backend/src/models/entities/promo-note.ts @@ -1,12 +1,19 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + OneToOne, +} from "typeorm"; +import { Note } from "./note.js"; +import type { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class PromoNote { @PrimaryColumn(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @OneToOne(type => Note, { onDelete: 'CASCADE', @@ -23,6 +30,6 @@ export class PromoNote { ...id(), comment: '[Denormalized]', }) - public userId: User['id']; + public userId: User["id"]; //#endregion } diff --git a/packages/backend/src/models/entities/promo-read.ts b/packages/backend/src/models/entities/promo-read.ts index a63b79cd1..5938bfde9 100644 --- a/packages/backend/src/models/entities/promo-read.ts +++ b/packages/backend/src/models/entities/promo-read.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { Note } from "./note.js"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -16,7 +23,7 @@ export class PromoRead { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -25,7 +32,7 @@ export class PromoRead { public user: User | null; @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/registration-tickets.ts b/packages/backend/src/models/entities/registration-tickets.ts index 139e40f85..af785fbc0 100644 --- a/packages/backend/src/models/entities/registration-tickets.ts +++ b/packages/backend/src/models/entities/registration-tickets.ts @@ -1,5 +1,5 @@ -import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { PrimaryColumn, Entity, Index, Column } from "typeorm"; +import { id } from "../id.js"; @Entity() export class RegistrationTicket { diff --git a/packages/backend/src/models/entities/registry-item.ts b/packages/backend/src/models/entities/registry-item.ts index 283796df9..655573883 100644 --- a/packages/backend/src/models/entities/registry-item.ts +++ b/packages/backend/src/models/entities/registry-item.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; // TODO: 同じdomain、同じscope、同じkeyのレコードは二つ以上存在しないように制約付けたい @Entity() @@ -23,7 +30,7 @@ export class RegistryItem { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/relay.ts b/packages/backend/src/models/entities/relay.ts index 94d192957..82c0779ff 100644 --- a/packages/backend/src/models/entities/relay.ts +++ b/packages/backend/src/models/entities/relay.ts @@ -1,5 +1,5 @@ -import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { PrimaryColumn, Entity, Index, Column } from "typeorm"; +import { id } from "../id.js"; @Entity() export class Relay { @@ -15,5 +15,5 @@ export class Relay { @Column('enum', { enum: ['requesting', 'accepted', 'rejected'], }) - public status: 'requesting' | 'accepted' | 'rejected'; + public status: "requesting" | "accepted" | "rejected"; } diff --git a/packages/backend/src/models/entities/signin.ts b/packages/backend/src/models/entities/signin.ts index ba81f45e4..785991823 100644 --- a/packages/backend/src/models/entities/signin.ts +++ b/packages/backend/src/models/entities/signin.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class Signin { @@ -14,7 +21,7 @@ export class Signin { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/sw-subscription.ts b/packages/backend/src/models/entities/sw-subscription.ts index 59144d348..26891c1ce 100644 --- a/packages/backend/src/models/entities/sw-subscription.ts +++ b/packages/backend/src/models/entities/sw-subscription.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class SwSubscription { @@ -12,7 +19,7 @@ export class SwSubscription { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/used-username.ts b/packages/backend/src/models/entities/used-username.ts index eb90bef6c..a069205a5 100644 --- a/packages/backend/src/models/entities/used-username.ts +++ b/packages/backend/src/models/entities/used-username.ts @@ -1,4 +1,4 @@ -import { PrimaryColumn, Entity, Column } from 'typeorm'; +import { PrimaryColumn, Entity, Column } from "typeorm"; @Entity() export class UsedUsername { diff --git a/packages/backend/src/models/entities/user-group-invitation.ts b/packages/backend/src/models/entities/user-group-invitation.ts index 10f357049..8037b30e1 100644 --- a/packages/backend/src/models/entities/user-group-invitation.ts +++ b/packages/backend/src/models/entities/user-group-invitation.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { UserGroup } from './user-group.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { UserGroup } from "./user-group.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'userGroupId'], { unique: true }) @@ -19,7 +26,7 @@ export class UserGroupInvitation { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -32,7 +39,7 @@ export class UserGroupInvitation { ...id(), comment: 'The group ID.', }) - public userGroupId: UserGroup['id']; + public userGroupId: UserGroup["id"]; @ManyToOne(type => UserGroup, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-group-joining.ts b/packages/backend/src/models/entities/user-group-joining.ts index 62a814218..6d503b274 100644 --- a/packages/backend/src/models/entities/user-group-joining.ts +++ b/packages/backend/src/models/entities/user-group-joining.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { UserGroup } from './user-group.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { UserGroup } from "./user-group.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'userGroupId'], { unique: true }) @@ -19,7 +26,7 @@ export class UserGroupJoining { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -32,7 +39,7 @@ export class UserGroupJoining { ...id(), comment: 'The group ID.', }) - public userGroupId: UserGroup['id']; + public userGroupId: UserGroup["id"]; @ManyToOne(type => UserGroup, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-group.ts b/packages/backend/src/models/entities/user-group.ts index 8d5de1d92..38e5af334 100644 --- a/packages/backend/src/models/entities/user-group.ts +++ b/packages/backend/src/models/entities/user-group.ts @@ -1,6 +1,13 @@ -import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + Entity, + Index, + JoinColumn, + Column, + PrimaryColumn, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class UserGroup { @@ -23,7 +30,7 @@ export class UserGroup { ...id(), comment: 'The ID of owner.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-ip.ts b/packages/backend/src/models/entities/user-ip.ts index 543e9e728..6b88d5221 100644 --- a/packages/backend/src/models/entities/user-ip.ts +++ b/packages/backend/src/models/entities/user-ip.ts @@ -1,7 +1,15 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './note.js'; -import { User } from './user.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, + PrimaryGeneratedColumn, +} from "typeorm"; +import { id } from "../id.js"; +import { Note } from "./note.js"; +import type { User } from "./user.js"; @Entity() @Index(['userId', 'ip'], { unique: true }) @@ -15,7 +23,7 @@ export class UserIp { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/entities/user-keypair.ts b/packages/backend/src/models/entities/user-keypair.ts index 85fa06297..212e742b9 100644 --- a/packages/backend/src/models/entities/user-keypair.ts +++ b/packages/backend/src/models/entities/user-keypair.ts @@ -1,11 +1,11 @@ -import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class UserKeypair { @PrimaryColumn(id()) - public userId: User['id']; + public userId: User["id"]; @OneToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-list-joining.ts b/packages/backend/src/models/entities/user-list-joining.ts index 12f28c414..e52fa7b39 100644 --- a/packages/backend/src/models/entities/user-list-joining.ts +++ b/packages/backend/src/models/entities/user-list-joining.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { UserList } from './user-list.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { UserList } from "./user-list.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'userListId'], { unique: true }) @@ -19,7 +26,7 @@ export class UserListJoining { ...id(), comment: 'The user ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -32,7 +39,7 @@ export class UserListJoining { ...id(), comment: 'The list ID.', }) - public userListId: UserList['id']; + public userListId: UserList["id"]; @ManyToOne(type => UserList, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-list.ts b/packages/backend/src/models/entities/user-list.ts index ca69394e9..7c4345230 100644 --- a/packages/backend/src/models/entities/user-list.ts +++ b/packages/backend/src/models/entities/user-list.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class UserList { @@ -17,7 +24,7 @@ export class UserList { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-note-pining.ts b/packages/backend/src/models/entities/user-note-pining.ts index c91ab7fdd..dc6d61f7e 100644 --- a/packages/backend/src/models/entities/user-note-pining.ts +++ b/packages/backend/src/models/entities/user-note-pining.ts @@ -1,7 +1,14 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { Note } from './note.js'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { Note } from "./note.js"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() @Index(['userId', 'noteId'], { unique: true }) @@ -16,7 +23,7 @@ export class UserNotePining { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -25,7 +32,7 @@ export class UserNotePining { public user: User | null; @Column(id()) - public noteId: Note['id']; + public noteId: Note["id"]; @ManyToOne(type => Note, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-pending.ts b/packages/backend/src/models/entities/user-pending.ts index 763794884..cac85d1c0 100644 --- a/packages/backend/src/models/entities/user-pending.ts +++ b/packages/backend/src/models/entities/user-pending.ts @@ -1,5 +1,5 @@ -import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; -import { id } from '../id.js'; +import { PrimaryColumn, Entity, Index, Column } from "typeorm"; +import { id } from "../id.js"; @Entity() export class UserPending { diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index 3654b0a99..cc3d23867 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -1,15 +1,22 @@ -import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; -import { ffVisibility, notificationTypes } from '@/types.js'; -import { id } from '../id.js'; -import { User } from './user.js'; -import { Page } from './page.js'; +import { + Entity, + Column, + Index, + OneToOne, + JoinColumn, + PrimaryColumn, +} from "typeorm"; +import { ffVisibility, notificationTypes } from "@/types.js"; +import { id } from "../id.js"; +import { User } from "./user.js"; +import { Page } from "./page.js"; // TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも // ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン @Entity() export class UserProfile { @PrimaryColumn(id()) - public userId: User['id']; + public userId: User["id"]; @OneToOne(type => User, { onDelete: 'CASCADE', @@ -176,7 +183,7 @@ export class UserProfile { ...id(), nullable: true, }) - public pinnedPageId: Page['id'] | null; + public pinnedPageId: Page["id"] | null; @OneToOne(type => Page, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/user-publickey.ts b/packages/backend/src/models/entities/user-publickey.ts index 31ed60de8..d1a9239d1 100644 --- a/packages/backend/src/models/entities/user-publickey.ts +++ b/packages/backend/src/models/entities/user-publickey.ts @@ -1,11 +1,18 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + OneToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class UserPublickey { @PrimaryColumn(id()) - public userId: User['id']; + public userId: User["id"]; @OneToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user-security-key.ts b/packages/backend/src/models/entities/user-security-key.ts index c4f2a852e..3b9d925d9 100644 --- a/packages/backend/src/models/entities/user-security-key.ts +++ b/packages/backend/src/models/entities/user-security-key.ts @@ -1,6 +1,13 @@ -import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne, Index } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + JoinColumn, + Column, + ManyToOne, + Index, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; @Entity() export class UserSecurityKey { @@ -11,7 +18,7 @@ export class UserSecurityKey { @Index() @Column(id()) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 3e406bdd7..c57ad916c 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -1,6 +1,13 @@ -import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { DriveFile } from './drive-file.js'; +import { + Entity, + Column, + Index, + OneToOne, + JoinColumn, + PrimaryColumn, +} from "typeorm"; +import { id } from "../id.js"; +import { DriveFile } from "./drive-file.js"; @Entity() @Index(['usernameLower', 'host'], { unique: true }) @@ -92,7 +99,7 @@ export class User { nullable: true, comment: 'The ID of avatar DriveFile.', }) - public avatarId: DriveFile['id'] | null; + public avatarId: DriveFile["id"] | null; @OneToOne(type => DriveFile, { onDelete: 'SET NULL', @@ -105,7 +112,7 @@ export class User { nullable: true, comment: 'The ID of banner DriveFile.', }) - public bannerId: DriveFile['id'] | null; + public bannerId: DriveFile["id"] | null; @OneToOne(type => DriveFile, { onDelete: 'SET NULL', diff --git a/packages/backend/src/models/entities/webhook.ts b/packages/backend/src/models/entities/webhook.ts index 56b411f87..5db51c3a3 100644 --- a/packages/backend/src/models/entities/webhook.ts +++ b/packages/backend/src/models/entities/webhook.ts @@ -1,8 +1,24 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.js'; -import { id } from '../id.js'; +import { + PrimaryColumn, + Entity, + Index, + JoinColumn, + Column, + ManyToOne, +} from "typeorm"; +import { User } from "./user.js"; +import { id } from "../id.js"; -export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const; +export const webhookEventTypes = [ + "mention", + "unfollow", + "follow", + "followed", + "note", + "reply", + "renote", + "reaction", +] as const; @Entity() export class Webhook { @@ -19,7 +35,7 @@ export class Webhook { ...id(), comment: 'The owner ID.', }) - public userId: User['id']; + public userId: User["id"]; @ManyToOne(type => User, { onDelete: 'CASCADE', @@ -37,7 +53,7 @@ export class Webhook { @Column('varchar', { length: 128, array: true, default: '{}', }) - public on: (typeof webhookEventTypes)[number][]; + public on: typeof webhookEventTypes[number][]; @Column('varchar', { length: 1024, diff --git a/packages/backend/src/models/id.ts b/packages/backend/src/models/id.ts index d614fc504..7e5a78798 100644 --- a/packages/backend/src/models/id.ts +++ b/packages/backend/src/models/id.ts @@ -1,4 +1,4 @@ export const id = () => ({ - type: 'varchar' as const, + type: "varchar" as const, length: 32, }); diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 3f7326931..98f6705f4 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -1,130 +1,130 @@ -import { } from 'typeorm'; -import { db } from '@/db/postgre.js'; +import {} from "typeorm"; +import { db } from "@/db/postgre.js"; -import { Announcement } from './entities/announcement.js'; -import { AnnouncementRead } from './entities/announcement-read.js'; -import { Instance } from './entities/instance.js'; -import { Poll } from './entities/poll.js'; -import { PollVote } from './entities/poll-vote.js'; -import { Meta } from './entities/meta.js'; -import { SwSubscription } from './entities/sw-subscription.js'; -import { NoteWatching } from './entities/note-watching.js'; -import { NoteThreadMuting } from './entities/note-thread-muting.js'; -import { NoteUnread } from './entities/note-unread.js'; -import { RegistrationTicket } from './entities/registration-tickets.js'; -import { UserRepository } from './repositories/user.js'; -import { NoteRepository } from './repositories/note.js'; -import { DriveFileRepository } from './repositories/drive-file.js'; -import { DriveFolderRepository } from './repositories/drive-folder.js'; -import { AccessToken } from './entities/access-token.js'; -import { UserNotePining } from './entities/user-note-pining.js'; -import { SigninRepository } from './repositories/signin.js'; -import { MessagingMessageRepository } from './repositories/messaging-message.js'; -import { UserListRepository } from './repositories/user-list.js'; -import { UserListJoining } from './entities/user-list-joining.js'; -import { UserGroupRepository } from './repositories/user-group.js'; -import { UserGroupJoining } from './entities/user-group-joining.js'; -import { UserGroupInvitationRepository } from './repositories/user-group-invitation.js'; -import { FollowRequestRepository } from './repositories/follow-request.js'; -import { MutingRepository } from './repositories/muting.js'; -import { BlockingRepository } from './repositories/blocking.js'; -import { NoteReactionRepository } from './repositories/note-reaction.js'; -import { NotificationRepository } from './repositories/notification.js'; -import { NoteFavoriteRepository } from './repositories/note-favorite.js'; -import { UserPublickey } from './entities/user-publickey.js'; -import { UserKeypair } from './entities/user-keypair.js'; -import { AppRepository } from './repositories/app.js'; -import { FollowingRepository } from './repositories/following.js'; -import { AbuseUserReportRepository } from './repositories/abuse-user-report.js'; -import { AuthSessionRepository } from './repositories/auth-session.js'; -import { UserProfile } from './entities/user-profile.js'; -import { AttestationChallenge } from './entities/attestation-challenge.js'; -import { UserSecurityKey } from './entities/user-security-key.js'; -import { HashtagRepository } from './repositories/hashtag.js'; -import { PageRepository } from './repositories/page.js'; -import { PageLikeRepository } from './repositories/page-like.js'; -import { GalleryPostRepository } from './repositories/gallery-post.js'; -import { GalleryLikeRepository } from './repositories/gallery-like.js'; -import { ModerationLogRepository } from './repositories/moderation-logs.js'; -import { UsedUsername } from './entities/used-username.js'; -import { ClipRepository } from './repositories/clip.js'; -import { ClipNote } from './entities/clip-note.js'; -import { AntennaRepository } from './repositories/antenna.js'; -import { AntennaNote } from './entities/antenna-note.js'; -import { PromoNote } from './entities/promo-note.js'; -import { PromoRead } from './entities/promo-read.js'; -import { EmojiRepository } from './repositories/emoji.js'; -import { RelayRepository } from './repositories/relay.js'; -import { ChannelRepository } from './repositories/channel.js'; -import { MutedNote } from './entities/muted-note.js'; -import { ChannelFollowing } from './entities/channel-following.js'; -import { ChannelNotePining } from './entities/channel-note-pining.js'; -import { RegistryItem } from './entities/registry-item.js'; -import { Ad } from './entities/ad.js'; -import { PasswordResetRequest } from './entities/password-reset-request.js'; -import { UserPending } from './entities/user-pending.js'; -import { InstanceRepository } from './repositories/instance.js'; -import { Webhook } from './entities/webhook.js'; -import { UserIp } from './entities/user-ip.js'; +import { Announcement } from "./entities/announcement.js"; +import { AnnouncementRead } from "./entities/announcement-read.js"; +import { Instance } from "./entities/instance.js"; +import { Poll } from "./entities/poll.js"; +import { PollVote } from "./entities/poll-vote.js"; +import { Meta } from "./entities/meta.js"; +import { SwSubscription } from "./entities/sw-subscription.js"; +import { NoteWatching } from "./entities/note-watching.js"; +import { NoteThreadMuting } from "./entities/note-thread-muting.js"; +import { NoteUnread } from "./entities/note-unread.js"; +import { RegistrationTicket } from "./entities/registration-tickets.js"; +import { UserRepository } from "./repositories/user.js"; +import { NoteRepository } from "./repositories/note.js"; +import { DriveFileRepository } from "./repositories/drive-file.js"; +import { DriveFolderRepository } from "./repositories/drive-folder.js"; +import { AccessToken } from "./entities/access-token.js"; +import { UserNotePining } from "./entities/user-note-pining.js"; +import { SigninRepository } from "./repositories/signin.js"; +import { MessagingMessageRepository } from "./repositories/messaging-message.js"; +import { UserListRepository } from "./repositories/user-list.js"; +import { UserListJoining } from "./entities/user-list-joining.js"; +import { UserGroupRepository } from "./repositories/user-group.js"; +import { UserGroupJoining } from "./entities/user-group-joining.js"; +import { UserGroupInvitationRepository } from "./repositories/user-group-invitation.js"; +import { FollowRequestRepository } from "./repositories/follow-request.js"; +import { MutingRepository } from "./repositories/muting.js"; +import { BlockingRepository } from "./repositories/blocking.js"; +import { NoteReactionRepository } from "./repositories/note-reaction.js"; +import { NotificationRepository } from "./repositories/notification.js"; +import { NoteFavoriteRepository } from "./repositories/note-favorite.js"; +import { UserPublickey } from "./entities/user-publickey.js"; +import { UserKeypair } from "./entities/user-keypair.js"; +import { AppRepository } from "./repositories/app.js"; +import { FollowingRepository } from "./repositories/following.js"; +import { AbuseUserReportRepository } from "./repositories/abuse-user-report.js"; +import { AuthSessionRepository } from "./repositories/auth-session.js"; +import { UserProfile } from "./entities/user-profile.js"; +import { AttestationChallenge } from "./entities/attestation-challenge.js"; +import { UserSecurityKey } from "./entities/user-security-key.js"; +import { HashtagRepository } from "./repositories/hashtag.js"; +import { PageRepository } from "./repositories/page.js"; +import { PageLikeRepository } from "./repositories/page-like.js"; +import { GalleryPostRepository } from "./repositories/gallery-post.js"; +import { GalleryLikeRepository } from "./repositories/gallery-like.js"; +import { ModerationLogRepository } from "./repositories/moderation-logs.js"; +import { UsedUsername } from "./entities/used-username.js"; +import { ClipRepository } from "./repositories/clip.js"; +import { ClipNote } from "./entities/clip-note.js"; +import { AntennaRepository } from "./repositories/antenna.js"; +import { AntennaNote } from "./entities/antenna-note.js"; +import { PromoNote } from "./entities/promo-note.js"; +import { PromoRead } from "./entities/promo-read.js"; +import { EmojiRepository } from "./repositories/emoji.js"; +import { RelayRepository } from "./repositories/relay.js"; +import { ChannelRepository } from "./repositories/channel.js"; +import { MutedNote } from "./entities/muted-note.js"; +import { ChannelFollowing } from "./entities/channel-following.js"; +import { ChannelNotePining } from "./entities/channel-note-pining.js"; +import { RegistryItem } from "./entities/registry-item.js"; +import { Ad } from "./entities/ad.js"; +import { PasswordResetRequest } from "./entities/password-reset-request.js"; +import { UserPending } from "./entities/user-pending.js"; +import { InstanceRepository } from "./repositories/instance.js"; +import { Webhook } from "./entities/webhook.js"; +import { UserIp } from "./entities/user-ip.js"; export const Announcements = db.getRepository(Announcement); export const AnnouncementReads = db.getRepository(AnnouncementRead); -export const Apps = (AppRepository); -export const Notes = (NoteRepository); -export const NoteFavorites = (NoteFavoriteRepository); +export const Apps = AppRepository; +export const Notes = NoteRepository; +export const NoteFavorites = NoteFavoriteRepository; export const NoteWatchings = db.getRepository(NoteWatching); export const NoteThreadMutings = db.getRepository(NoteThreadMuting); -export const NoteReactions = (NoteReactionRepository); +export const NoteReactions = NoteReactionRepository; export const NoteUnreads = db.getRepository(NoteUnread); export const Polls = db.getRepository(Poll); export const PollVotes = db.getRepository(PollVote); -export const Users = (UserRepository); +export const Users = UserRepository; export const UserProfiles = db.getRepository(UserProfile); export const UserKeypairs = db.getRepository(UserKeypair); export const UserPendings = db.getRepository(UserPending); export const AttestationChallenges = db.getRepository(AttestationChallenge); export const UserSecurityKeys = db.getRepository(UserSecurityKey); export const UserPublickeys = db.getRepository(UserPublickey); -export const UserLists = (UserListRepository); +export const UserLists = UserListRepository; export const UserListJoinings = db.getRepository(UserListJoining); -export const UserGroups = (UserGroupRepository); +export const UserGroups = UserGroupRepository; export const UserGroupJoinings = db.getRepository(UserGroupJoining); -export const UserGroupInvitations = (UserGroupInvitationRepository); +export const UserGroupInvitations = UserGroupInvitationRepository; export const UserNotePinings = db.getRepository(UserNotePining); export const UserIps = db.getRepository(UserIp); export const UsedUsernames = db.getRepository(UsedUsername); -export const Followings = (FollowingRepository); -export const FollowRequests = (FollowRequestRepository); -export const Instances = (InstanceRepository); -export const Emojis = (EmojiRepository); -export const DriveFiles = (DriveFileRepository); -export const DriveFolders = (DriveFolderRepository); -export const Notifications = (NotificationRepository); +export const Followings = FollowingRepository; +export const FollowRequests = FollowRequestRepository; +export const Instances = InstanceRepository; +export const Emojis = EmojiRepository; +export const DriveFiles = DriveFileRepository; +export const DriveFolders = DriveFolderRepository; +export const Notifications = NotificationRepository; export const Metas = db.getRepository(Meta); -export const Mutings = (MutingRepository); -export const Blockings = (BlockingRepository); +export const Mutings = MutingRepository; +export const Blockings = BlockingRepository; export const SwSubscriptions = db.getRepository(SwSubscription); -export const Hashtags = (HashtagRepository); -export const AbuseUserReports = (AbuseUserReportRepository); +export const Hashtags = HashtagRepository; +export const AbuseUserReports = AbuseUserReportRepository; export const RegistrationTickets = db.getRepository(RegistrationTicket); -export const AuthSessions = (AuthSessionRepository); +export const AuthSessions = AuthSessionRepository; export const AccessTokens = db.getRepository(AccessToken); -export const Signins = (SigninRepository); -export const MessagingMessages = (MessagingMessageRepository); -export const Pages = (PageRepository); -export const PageLikes = (PageLikeRepository); -export const GalleryPosts = (GalleryPostRepository); -export const GalleryLikes = (GalleryLikeRepository); -export const ModerationLogs = (ModerationLogRepository); -export const Clips = (ClipRepository); +export const Signins = SigninRepository; +export const MessagingMessages = MessagingMessageRepository; +export const Pages = PageRepository; +export const PageLikes = PageLikeRepository; +export const GalleryPosts = GalleryPostRepository; +export const GalleryLikes = GalleryLikeRepository; +export const ModerationLogs = ModerationLogRepository; +export const Clips = ClipRepository; export const ClipNotes = db.getRepository(ClipNote); -export const Antennas = (AntennaRepository); +export const Antennas = AntennaRepository; export const AntennaNotes = db.getRepository(AntennaNote); export const PromoNotes = db.getRepository(PromoNote); export const PromoReads = db.getRepository(PromoRead); -export const Relays = (RelayRepository); +export const Relays = RelayRepository; export const MutedNotes = db.getRepository(MutedNote); -export const Channels = (ChannelRepository); +export const Channels = ChannelRepository; export const ChannelFollowings = db.getRepository(ChannelFollowing); export const ChannelNotePinings = db.getRepository(ChannelNotePining); export const RegistryItems = db.getRepository(RegistryItem); diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 36d7ab90c..07afef48c 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -1,38 +1,39 @@ -import { db } from '@/db/postgre.js'; -import { Users } from '../index.js'; -import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; -import { awaitAll } from '@/prelude/await-all.js'; +import { db } from "@/db/postgre.js"; +import { Users } from "../index.js"; +import { AbuseUserReport } from "@/models/entities/abuse-user-report.js"; +import { awaitAll } from "@/prelude/await-all.js"; -export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({ - async pack( - src: AbuseUserReport['id'] | AbuseUserReport, - ) { - const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); +export const AbuseUserReportRepository = db + .getRepository(AbuseUserReport) + .extend({ + async pack(src: AbuseUserReport["id"] | AbuseUserReport) { + const report = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return await awaitAll({ - id: report.id, - createdAt: report.createdAt.toISOString(), - comment: report.comment, - resolved: report.resolved, - reporterId: report.reporterId, - targetUserId: report.targetUserId, - assigneeId: report.assigneeId, - reporter: Users.pack(report.reporter || report.reporterId, null, { - detail: true, - }), - targetUser: Users.pack(report.targetUser || report.targetUserId, null, { - detail: true, - }), - assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { - detail: true, - }) : null, - forwarded: report.forwarded, - }); - }, + return await awaitAll({ + id: report.id, + createdAt: report.createdAt.toISOString(), + comment: report.comment, + resolved: report.resolved, + reporterId: report.reporterId, + targetUserId: report.targetUserId, + assigneeId: report.assigneeId, + reporter: Users.pack(report.reporter || report.reporterId, null, { + detail: true, + }), + targetUser: Users.pack(report.targetUser || report.targetUserId, null, { + detail: true, + }), + assignee: report.assigneeId + ? Users.pack(report.assignee || report.assigneeId, null, { + detail: true, + }) + : null, + forwarded: report.forwarded, + }); + }, - packMany( - reports: any[], - ) { - return Promise.all(reports.map(x => this.pack(x))); - }, -}); + packMany(reports: any[]) { + return Promise.all(reports.map((x) => this.pack(x))); + }, + }); diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 70180e2de..57ce2fc9e 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -1,16 +1,19 @@ -import { db } from '@/db/postgre.js'; -import { Antenna } from '@/models/entities/antenna.js'; -import { Packed } from '@/misc/schema.js'; -import { AntennaNotes, UserGroupJoinings } from '../index.js'; +import { db } from "@/db/postgre.js"; +import { Antenna } from "@/models/entities/antenna.js"; +import type { Packed } from "@/misc/schema.js"; +import { AntennaNotes, UserGroupJoinings } from "../index.js"; export const AntennaRepository = db.getRepository(Antenna).extend({ - async pack( - src: Antenna['id'] | Antenna, - ): Promise> { - const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: Antenna["id"] | Antenna): Promise> { + const antenna = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null; - const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null; + const hasUnreadNote = + (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != + null; + const userGroupJoining = antenna.userGroupJoiningId + ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) + : null; return { id: antenna.id, diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index e08dd6f0e..af3dfb81a 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -1,26 +1,30 @@ -import { db } from '@/db/postgre.js'; -import { App } from '@/models/entities/app.js'; -import { AccessTokens } from '../index.js'; -import { Packed } from '@/misc/schema.js'; -import { User } from '../entities/user.js'; +import { db } from "@/db/postgre.js"; +import { App } from "@/models/entities/app.js"; +import { AccessTokens } from "../index.js"; +import type { Packed } from "@/misc/schema.js"; +import type { User } from "../entities/user.js"; export const AppRepository = db.getRepository(App).extend({ async pack( - src: App['id'] | App, - me?: { id: User['id'] } | null | undefined, + src: App["id"] | App, + me?: { id: User["id"] } | null | undefined, options?: { - detail?: boolean, - includeSecret?: boolean, - includeProfileImageIds?: boolean - } - ): Promise> { - const opts = Object.assign({ - detail: false, - includeSecret: false, - includeProfileImageIds: false, - }, options); + detail?: boolean; + includeSecret?: boolean; + includeProfileImageIds?: boolean; + }, + ): Promise> { + const opts = Object.assign( + { + detail: false, + includeSecret: false, + includeProfileImageIds: false, + }, + options, + ); - const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const app = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: app.id, @@ -28,12 +32,14 @@ export const AppRepository = db.getRepository(App).extend({ callbackUrl: app.callbackUrl, permission: app.permission, ...(opts.includeSecret ? { secret: app.secret } : {}), - ...(me ? { - isAuthorized: await AccessTokens.countBy({ - appId: app.id, - userId: me.id, - }).then(count => count > 0), - } : {}), + ...(me + ? { + isAuthorized: await AccessTokens.countBy({ + appId: app.id, + userId: me.id, + }).then((count) => count > 0), + } + : {}), }; }, }); diff --git a/packages/backend/src/models/repositories/auth-session.ts b/packages/backend/src/models/repositories/auth-session.ts index 3f1f6f489..d3e1d45d6 100644 --- a/packages/backend/src/models/repositories/auth-session.ts +++ b/packages/backend/src/models/repositories/auth-session.ts @@ -1,15 +1,16 @@ -import { db } from '@/db/postgre.js'; -import { Apps } from '../index.js'; -import { AuthSession } from '@/models/entities/auth-session.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { Apps } from "../index.js"; +import { AuthSession } from "@/models/entities/auth-session.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { User } from "@/models/entities/user.js"; export const AuthSessionRepository = db.getRepository(AuthSession).extend({ async pack( - src: AuthSession['id'] | AuthSession, - me?: { id: User['id'] } | null | undefined + src: AuthSession["id"] | AuthSession, + me?: { id: User["id"] } | null | undefined, ) { - const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const session = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: session.id, diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index 1d569fb87..3dfa74e76 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -1,16 +1,17 @@ -import { db } from '@/db/postgre.js'; -import { Users } from '../index.js'; -import { Blocking } from '@/models/entities/blocking.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { Users } from "../index.js"; +import { Blocking } from "@/models/entities/blocking.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; +import type { User } from "@/models/entities/user.js"; export const BlockingRepository = db.getRepository(Blocking).extend({ async pack( - src: Blocking['id'] | Blocking, - me?: { id: User['id'] } | null | undefined - ): Promise> { - const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + src: Blocking["id"] | Blocking, + me?: { id: User["id"] } | null | undefined, + ): Promise> { + const blocking = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: blocking.id, @@ -22,10 +23,7 @@ export const BlockingRepository = db.getRepository(Blocking).extend({ }); }, - packMany( - blockings: any[], - me: { id: User['id'] } - ) { - return Promise.all(blockings.map(x => this.pack(x, me))); + packMany(blockings: any[], me: { id: User["id"] }) { + return Promise.all(blockings.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 213ac3671..7800a6594 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -1,30 +1,42 @@ -import { db } from '@/db/postgre.js'; -import { Channel } from '@/models/entities/channel.js'; -import { Packed } from '@/misc/schema.js'; -import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { Channel } from "@/models/entities/channel.js"; +import type { Packed } from "@/misc/schema.js"; +import { DriveFiles, ChannelFollowings, NoteUnreads } from "../index.js"; +import type { User } from "@/models/entities/user.js"; export const ChannelRepository = db.getRepository(Channel).extend({ async pack( - src: Channel['id'] | Channel, - me?: { id: User['id'] } | null | undefined, - ): Promise> { - const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + src: Channel["id"] | Channel, + me?: { id: User["id"] } | null | undefined, + ): Promise> { + const channel = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const meId = me ? me.id : null; - const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null; + const banner = channel.bannerId + ? await DriveFiles.findOneBy({ id: channel.bannerId }) + : null; - const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined; + const hasUnreadNote = meId + ? (await NoteUnreads.findOneBy({ + noteChannelId: channel.id, + userId: meId, + })) != null + : undefined; - const following = meId ? await ChannelFollowings.findOneBy({ - followerId: meId, - followeeId: channel.id, - }) : null; + const following = meId + ? await ChannelFollowings.findOneBy({ + followerId: meId, + followeeId: channel.id, + }) + : null; return { id: channel.id, createdAt: channel.createdAt.toISOString(), - lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null, + lastNotedAt: channel.lastNotedAt + ? channel.lastNotedAt.toISOString() + : null, name: channel.name, description: channel.description, userId: channel.userId, @@ -32,10 +44,12 @@ export const ChannelRepository = db.getRepository(Channel).extend({ usersCount: channel.usersCount, notesCount: channel.notesCount, - ...(me ? { - isFollowing: following != null, - hasUnreadNote, - } : {}), + ...(me + ? { + isFollowing: following != null, + hasUnreadNote, + } + : {}), }; }, }); diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index b4a342905..0c21691bf 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -1,14 +1,13 @@ -import { db } from '@/db/postgre.js'; -import { Clip } from '@/models/entities/clip.js'; -import { Packed } from '@/misc/schema.js'; -import { Users } from '../index.js'; -import { awaitAll } from '@/prelude/await-all.js'; +import { db } from "@/db/postgre.js"; +import { Clip } from "@/models/entities/clip.js"; +import type { Packed } from "@/misc/schema.js"; +import { Users } from "../index.js"; +import { awaitAll } from "@/prelude/await-all.js"; export const ClipRepository = db.getRepository(Clip).extend({ - async pack( - src: Clip['id'] | Clip, - ): Promise> { - const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: Clip["id"] | Clip): Promise> { + const clip = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: clip.id, @@ -21,10 +20,7 @@ export const ClipRepository = db.getRepository(Clip).extend({ }); }, - packMany( - clips: Clip[], - ) { - return Promise.all(clips.map(x => this.pack(x))); + packMany(clips: Clip[]) { + return Promise.all(clips.map((x) => this.pack(x))); }, }); - diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index a01fd86c6..3918f7947 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -1,39 +1,41 @@ -import { db } from '@/db/postgre.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { User } from '@/models/entities/user.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { awaitAll, Promiseable } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; -import config from '@/config/index.js'; -import { query, appendQuery } from '@/prelude/url.js'; -import { Meta } from '@/models/entities/meta.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, DriveFolders } from '../index.js'; -import { deepClone } from '@/misc/clone.js'; - +import { db } from "@/db/postgre.js"; +import { DriveFile } from "@/models/entities/drive-file.js"; +import type { User } from "@/models/entities/user.js"; +import { toPuny } from "@/misc/convert-host.js"; +import { awaitAll, Promiseable } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; +import config from "@/config/index.js"; +import { query, appendQuery } from "@/prelude/url.js"; +import { Meta } from "@/models/entities/meta.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, DriveFolders } from "../index.js"; +import { deepClone } from "@/misc/clone.js"; type PackOptions = { - detail?: boolean, - self?: boolean, - withUser?: boolean, + detail?: boolean; + self?: boolean; + withUser?: boolean; }; export const DriveFileRepository = db.getRepository(DriveFile).extend({ validateFileName(name: string): boolean { return ( - (name.trim().length > 0) && - (name.length <= 200) && - (name.indexOf('\\') === -1) && - (name.indexOf('/') === -1) && - (name.indexOf('..') === -1) + name.trim().length > 0 && + name.length <= 200 && + name.indexOf("\\") === -1 && + name.indexOf("/") === -1 && + name.indexOf("..") === -1 ); }, - getPublicProperties(file: DriveFile): DriveFile['properties'] { + getPublicProperties(file: DriveFile): DriveFile["properties"] { if (file.properties.orientation != null) { const properties = deepClone(file.properties); if (file.properties.orientation >= 5) { - [properties.width, properties.height] = [properties.height, properties.width]; + [properties.width, properties.height] = [ + properties.height, + properties.width, + ]; } properties.orientation = undefined; return properties; @@ -44,85 +46,107 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ getPublicUrl(file: DriveFile, thumbnail = false): string | null { // リモートかつメディアプロキシ - if (file.uri != null && file.userHost != null && config.mediaProxy != null) { - return appendQuery(config.mediaProxy, query({ - url: file.uri, - thumbnail: thumbnail ? '1' : undefined, - })); + if ( + file.uri != null && + file.userHost != null && + config.mediaProxy != null + ) { + return appendQuery( + config.mediaProxy, + query({ + url: file.uri, + thumbnail: thumbnail ? "1" : undefined, + }), + ); } // リモートかつ期限切れはローカルプロキシを試みる if (file.uri != null && file.isLink && config.proxyRemoteFiles) { const key = thumbnail ? file.thumbnailAccessKey : file.webpublicAccessKey; - if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 + if (key && !key.match("/")) { + // 古いものはここにオブジェクトストレージキーが入ってるので除外 return `${config.url}/files/${key}`; } } - const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml', 'image/avif'].includes(file.type); + const isImage = + file.type && + [ + "image/png", + "image/apng", + "image/gif", + "image/jpeg", + "image/webp", + "image/svg+xml", + "image/avif", + ].includes(file.type); - return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url); + return thumbnail + ? file.thumbnailUrl || (isImage ? file.webpublicUrl || file.url : null) + : file.webpublicUrl || file.url; }, - async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise { - const id = typeof user === 'object' ? user.id : user; + async calcDriveUsageOf( + user: User["id"] | { id: User["id"] }, + ): Promise { + const id = typeof user === "object" ? user.id : user; - const { sum } = await this - .createQueryBuilder('file') - .where('file.userId = :id', { id: id }) - .andWhere('file.isLink = FALSE') - .select('SUM(file.size)', 'sum') + const { sum } = await this.createQueryBuilder("file") + .where("file.userId = :id", { id: id }) + .andWhere("file.isLink = FALSE") + .select("SUM(file.size)", "sum") .getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfHost(host: string): Promise { - const { sum } = await this - .createQueryBuilder('file') - .where('file.userHost = :host', { host: toPuny(host) }) - .andWhere('file.isLink = FALSE') - .select('SUM(file.size)', 'sum') + const { sum } = await this.createQueryBuilder("file") + .where("file.userHost = :host", { host: toPuny(host) }) + .andWhere("file.isLink = FALSE") + .select("SUM(file.size)", "sum") .getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfLocal(): Promise { - const { sum } = await this - .createQueryBuilder('file') - .where('file.userHost IS NULL') - .andWhere('file.isLink = FALSE') - .select('SUM(file.size)', 'sum') + const { sum } = await this.createQueryBuilder("file") + .where("file.userHost IS NULL") + .andWhere("file.isLink = FALSE") + .select("SUM(file.size)", "sum") .getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfRemote(): Promise { - const { sum } = await this - .createQueryBuilder('file') - .where('file.userHost IS NOT NULL') - .andWhere('file.isLink = FALSE') - .select('SUM(file.size)', 'sum') + const { sum } = await this.createQueryBuilder("file") + .where("file.userHost IS NOT NULL") + .andWhere("file.isLink = FALSE") + .select("SUM(file.size)", "sum") .getRawOne(); return parseInt(sum, 10) || 0; }, async pack( - src: DriveFile['id'] | DriveFile, + src: DriveFile["id"] | DriveFile, options?: PackOptions, - ): Promise> { - const opts = Object.assign({ - detail: false, - self: false, - }, options); + ): Promise> { + const opts = Object.assign( + { + detail: false, + self: false, + }, + options, + ); - const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const file = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return await awaitAll>({ + return await awaitAll>({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, @@ -136,27 +160,34 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ thumbnailUrl: this.getPublicUrl(file, true), comment: file.comment, folderId: file.folderId, - folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { - detail: true, - }) : null, + folder: + opts.detail && file.folderId + ? DriveFolders.pack(file.folderId, { + detail: true, + }) + : null, userId: opts.withUser ? file.userId : null, - user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, + user: opts.withUser && file.userId ? Users.pack(file.userId) : null, }); }, async packNullable( - src: DriveFile['id'] | DriveFile, + src: DriveFile["id"] | DriveFile, options?: PackOptions, - ): Promise | null> { - const opts = Object.assign({ - detail: false, - self: false, - }, options); + ): Promise | null> { + const opts = Object.assign( + { + detail: false, + self: false, + }, + options, + ); - const file = typeof src === 'object' ? src : await this.findOneBy({ id: src }); + const file = + typeof src === "object" ? src : await this.findOneBy({ id: src }); if (file == null) return null; - return await awaitAll>({ + return await awaitAll>({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, @@ -170,19 +201,24 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ thumbnailUrl: this.getPublicUrl(file, true), comment: file.comment, folderId: file.folderId, - folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { - detail: true, - }) : null, + folder: + opts.detail && file.folderId + ? DriveFolders.pack(file.folderId, { + detail: true, + }) + : null, userId: opts.withUser ? file.userId : null, - user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, + user: opts.withUser && file.userId ? Users.pack(file.userId) : null, }); }, async packMany( - files: (DriveFile['id'] | DriveFile)[], + files: (DriveFile["id"] | DriveFile)[], options?: PackOptions, - ): Promise[]> { - const items = await Promise.all(files.map(f => this.packNullable(f, options))); - return items.filter((x): x is Packed<'DriveFile'> => x != null); + ): Promise[]> { + const items = await Promise.all( + files.map((f) => this.packNullable(f, options)), + ); + return items.filter((x): x is Packed<"DriveFile"> => x != null); }, }); diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index ab5f3dab6..9823561d0 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -1,21 +1,25 @@ -import { db } from '@/db/postgre.js'; -import { DriveFolders, DriveFiles } from '../index.js'; -import { DriveFolder } from '@/models/entities/drive-folder.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; +import { db } from "@/db/postgre.js"; +import { DriveFolders, DriveFiles } from "../index.js"; +import { DriveFolder } from "@/models/entities/drive-folder.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; export const DriveFolderRepository = db.getRepository(DriveFolder).extend({ async pack( - src: DriveFolder['id'] | DriveFolder, + src: DriveFolder["id"] | DriveFolder, options?: { - detail: boolean - } - ): Promise> { - const opts = Object.assign({ - detail: false, - }, options); + detail: boolean; + }, + ): Promise> { + const opts = Object.assign( + { + detail: false, + }, + options, + ); - const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const folder = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: folder.id, @@ -23,20 +27,24 @@ export const DriveFolderRepository = db.getRepository(DriveFolder).extend({ name: folder.name, parentId: folder.parentId, - ...(opts.detail ? { - foldersCount: DriveFolders.countBy({ - parentId: folder.id, - }), - filesCount: DriveFiles.countBy({ - folderId: folder.id, - }), + ...(opts.detail + ? { + foldersCount: DriveFolders.countBy({ + parentId: folder.id, + }), + filesCount: DriveFiles.countBy({ + folderId: folder.id, + }), - ...(folder.parentId ? { - parent: this.pack(folder.parentId, { - detail: true, - }), - } : {}), - } : {}), + ...(folder.parentId + ? { + parent: this.pack(folder.parentId, { + detail: true, + }), + } + : {}), + } + : {}), }); }, }); diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index a0d390d79..e868fe94f 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -1,12 +1,11 @@ -import { db } from '@/db/postgre.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { Packed } from '@/misc/schema.js'; +import { db } from "@/db/postgre.js"; +import { Emoji } from "@/models/entities/emoji.js"; +import type { Packed } from "@/misc/schema.js"; export const EmojiRepository = db.getRepository(Emoji).extend({ - async pack( - src: Emoji['id'] | Emoji, - ): Promise> { - const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: Emoji["id"] | Emoji): Promise> { + const emoji = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: emoji.id, @@ -19,9 +18,7 @@ export const EmojiRepository = db.getRepository(Emoji).extend({ }; }, - packMany( - emojis: any[], - ) { - return Promise.all(emojis.map(x => this.pack(x))); + packMany(emojis: any[]) { + return Promise.all(emojis.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/repositories/follow-request.ts b/packages/backend/src/models/repositories/follow-request.ts index c4a7203aa..cef6ea722 100644 --- a/packages/backend/src/models/repositories/follow-request.ts +++ b/packages/backend/src/models/repositories/follow-request.ts @@ -1,14 +1,15 @@ -import { db } from '@/db/postgre.js'; -import { FollowRequest } from '@/models/entities/follow-request.js'; -import { Users } from '../index.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { FollowRequest } from "@/models/entities/follow-request.js"; +import { Users } from "../index.js"; +import type { User } from "@/models/entities/user.js"; export const FollowRequestRepository = db.getRepository(FollowRequest).extend({ async pack( - src: FollowRequest['id'] | FollowRequest, - me?: { id: User['id'] } | null | undefined + src: FollowRequest["id"] | FollowRequest, + me?: { id: User["id"] } | null | undefined, ) { - const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const request = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: request.id, diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index 46109244f..b102365e0 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -1,9 +1,9 @@ -import { db } from '@/db/postgre.js'; -import { Users } from '../index.js'; -import { Following } from '@/models/entities/following.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { Users } from "../index.js"; +import { Following } from "@/models/entities/following.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; +import type { User } from "@/models/entities/user.js"; type LocalFollowerFollowing = Following & { followerHost: null; @@ -47,14 +47,15 @@ export const FollowingRepository = db.getRepository(Following).extend({ }, async pack( - src: Following['id'] | Following, - me?: { id: User['id'] } | null | undefined, + src: Following["id"] | Following, + me?: { id: User["id"] } | null | undefined, opts?: { populateFollowee?: boolean; populateFollower?: boolean; - } - ): Promise> { - const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + }, + ): Promise> { + const following = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); if (opts == null) opts = {}; @@ -63,23 +64,27 @@ export const FollowingRepository = db.getRepository(Following).extend({ createdAt: following.createdAt.toISOString(), followeeId: following.followeeId, followerId: following.followerId, - followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, { - detail: true, - }) : undefined, - follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, { - detail: true, - }) : undefined, + followee: opts.populateFollowee + ? Users.pack(following.followee || following.followeeId, me, { + detail: true, + }) + : undefined, + follower: opts.populateFollower + ? Users.pack(following.follower || following.followerId, me, { + detail: true, + }) + : undefined, }); }, packMany( followings: any[], - me?: { id: User['id'] } | null | undefined, + me?: { id: User["id"] } | null | undefined, opts?: { populateFollowee?: boolean; populateFollower?: boolean; - } + }, ) { - return Promise.all(followings.map(x => this.pack(x, me, opts))); + return Promise.all(followings.map((x) => this.pack(x, me, opts))); }, }); diff --git a/packages/backend/src/models/repositories/gallery-like.ts b/packages/backend/src/models/repositories/gallery-like.ts index 08ca4962b..c8920d1ee 100644 --- a/packages/backend/src/models/repositories/gallery-like.ts +++ b/packages/backend/src/models/repositories/gallery-like.ts @@ -1,13 +1,11 @@ -import { db } from '@/db/postgre.js'; -import { GalleryLike } from '@/models/entities/gallery-like.js'; -import { GalleryPosts } from '../index.js'; +import { db } from "@/db/postgre.js"; +import { GalleryLike } from "@/models/entities/gallery-like.js"; +import { GalleryPosts } from "../index.js"; export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({ - async pack( - src: GalleryLike['id'] | GalleryLike, - me?: any - ) { - const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: GalleryLike["id"] | GalleryLike, me?: any) { + const like = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, @@ -15,10 +13,7 @@ export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({ }; }, - packMany( - likes: any[], - me: any - ) { - return Promise.all(likes.map(x => this.pack(x, me))); + packMany(likes: any[], me: any) { + return Promise.all(likes.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index bb8d40b75..b4206b0bf 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -1,17 +1,18 @@ -import { db } from '@/db/postgre.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; -import { Packed } from '@/misc/schema.js'; -import { Users, DriveFiles, GalleryLikes } from '../index.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { GalleryPost } from "@/models/entities/gallery-post.js"; +import type { Packed } from "@/misc/schema.js"; +import { Users, DriveFiles, GalleryLikes } from "../index.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { User } from "@/models/entities/user.js"; export const GalleryPostRepository = db.getRepository(GalleryPost).extend({ async pack( - src: GalleryPost['id'] | GalleryPost, - me?: { id: User['id'] } | null | undefined, - ): Promise> { + src: GalleryPost["id"] | GalleryPost, + me?: { id: User["id"] } | null | undefined, + ): Promise> { const meId = me ? me.id : null; - const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const post = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: post.id, @@ -26,14 +27,15 @@ export const GalleryPostRepository = db.getRepository(GalleryPost).extend({ tags: post.tags.length > 0 ? post.tags : undefined, isSensitive: post.isSensitive, likedCount: post.likedCount, - isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId + ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then( + (x) => x != null, + ) + : undefined, }); }, - packMany( - posts: GalleryPost[], - me?: { id: User['id'] } | null | undefined, - ) { - return Promise.all(posts.map(x => this.pack(x, me))); + packMany(posts: GalleryPost[], me?: { id: User["id"] } | null | undefined) { + return Promise.all(posts.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index e6c0e36f0..7bd76c1c7 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -1,11 +1,9 @@ -import { db } from '@/db/postgre.js'; -import { Hashtag } from '@/models/entities/hashtag.js'; -import { Packed } from '@/misc/schema.js'; +import { db } from "@/db/postgre.js"; +import { Hashtag } from "@/models/entities/hashtag.js"; +import type { Packed } from "@/misc/schema.js"; export const HashtagRepository = db.getRepository(Hashtag).extend({ - async pack( - src: Hashtag, - ): Promise> { + async pack(src: Hashtag): Promise> { return { tag: src.name, mentionedUsersCount: src.mentionedUsersCount, @@ -17,9 +15,7 @@ export const HashtagRepository = db.getRepository(Hashtag).extend({ }; }, - packMany( - hashtags: Hashtag[], - ) { - return Promise.all(hashtags.map(x => this.pack(x))); + packMany(hashtags: Hashtag[]) { + return Promise.all(hashtags.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 887a24f1e..fb4498911 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,13 +1,11 @@ -import { db } from '@/db/postgre.js'; -import { Instance } from '@/models/entities/instance.js'; -import { Packed } from '@/misc/schema.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +import { db } from "@/db/postgre.js"; +import { Instance } from "@/models/entities/instance.js"; +import type { Packed } from "@/misc/schema.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; export const InstanceRepository = db.getRepository(Instance).extend({ - async pack( - instance: Instance, - ): Promise> { + async pack(instance: Instance): Promise> { const meta = await fetchMeta(); return { id: instance.id, @@ -17,7 +15,9 @@ export const InstanceRepository = db.getRepository(Instance).extend({ notesCount: instance.notesCount, followingCount: instance.followingCount, followersCount: instance.followersCount, - latestRequestSentAt: instance.latestRequestSentAt ? instance.latestRequestSentAt.toISOString() : null, + latestRequestSentAt: instance.latestRequestSentAt + ? instance.latestRequestSentAt.toISOString() + : null, lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(), isNotResponding: instance.isNotResponding, isSuspended: instance.isSuspended, @@ -32,13 +32,13 @@ export const InstanceRepository = db.getRepository(Instance).extend({ iconUrl: instance.iconUrl, faviconUrl: instance.faviconUrl, themeColor: instance.themeColor, - infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null, + infoUpdatedAt: instance.infoUpdatedAt + ? instance.infoUpdatedAt.toISOString() + : null, }; }, - packMany( - instances: Instance[], - ) { - return Promise.all(instances.map(x => this.pack(x))); + packMany(instances: Instance[]) { + return Promise.all(instances.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 6c51c93ff..6c0987bf0 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -1,39 +1,48 @@ -import { db } from '@/db/postgre.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { Users, DriveFiles, UserGroups } from '../index.js'; -import { Packed } from '@/misc/schema.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { Users, DriveFiles, UserGroups } from "../index.js"; +import type { Packed } from "@/misc/schema.js"; +import type { User } from "@/models/entities/user.js"; -export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({ - async pack( - src: MessagingMessage['id'] | MessagingMessage, - me?: { id: User['id'] } | null | undefined, - options?: { - populateRecipient?: boolean, - populateGroup?: boolean, - } - ): Promise> { - const opts = options || { - populateRecipient: true, - populateGroup: true, - }; +export const MessagingMessageRepository = db + .getRepository(MessagingMessage) + .extend({ + async pack( + src: MessagingMessage["id"] | MessagingMessage, + me?: { id: User["id"] } | null | undefined, + options?: { + populateRecipient?: boolean; + populateGroup?: boolean; + }, + ): Promise> { + const opts = options || { + populateRecipient: true, + populateGroup: true, + }; - const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const message = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return { - id: message.id, - createdAt: message.createdAt.toISOString(), - text: message.text, - userId: message.userId, - user: await Users.pack(message.user || message.userId, me), - recipientId: message.recipientId, - recipient: message.recipientId && opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined, - groupId: message.groupId, - group: message.groupId && opts.populateGroup ? await UserGroups.pack(message.group || message.groupId) : undefined, - fileId: message.fileId, - file: message.fileId ? await DriveFiles.pack(message.fileId) : null, - isRead: message.isRead, - reads: message.reads, - }; - }, -}); + return { + id: message.id, + createdAt: message.createdAt.toISOString(), + text: message.text, + userId: message.userId, + user: await Users.pack(message.user || message.userId, me), + recipientId: message.recipientId, + recipient: + message.recipientId && opts.populateRecipient + ? await Users.pack(message.recipient || message.recipientId, me) + : undefined, + groupId: message.groupId, + group: + message.groupId && opts.populateGroup + ? await UserGroups.pack(message.group || message.groupId) + : undefined, + fileId: message.fileId, + file: message.fileId ? await DriveFiles.pack(message.fileId) : null, + isRead: message.isRead, + reads: message.reads, + }; + }, + }); diff --git a/packages/backend/src/models/repositories/moderation-logs.ts b/packages/backend/src/models/repositories/moderation-logs.ts index 1488b1eab..3858b9509 100644 --- a/packages/backend/src/models/repositories/moderation-logs.ts +++ b/packages/backend/src/models/repositories/moderation-logs.ts @@ -1,13 +1,12 @@ -import { db } from '@/db/postgre.js'; -import { Users } from '../index.js'; -import { ModerationLog } from '@/models/entities/moderation-log.js'; -import { awaitAll } from '@/prelude/await-all.js'; +import { db } from "@/db/postgre.js"; +import { Users } from "../index.js"; +import { ModerationLog } from "@/models/entities/moderation-log.js"; +import { awaitAll } from "@/prelude/await-all.js"; export const ModerationLogRepository = db.getRepository(ModerationLog).extend({ - async pack( - src: ModerationLog['id'] | ModerationLog, - ) { - const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: ModerationLog["id"] | ModerationLog) { + const log = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: log.id, @@ -21,9 +20,7 @@ export const ModerationLogRepository = db.getRepository(ModerationLog).extend({ }); }, - packMany( - reports: any[], - ) { - return Promise.all(reports.map(x => this.pack(x))); + packMany(reports: any[]) { + return Promise.all(reports.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index 7891b10fb..4d0201d5a 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -1,16 +1,17 @@ -import { db } from '@/db/postgre.js'; -import { Users } from '../index.js'; -import { Muting } from '@/models/entities/muting.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { Users } from "../index.js"; +import { Muting } from "@/models/entities/muting.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; +import type { User } from "@/models/entities/user.js"; export const MutingRepository = db.getRepository(Muting).extend({ async pack( - src: Muting['id'] | Muting, - me?: { id: User['id'] } | null | undefined - ): Promise> { - const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + src: Muting["id"] | Muting, + me?: { id: User["id"] } | null | undefined, + ): Promise> { + const muting = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: muting.id, @@ -23,10 +24,7 @@ export const MutingRepository = db.getRepository(Muting).extend({ }); }, - packMany( - mutings: any[], - me: { id: User['id'] } - ) { - return Promise.all(mutings.map(x => this.pack(x, me))); + packMany(mutings: any[], me: { id: User["id"] }) { + return Promise.all(mutings.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index 1d5702053..ba43e3c3b 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -1,14 +1,15 @@ -import { db } from '@/db/postgre.js'; -import { NoteFavorite } from '@/models/entities/note-favorite.js'; -import { Notes } from '../index.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { NoteFavorite } from "@/models/entities/note-favorite.js"; +import { Notes } from "../index.js"; +import type { User } from "@/models/entities/user.js"; export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ async pack( - src: NoteFavorite['id'] | NoteFavorite, - me?: { id: User['id'] } | null | undefined + src: NoteFavorite["id"] | NoteFavorite, + me?: { id: User["id"] } | null | undefined, ) { - const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const favorite = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: favorite.id, @@ -19,11 +20,12 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ }; }, - packMany( - favorites: any[], - me: { id: User['id'] } - ) { - return Promise.allSettled(favorites.map(x => this.pack(x, me))) - .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : [])); + packMany(favorites: any[], me: { id: User["id"] }) { + return Promise.allSettled(favorites.map((x) => this.pack(x, me))).then( + (promises) => + promises.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ), + ); }, }); diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index 46084a9a1..6d1dfbd6f 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -1,46 +1,56 @@ -import { db } from '@/db/postgre.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { Notes, Users } from '../index.js'; -import { Packed } from '@/misc/schema.js'; -import { convertLegacyReaction } from '@/misc/reaction-lib.js'; -import { User } from '@/models/entities/user.js'; +import { db } from "@/db/postgre.js"; +import { NoteReaction } from "@/models/entities/note-reaction.js"; +import { Notes, Users } from "../index.js"; +import type { Packed } from "@/misc/schema.js"; +import { convertLegacyReaction } from "@/misc/reaction-lib.js"; +import type { User } from "@/models/entities/user.js"; export const NoteReactionRepository = db.getRepository(NoteReaction).extend({ async pack( - src: NoteReaction['id'] | NoteReaction, - me?: { id: User['id'] } | null | undefined, + src: NoteReaction["id"] | NoteReaction, + me?: { id: User["id"] } | null | undefined, options?: { withNote: boolean; }, - ): Promise> { - const opts = Object.assign({ - withNote: false, - }, options); + ): Promise> { + const opts = Object.assign( + { + withNote: false, + }, + options, + ); - const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const reaction = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: reaction.id, createdAt: reaction.createdAt.toISOString(), user: await Users.pack(reaction.user ?? reaction.userId, me), type: convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - // may throw error - note: await Notes.pack(reaction.note ?? reaction.noteId, me), - } : {}), + ...(opts.withNote + ? { + // may throw error + note: await Notes.pack(reaction.note ?? reaction.noteId, me), + } + : {}), }; }, async packMany( src: NoteReaction[], - me?: { id: User['id'] } | null | undefined, + me?: { id: User["id"] } | null | undefined, options?: { withNote: booleam; }, - ): Promise[]> { - const reactions = await Promise.allSettled(src.map(reaction => this.pack(reaction, me, options))); + ): Promise[]> { + const reactions = await Promise.allSettled( + src.map((reaction) => this.pack(reaction, me, options)), + ); // filter out rejected promises, only keep fulfilled values - return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []); - } + return reactions.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ); + }, }); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index e697b4cea..2bc3b90ca 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -1,20 +1,36 @@ -import { In } from 'typeorm'; -import * as mfm from 'mfm-js'; -import { Note } from '@/models/entities/note.js'; -import { User } from '@/models/entities/user.js'; -import { Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls, Channels } from '../index.js'; -import { Packed } from '@/misc/schema.js'; -import { nyaize } from '@/misc/nyaize.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; -import { db } from '@/db/postgre.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { In } from "typeorm"; +import * as mfm from "mfm-js"; +import { Note } from "@/models/entities/note.js"; +import type { User } from "@/models/entities/user.js"; +import { + Users, + PollVotes, + DriveFiles, + NoteReactions, + Followings, + Polls, + Channels, +} from "../index.js"; +import type { Packed } from "@/misc/schema.js"; +import { nyaize } from "@/misc/nyaize.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import { + convertLegacyReaction, + convertLegacyReactions, + decodeReaction, +} from "@/misc/reaction-lib.js"; +import type { NoteReaction } from "@/models/entities/note-reaction.js"; +import { + aggregateNoteEmojis, + populateEmojis, + prefetchEmojis, +} from "@/misc/populate-emojis.js"; +import { db } from "@/db/postgre.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; -async function populatePoll(note: Note, meId: User['id'] | null) { +async function populatePoll(note: Note, meId: User["id"] | null) { const poll = await Polls.findOneByOrFail({ noteId: note.id }); - const choices = poll.choices.map(c => ({ + const choices = poll.choices.map((c) => ({ text: c, votes: poll.votes[poll.choices.indexOf(c)], isVoted: false, @@ -27,7 +43,7 @@ async function populatePoll(note: Note, meId: User['id'] | null) { noteId: note.id, }); - const myChoices = votes.map(v => v.choice); + const myChoices = votes.map((v) => v.choice); for (const myChoice of myChoices) { choices[myChoice].isVoted = true; } @@ -50,9 +66,13 @@ async function populatePoll(note: Note, meId: User['id'] | null) { }; } -async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { - myReactions: Map; -}) { +async function populateMyReaction( + note: Note, + meId: User["id"], + _hint_?: { + myReactions: Map; + }, +) { if (_hint_?.myReactions) { const reaction = _hint_.myReactions.get(note.id); if (reaction) { @@ -76,10 +96,10 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { } export const NoteRepository = db.getRepository(Note).extend({ - async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { + async isVisibleForMe(note: Note, meId: User["id"] | null): Promise { // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 - if (note.visibility === 'specified') { + if (note.visibility === "specified") { if (meId == null) { return false; } else if (meId === note.userId) { @@ -91,15 +111,15 @@ export const NoteRepository = db.getRepository(Note).extend({ } // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (note.visibility === 'followers') { + if (note.visibility === "followers") { if (meId == null) { return false; } else if (meId === note.userId) { return true; - } else if (note.reply && (meId === note.reply.userId)) { + } else if (note.reply && meId === note.reply.userId) { // 自分の投稿に対するリプライ return true; - } else if (note.mentions && note.mentions.some(id => meId === id)) { + } else if (note.mentions?.some((id) => meId === id)) { // 自分へのメンション return true; } else { @@ -130,31 +150,40 @@ export const NoteRepository = db.getRepository(Note).extend({ }, async pack( - src: Note['id'] | Note, - me?: { id: User['id'] } | null | undefined, + src: Note["id"] | Note, + me?: { id: User["id"] } | null | undefined, options?: { detail?: boolean; _hint_?: { - myReactions: Map; + myReactions: Map; }; - } - ): Promise> { - const opts = Object.assign({ - detail: true, - }, options); + }, + ): Promise> { + const opts = Object.assign( + { + detail: true, + }, + options, + ); const meId = me ? me.id : null; - const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const note = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const host = note.userHost; - if (!await this.isVisibleForMe(note, meId)) { - throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + if (!(await this.isVisibleForMe(note, meId))) { + throw new IdentifiableError( + "9725d0ce-ba28-4dde-95a7-2cbb2c15de24", + "No such note.", + ); } 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 @@ -163,9 +192,12 @@ export const NoteRepository = db.getRepository(Note).extend({ : await Channels.findOneBy({ id: note.channelId }) : null; - const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); + const reactionEmojiNames = Object.keys(note.reactions) + .filter((x) => x?.startsWith(":")) + .map((x) => decodeReaction(x).reaction) + .map((x) => x.replace(/:/g, "")); - const packed: Packed<'Note'> = await awaitAll({ + const packed: Packed<"Note"> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), userId: note.userId, @@ -176,7 +208,8 @@ export const NoteRepository = db.getRepository(Note).extend({ cw: note.cw, visibility: note.visibility, localOnly: note.localOnly || undefined, - visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, + visibleUserIds: + note.visibility === "specified" ? note.visibleUserIds : undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, reactions: convertLegacyReactions(note.reactions), @@ -187,37 +220,47 @@ export const NoteRepository = db.getRepository(Note).extend({ replyId: note.replyId, renoteId: note.renoteId, channelId: note.channelId || undefined, - channel: channel ? { - id: channel.id, - name: channel.name, - } : undefined, + channel: channel + ? { + id: channel.id, + name: channel.name, + } + : undefined, mentions: note.mentions.length > 0 ? note.mentions : undefined, uri: note.uri || undefined, url: note.url || undefined, - ...(opts.detail ? { - reply: note.replyId ? this.pack(note.reply || note.replyId, me, { - detail: false, - _hint_: options?._hint_, - }) : undefined, + ...(opts.detail + ? { + reply: note.replyId + ? this.pack(note.reply || note.replyId, me, { + detail: false, + _hint_: options?._hint_, + }) + : undefined, - renote: note.renoteId ? this.pack(note.renote || note.renoteId, me, { - detail: true, - _hint_: options?._hint_, - }) : undefined, + renote: note.renoteId + ? this.pack(note.renote || note.renoteId, me, { + detail: true, + _hint_: options?._hint_, + }) + : undefined, - poll: note.hasPoll ? populatePoll(note, meId) : undefined, + poll: note.hasPoll ? populatePoll(note, meId) : undefined, - ...(meId ? { - myReaction: populateMyReaction(note, meId, options?._hint_), - } : {}), - } : {}), + ...(meId + ? { + myReaction: populateMyReaction(note, meId, options?._hint_), + } + : {}), + } + : {}), }); if (packed.user.isCat && packed.text) { const tokens = packed.text ? mfm.parse(packed.text) : []; - mfm.inspect(tokens, node => { - if (node.type === 'text') { + mfm.inspect(tokens, (node) => { + if (node.type === "text") { // TODO: quoteなtextはskip node.props.text = nyaize(node.props.text); } @@ -230,38 +273,49 @@ export const NoteRepository = db.getRepository(Note).extend({ async packMany( notes: Note[], - me?: { id: User['id'] } | null | undefined, + me?: { id: User["id"] } | null | undefined, options?: { detail?: boolean; - } + }, ) { if (notes.length === 0) return []; const meId = me ? me.id : null; - const myReactionsMap = new Map(); + const myReactionsMap = new Map(); if (meId) { - const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); - const targets = [...notes.map(n => n.id), ...renoteIds]; + const renoteIds = notes + .filter((n) => n.renoteId != null) + .map((n) => n.renoteId!); + const targets = [...notes.map((n) => n.id), ...renoteIds]; const myReactions = await NoteReactions.findBy({ userId: meId, noteId: In(targets), }); for (const target of targets) { - myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) || null); + myReactionsMap.set( + target, + myReactions.find((reaction) => reaction.noteId === target) || null, + ); } } await prefetchEmojis(aggregateNoteEmojis(notes)); - const promises = await Promise.allSettled(notes.map(n => this.pack(n, me, { - ...options, - _hint_: { - myReactions: myReactionsMap, - }, - }))); + const promises = await Promise.allSettled( + notes.map((n) => + this.pack(n, me, { + ...options, + _hint_: { + myReactions: myReactionsMap, + }, + }), + ), + ); // filter out rejected promises, only keep fulfilled values - return promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []); + return promises.flatMap((result) => + result.status === "fulfilled" ? [result.value] : [], + ); }, }); diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index efa3e860b..1538e67d8 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -1,26 +1,37 @@ -import { In, Repository } from 'typeorm'; -import { Notification } from '@/models/entities/notification.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import type { Packed } from '@/misc/schema.js'; -import type { Note } from '@/models/entities/note.js'; -import type { NoteReaction } from '@/models/entities/note-reaction.js'; -import type { User } from '@/models/entities/user.js'; -import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; -import { notificationTypes } from '@/types.js'; -import { db } from '@/db/postgre.js'; -import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; +import { In, Repository } from "typeorm"; +import { Notification } from "@/models/entities/notification.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; +import type { Note } from "@/models/entities/note.js"; +import type { NoteReaction } from "@/models/entities/note-reaction.js"; +import type { User } from "@/models/entities/user.js"; +import { aggregateNoteEmojis, prefetchEmojis } from "@/misc/populate-emojis.js"; +import { notificationTypes } from "@/types.js"; +import { db } from "@/db/postgre.js"; +import { + Users, + Notes, + UserGroupInvitations, + AccessTokens, + NoteReactions, +} from "../index.js"; export const NotificationRepository = db.getRepository(Notification).extend({ async pack( - src: Notification['id'] | Notification, + src: Notification["id"] | Notification, options: { _hintForEachNotes_?: { - myReactions: Map; + myReactions: Map; }; }, - ): Promise> { - const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null; + ): Promise> { + const notification = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); + const token = notification.appAccessTokenId + ? await AccessTokens.findOneByOrFail({ + id: notification.appAccessTokenId, + }) + : null; return await awaitAll({ id: notification.id, @@ -28,72 +39,123 @@ export const NotificationRepository = db.getRepository(Notification).extend({ type: notification.type, isRead: notification.isRead, userId: notification.notifierId, - user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null, - ...(notification.type === 'mention' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - } : {}), - ...(notification.type === 'reply' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - } : {}), - ...(notification.type === 'renote' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - } : {}), - ...(notification.type === 'quote' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - } : {}), - ...(notification.type === 'reaction' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - reaction: notification.reaction, - } : {}), - ...(notification.type === 'pollVote' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - choice: notification.choice, - } : {}), - ...(notification.type === 'pollEnded' ? { - note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, { - detail: true, - _hint_: options._hintForEachNotes_, - }), - } : {}), - ...(notification.type === 'groupInvited' ? { - invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!), - } : {}), - ...(notification.type === 'app' ? { - body: notification.customBody, - header: notification.customHeader || token?.name, - icon: notification.customIcon || token?.iconUrl, - } : {}), + user: notification.notifierId + ? Users.pack(notification.notifier || notification.notifierId) + : null, + ...(notification.type === "mention" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + } + : {}), + ...(notification.type === "reply" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + } + : {}), + ...(notification.type === "renote" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + } + : {}), + ...(notification.type === "quote" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + } + : {}), + ...(notification.type === "reaction" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + reaction: notification.reaction, + } + : {}), + ...(notification.type === "pollVote" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + choice: notification.choice, + } + : {}), + ...(notification.type === "pollEnded" + ? { + note: Notes.pack( + notification.note || notification.noteId!, + { id: notification.notifieeId }, + { + detail: true, + _hint_: options._hintForEachNotes_, + }, + ), + } + : {}), + ...(notification.type === "groupInvited" + ? { + invitation: UserGroupInvitations.pack( + notification.userGroupInvitationId!, + ), + } + : {}), + ...(notification.type === "app" + ? { + body: notification.customBody, + header: notification.customHeader || token?.name, + icon: notification.customIcon || token?.iconUrl, + } + : {}), }); }, - async packMany( - notifications: Notification[], - meId: User['id'], - ) { + async packMany(notifications: Notification[], meId: User["id"]) { if (notifications.length === 0) return []; - const notes = notifications.filter(x => x.note != null).map(x => x.note!); - const noteIds = notes.map(n => n.id); - const myReactionsMap = new Map(); - const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); + const notes = notifications + .filter((x) => x.note != null) + .map((x) => x.note!); + const noteIds = notes.map((n) => n.id); + const myReactionsMap = new Map(); + const renoteIds = notes + .filter((n) => n.renoteId != null) + .map((n) => n.renoteId!); const targets = [...noteIds, ...renoteIds]; const myReactions = await NoteReactions.findBy({ userId: meId, @@ -101,20 +163,23 @@ export const NotificationRepository = db.getRepository(Notification).extend({ }); for (const target of targets) { - myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) || null); + myReactionsMap.set( + target, + myReactions.find((reaction) => reaction.noteId === target) || null, + ); } await prefetchEmojis(aggregateNoteEmojis(notes)); - const results = await Promise.all(notifications - .map(x => + const results = await Promise.all( + notifications.map((x) => this.pack(x, { _hintForEachNotes_: { myReactions: myReactionsMap, }, - }).catch(e => null), + }).catch((e) => null), ), ); - return results.filter(x => x != null); + return results.filter((x) => x != null); }, }); diff --git a/packages/backend/src/models/repositories/page-like.ts b/packages/backend/src/models/repositories/page-like.ts index 3f259f981..f78ef81b0 100644 --- a/packages/backend/src/models/repositories/page-like.ts +++ b/packages/backend/src/models/repositories/page-like.ts @@ -1,14 +1,15 @@ -import { db } from '@/db/postgre.js'; -import { PageLike } from '@/models/entities/page-like.js'; -import type { User } from '@/models/entities/user.js'; -import { Pages } from '../index.js'; +import { db } from "@/db/postgre.js"; +import { PageLike } from "@/models/entities/page-like.js"; +import type { User } from "@/models/entities/user.js"; +import { Pages } from "../index.js"; export const PageLikeRepository = db.getRepository(PageLike).extend({ async pack( - src: PageLike['id'] | PageLike, - me?: { id: User['id'] } | null | undefined, + src: PageLike["id"] | PageLike, + me?: { id: User["id"] } | null | undefined, ) { - const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const like = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return { id: like.id, @@ -16,10 +17,7 @@ export const PageLikeRepository = db.getRepository(PageLike).extend({ }; }, - packMany( - likes: PageLike[], - me: { id: User['id'] }, - ) { - return Promise.all(likes.map(x => this.pack(x, me))); + packMany(likes: PageLike[], me: { id: User["id"] }) { + return Promise.all(likes.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 1a8bc50e2..d9241c362 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -1,27 +1,30 @@ -import { db } from '@/db/postgre.js'; -import { Page } from '@/models/entities/page.js'; -import type { Packed } from '@/misc/schema.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import type { DriveFile } from '@/models/entities/drive-file.js'; -import type { User } from '@/models/entities/user.js'; -import { Users, DriveFiles, PageLikes } from '../index.js'; +import { db } from "@/db/postgre.js"; +import { Page } from "@/models/entities/page.js"; +import type { Packed } from "@/misc/schema.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { User } from "@/models/entities/user.js"; +import { Users, DriveFiles, PageLikes } from "../index.js"; export const PageRepository = db.getRepository(Page).extend({ async pack( - src: Page['id'] | Page, - me?: { id: User['id'] } | null | undefined, - ): Promise> { + src: Page["id"] | Page, + me?: { id: User["id"] } | null | undefined, + ): Promise> { const meId = me ? me.id : null; - const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + const page = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const attachedFiles: Promise[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { - if (x.type === 'image') { - attachedFiles.push(DriveFiles.findOneBy({ - id: x.fileId, - userId: page.userId, - })); + if (x.type === "image") { + attachedFiles.push( + DriveFiles.findOneBy({ + id: x.fileId, + userId: page.userId, + }), + ); } if (x.children) { collectFile(x.children); @@ -34,12 +37,12 @@ export const PageRepository = db.getRepository(Page).extend({ let migrated = false; const migrate = (xs: any[]) => { for (const x of xs) { - if (x.type === 'input') { - if (x.inputType === 'text') { - x.type = 'textInput'; + if (x.type === "input") { + if (x.inputType === "text") { + x.type = "textInput"; } - if (x.inputType === 'number') { - x.type = 'numberInput'; + if (x.inputType === "number") { + x.type = "numberInput"; if (x.default) x.default = parseInt(x.default, 10); } migrated = true; @@ -73,17 +76,24 @@ export const PageRepository = db.getRepository(Page).extend({ font: page.font, script: page.script, eyeCatchingImageId: page.eyeCatchingImageId, - eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, - attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)), + eyeCatchingImage: page.eyeCatchingImageId + ? await DriveFiles.pack(page.eyeCatchingImageId) + : null, + attachedFiles: DriveFiles.packMany( + ( + await Promise.all(attachedFiles) + ).filter((x): x is DriveFile => x != null), + ), likedCount: page.likedCount, - isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId + ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then( + (x) => x != null, + ) + : undefined, }); }, - packMany( - pages: Page[], - me?: { id: User['id'] } | null | undefined, - ) { - return Promise.all(pages.map(x => this.pack(x, me))); + packMany(pages: Page[], me?: { id: User["id"] } | null | undefined) { + return Promise.all(pages.map((x) => this.pack(x, me))); }, }); diff --git a/packages/backend/src/models/repositories/relay.ts b/packages/backend/src/models/repositories/relay.ts index fa1c8f4d8..633861496 100644 --- a/packages/backend/src/models/repositories/relay.ts +++ b/packages/backend/src/models/repositories/relay.ts @@ -1,5 +1,4 @@ -import { db } from '@/db/postgre.js'; -import { Relay } from '@/models/entities/relay.js'; +import { db } from "@/db/postgre.js"; +import { Relay } from "@/models/entities/relay.js"; -export const RelayRepository = db.getRepository(Relay).extend({ -}); +export const RelayRepository = db.getRepository(Relay).extend({}); diff --git a/packages/backend/src/models/repositories/signin.ts b/packages/backend/src/models/repositories/signin.ts index 94410ec58..06cf2c210 100644 --- a/packages/backend/src/models/repositories/signin.ts +++ b/packages/backend/src/models/repositories/signin.ts @@ -1,10 +1,8 @@ -import { db } from '@/db/postgre.js'; -import { Signin } from '@/models/entities/signin.js'; +import { db } from "@/db/postgre.js"; +import { Signin } from "@/models/entities/signin.js"; export const SigninRepository = db.getRepository(Signin).extend({ - async pack( - src: Signin, - ) { + async pack(src: Signin) { return src; }, }); diff --git a/packages/backend/src/models/repositories/user-group-invitation.ts b/packages/backend/src/models/repositories/user-group-invitation.ts index 79ad019c9..920fb9ba2 100644 --- a/packages/backend/src/models/repositories/user-group-invitation.ts +++ b/packages/backend/src/models/repositories/user-group-invitation.ts @@ -1,22 +1,23 @@ -import { db } from '@/db/postgre.js'; -import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; -import { UserGroups } from '../index.js'; +import { db } from "@/db/postgre.js"; +import { UserGroupInvitation } from "@/models/entities/user-group-invitation.js"; +import { UserGroups } from "../index.js"; -export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({ - async pack( - src: UserGroupInvitation['id'] | UserGroupInvitation, - ) { - const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); +export const UserGroupInvitationRepository = db + .getRepository(UserGroupInvitation) + .extend({ + async pack(src: UserGroupInvitation["id"] | UserGroupInvitation) { + const invitation = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return { - id: invitation.id, - group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId), - }; - }, + return { + id: invitation.id, + group: await UserGroups.pack( + invitation.userGroup || invitation.userGroupId, + ), + }; + }, - packMany( - invitations: any[], - ) { - return Promise.all(invitations.map(x => this.pack(x))); - }, -}); + packMany(invitations: any[]) { + return Promise.all(invitations.map((x) => this.pack(x))); + }, + }); diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index 6eb923424..daec94490 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -1,13 +1,12 @@ -import { db } from '@/db/postgre.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { UserGroupJoinings } from '../index.js'; -import { Packed } from '@/misc/schema.js'; +import { db } from "@/db/postgre.js"; +import { UserGroup } from "@/models/entities/user-group.js"; +import { UserGroupJoinings } from "../index.js"; +import type { Packed } from "@/misc/schema.js"; export const UserGroupRepository = db.getRepository(UserGroup).extend({ - async pack( - src: UserGroup['id'] | UserGroup, - ): Promise> { - const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: UserGroup["id"] | UserGroup): Promise> { + const userGroup = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const users = await UserGroupJoinings.findBy({ userGroupId: userGroup.id, @@ -18,7 +17,7 @@ export const UserGroupRepository = db.getRepository(UserGroup).extend({ createdAt: userGroup.createdAt.toISOString(), name: userGroup.name, ownerId: userGroup.userId, - userIds: users.map(x => x.userId), + userIds: users.map((x) => x.userId), }; }, }); diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 2b6f411ef..e3abeac3f 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -1,13 +1,12 @@ -import { db } from '@/db/postgre.js'; -import { UserList } from '@/models/entities/user-list.js'; -import { UserListJoinings } from '../index.js'; -import { Packed } from '@/misc/schema.js'; +import { db } from "@/db/postgre.js"; +import { UserList } from "@/models/entities/user-list.js"; +import { UserListJoinings } from "../index.js"; +import type { Packed } from "@/misc/schema.js"; export const UserListRepository = db.getRepository(UserList).extend({ - async pack( - src: UserList['id'] | UserList, - ): Promise> { - const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + async pack(src: UserList["id"] | UserList): Promise> { + const userList = + typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); const users = await UserListJoinings.findBy({ userListId: userList.id, @@ -17,7 +16,7 @@ export const UserListRepository = db.getRepository(UserList).extend({ id: userList.id, createdAt: userList.createdAt.toISOString(), name: userList.name, - userIds: users.map(x => x.userId), + userIds: users.map((x) => x.userId), }; }, }); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 138929c4b..aa224b667 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -1,21 +1,21 @@ -import { URL } from 'url'; -import { In, Not } from 'typeorm'; -import Ajv from 'ajv'; -import type { ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { User } from '@/models/entities/user.js'; -import config from '@/config/index.js'; -import type { Packed } from '@/misc/schema.js'; -import type { Promiseable } from '@/prelude/await-all.js'; -import { awaitAll } from '@/prelude/await-all.js'; -import { populateEmojis } from '@/misc/populate-emojis.js'; -import { getAntennas } from '@/misc/antenna-cache.js'; -import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; -import { Cache } from '@/misc/cache.js'; -import { db } from '@/db/postgre.js'; -import { isActor, getApId } from '@/remote/activitypub/type.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { createPerson } from '@/remote/activitypub/models/person.js'; +import { URL } from "url"; +import { In, Not } from "typeorm"; +import Ajv from "ajv"; +import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import config from "@/config/index.js"; +import type { Packed } from "@/misc/schema.js"; +import type { Promiseable } from "@/prelude/await-all.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import { populateEmojis } from "@/misc/populate-emojis.js"; +import { getAntennas } from "@/misc/antenna-cache.js"; +import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "@/const.js"; +import { Cache } from "@/misc/cache.js"; +import { db } from "@/db/postgre.js"; +import { isActor, getApId } from "@/remote/activitypub/type.js"; +import DbResolver from "@/remote/activitypub/db-resolver.js"; +import Resolver from "@/remote/activitypub/resolver.js"; +import { createPerson } from "@/remote/activitypub/models/person.js"; import { AnnouncementReads, Announcements, @@ -36,49 +36,69 @@ import { UserNotePinings, UserProfiles, UserSecurityKeys, -} from '../index.js'; -import type { Instance } from '../entities/instance.js'; +} from "../index.js"; +import type { Instance } from "../entities/instance.js"; const userInstanceCache = new Cache(1000 * 60 * 60 * 3); -type IsUserDetailed = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; -type IsMeAndIsUserDetailed = - Detailed extends true ? - ExpectsMe extends true ? Packed<'MeDetailed'> : - ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : - Packed<'UserDetailed'> : - Packed<'UserLite'>; +type IsUserDetailed = Detailed extends true + ? Packed<"UserDetailed"> + : Packed<"UserLite">; +type IsMeAndIsUserDetailed< + ExpectsMe extends boolean | null, + Detailed extends boolean, +> = Detailed extends true + ? ExpectsMe extends true + ? Packed<"MeDetailed"> + : ExpectsMe extends false + ? Packed<"UserDetailedNotMe"> + : Packed<"UserDetailed"> + : Packed<"UserLite">; const ajv = new Ajv(); -const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; -const passwordSchema = { type: 'string', minLength: 1 } as const; -const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; -const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; -const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; -const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; +const localUsernameSchema = { + type: "string", + pattern: /^\w{1,20}$/.toString().slice(1, -1), +} as const; +const passwordSchema = { type: "string", minLength: 1 } as const; +const nameSchema = { type: "string", minLength: 1, maxLength: 50 } as const; +const descriptionSchema = { + type: "string", + minLength: 1, + maxLength: 500, +} as const; +const locationSchema = { type: "string", minLength: 1, maxLength: 50 } as const; +const birthdaySchema = { + type: "string", + pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1), +} as const; function isLocalUser(user: User): user is ILocalUser; -function isLocalUser(user: T): user is T & { host: null; }; +function isLocalUser( + user: T, +): user is T & { host: null }; /** * Returns true if the user is local. * * @param user The user to check. * @returns True if the user is local. */ -function isLocalUser(user: User | { host: User['host'] }): boolean { +function isLocalUser(user: User | { host: User["host"] }): boolean { return user.host == null; } function isRemoteUser(user: User): user is IRemoteUser; -function isRemoteUser(user: T): user is T & { host: string; }; +function isRemoteUser( + user: T, +): user is T & { host: string }; /** * Returns true if the user is remote. * * @param user The user to check. * @returns True if the user is remote. */ -function isRemoteUser(user: User | { host: User['host'] }): boolean { +function isRemoteUser(user: User | { host: User["host"] }): boolean { return !isLocalUser(user); } @@ -99,7 +119,7 @@ export const UserRepository = db.getRepository(User).extend({ validateBirthday: ajv.compile(birthdaySchema), //#endregion - async getRelation(me: User['id'], target: User['id']) { + async getRelation(me: User["id"], target: User["id"]) { return awaitAll({ id: target, isFollowing: Followings.count({ @@ -108,89 +128,100 @@ export const UserRepository = db.getRepository(User).extend({ followeeId: target, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), isFollowed: Followings.count({ where: { followerId: target, followeeId: me, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), hasPendingFollowRequestFromYou: FollowRequests.count({ where: { followerId: me, followeeId: target, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), hasPendingFollowRequestToYou: FollowRequests.count({ where: { followerId: target, followeeId: me, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), isBlocking: Blockings.count({ where: { blockerId: me, blockeeId: target, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), isBlocked: Blockings.count({ where: { blockerId: target, blockeeId: me, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), isMuted: Mutings.count({ where: { muterId: me, muteeId: target, }, take: 1, - }).then(n => n > 0), + }).then((n) => n > 0), }); }, - async getHasUnreadMessagingMessage(userId: User['id']): Promise { + async getHasUnreadMessagingMessage(userId: User["id"]): Promise { const mute = await Mutings.findBy({ muterId: userId, }); const joinings = await UserGroupJoinings.findBy({ userId: userId }); - const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message') - .where('message.groupId = :groupId', { groupId: j.userGroupId }) - .andWhere('message.userId != :userId', { userId: userId }) - .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) - .andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない - .getOne().then(x => x != null))); + const groupQs = Promise.all( + joinings.map((j) => + MessagingMessages.createQueryBuilder("message") + .where("message.groupId = :groupId", { groupId: j.userGroupId }) + .andWhere("message.userId != :userId", { userId: userId }) + .andWhere("NOT (:userId = ANY(message.reads))", { userId: userId }) + .andWhere("message.createdAt > :joinedAt", { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない + .getOne() + .then((x) => x != null), + ), + ); const [withUser, withGroups] = await Promise.all([ MessagingMessages.count({ where: { recipientId: userId, isRead: false, - ...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}), + ...(mute.length > 0 + ? { userId: Not(In(mute.map((x) => x.muteeId))) } + : {}), }, take: 1, - }).then(count => count > 0), + }).then((count) => count > 0), groupQs, ]); - return withUser || withGroups.some(x => x); + return withUser || withGroups.some((x) => x); }, - async getHasUnreadAnnouncement(userId: User['id']): Promise { + async getHasUnreadAnnouncement(userId: User["id"]): Promise { const reads = await AnnouncementReads.findBy({ userId: userId, }); - const count = await Announcements.countBy(reads.length > 0 ? { - id: Not(In(reads.map(read => read.announcementId))), - } : {}); + const count = await Announcements.countBy( + reads.length > 0 + ? { + id: Not(In(reads.map((read) => read.announcementId))), + } + : {}, + ); return count > 0; }, @@ -199,12 +230,12 @@ export const UserRepository = db.getRepository(User).extend({ const dbResolver = new DbResolver(); let local = await dbResolver.getUserFromApId(uri); if (local) { - return local; - } + return local; + } // fetching Object once from remote const resolver = new Resolver(); - const object = await resolver.resolve(uri) as any; + const object = (await resolver.resolve(uri)) as any; // /@user If a URI other than the id is specified, // the URI is determined here @@ -216,38 +247,46 @@ export const UserRepository = db.getRepository(User).extend({ return isActor(object) ? await createPerson(getApId(object)) : null; }, - async getHasUnreadAntenna(userId: User['id']): Promise { - const myAntennas = (await getAntennas()).filter(a => a.userId === userId); + async getHasUnreadAntenna(userId: User["id"]): Promise { + const myAntennas = (await getAntennas()).filter((a) => a.userId === userId); - const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({ - antennaId: In(myAntennas.map(x => x.id)), - read: false, - }) : null; + const unread = + myAntennas.length > 0 + ? await AntennaNotes.findOneBy({ + antennaId: In(myAntennas.map((x) => x.id)), + read: false, + }) + : null; return unread != null; }, - async getHasUnreadChannel(userId: User['id']): Promise { + async getHasUnreadChannel(userId: User["id"]): Promise { const channels = await ChannelFollowings.findBy({ followerId: userId }); - const unread = channels.length > 0 ? await NoteUnreads.findOneBy({ - userId: userId, - noteChannelId: In(channels.map(x => x.followeeId)), - }) : null; + const unread = + channels.length > 0 + ? await NoteUnreads.findOneBy({ + userId: userId, + noteChannelId: In(channels.map((x) => x.followeeId)), + }) + : null; return unread != null; }, - async getHasUnreadNotification(userId: User['id']): Promise { + async getHasUnreadNotification(userId: User["id"]): Promise { const mute = await Mutings.findBy({ muterId: userId, }); - const mutedUserIds = mute.map(m => m.muteeId); + const mutedUserIds = mute.map((m) => m.muteeId); const count = await Notifications.count({ where: { notifieeId: userId, - ...(mutedUserIds.length > 0 ? { notifierId: Not(In(mutedUserIds)) } : {}), + ...(mutedUserIds.length > 0 + ? { notifierId: Not(In(mutedUserIds)) } + : {}), isRead: false, }, take: 1, @@ -256,7 +295,9 @@ export const UserRepository = db.getRepository(User).extend({ return count > 0; }, - async getHasPendingReceivedFollowRequest(userId: User['id']): Promise { + async getHasPendingReceivedFollowRequest( + userId: User["id"], + ): Promise { const count = await FollowRequests.countBy({ followeeId: userId, }); @@ -264,23 +305,28 @@ export const UserRepository = db.getRepository(User).extend({ return count > 0; }, - getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { - if (user.hideOnlineStatus) return 'unknown'; - if (user.lastActiveDate == null) return 'unknown'; + getOnlineStatus(user: User): "unknown" | "online" | "active" | "offline" { + if (user.hideOnlineStatus) return "unknown"; + if (user.lastActiveDate == null) return "unknown"; const elapsed = Date.now() - user.lastActiveDate.getTime(); - return ( - elapsed < USER_ONLINE_THRESHOLD ? 'online' : - elapsed < USER_ACTIVE_THRESHOLD ? 'active' : - 'offline' - ); + return elapsed < USER_ONLINE_THRESHOLD + ? "online" + : elapsed < USER_ACTIVE_THRESHOLD + ? "active" + : "offline"; }, async getAvatarUrl(user: User): Promise { if (user.avatar) { - return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id); + return ( + DriveFiles.getPublicUrl(user.avatar, true) || + this.getIdenticonUrl(user.id) + ); } else if (user.avatarId) { const avatar = await DriveFiles.findOneByOrFail({ id: user.avatarId }); - return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id); + return ( + DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id) + ); } else { return this.getIdenticonUrl(user.id); } @@ -288,35 +334,46 @@ export const UserRepository = db.getRepository(User).extend({ getAvatarUrlSync(user: User): string { if (user.avatar) { - return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id); + return ( + DriveFiles.getPublicUrl(user.avatar, true) || + this.getIdenticonUrl(user.id) + ); } else { return this.getIdenticonUrl(user.id); } }, - getIdenticonUrl(userId: User['id']): string { + getIdenticonUrl(userId: User["id"]): string { return `${config.url}/identicon/${userId}`; }, - async pack( - src: User['id'] | User, - me?: { id: User['id'] } | null | undefined, + async pack< + ExpectsMe extends boolean | null = null, + D extends boolean = false, + >( + src: User["id"] | User, + me?: { id: User["id"] } | null | undefined, options?: { - detail?: D, - includeSecrets?: boolean, + detail?: D; + includeSecrets?: boolean; }, ): Promise> { - const opts = Object.assign({ - detail: false, - includeSecrets: false, - }, options); + const opts = Object.assign( + { + detail: false, + includeSecrets: false, + }, + options, + ); let user: User; - if (typeof src === 'object') { + if (typeof src === "object") { user = src; - if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null; - if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null; + if (src.avatar === undefined && src.avatarId) + src.avatar = (await DriveFiles.findOneBy({ id: src.avatarId })) ?? null; + if (src.banner === undefined && src.bannerId) + src.banner = (await DriveFiles.findOneBy({ id: src.bannerId })) ?? null; } else { user = await this.findOneOrFail({ where: { id: src }, @@ -330,23 +387,42 @@ export const UserRepository = db.getRepository(User).extend({ const meId = me ? me.id : null; const isMe = meId === user.id; - const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; - const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin') - .where('pin.userId = :userId', { userId: user.id }) - .innerJoinAndSelect('pin.note', 'note') - .orderBy('pin.id', 'DESC') - .getMany() : []; - const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null; + const relation = + meId && !isMe && opts.detail + ? await this.getRelation(meId, user.id) + : null; + const pins = opts.detail + ? await UserNotePinings.createQueryBuilder("pin") + .where("pin.userId = :userId", { userId: user.id }) + .innerJoinAndSelect("pin.note", "note") + .orderBy("pin.id", "DESC") + .getMany() + : []; + const profile = opts.detail + ? await UserProfiles.findOneByOrFail({ userId: user.id }) + : null; - const followingCount = profile == null ? null : - (profile.ffVisibility === 'public') || isMe ? user.followingCount : - (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : - null; + const followingCount = + profile == null + ? null + : profile.ffVisibility === "public" || isMe + ? user.followingCount + : profile.ffVisibility === "followers" && + relation && + relation.isFollowing + ? user.followingCount + : null; - const followersCount = profile == null ? null : - (profile.ffVisibility === 'public') || isMe ? user.followersCount : - (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : - null; + const followersCount = + profile == null + ? null + : profile.ffVisibility === "public" || isMe + ? user.followersCount + : profile.ffVisibility === "followers" && + relation && + relation.isFollowing + ? user.followersCount + : null; const falsy = opts.detail ? false : undefined; @@ -362,135 +438,170 @@ export const UserRepository = db.getRepository(User).extend({ isModerator: user.isModerator || falsy, isBot: user.isBot || falsy, isCat: user.isCat || falsy, - instance: user.host ? userInstanceCache.fetch(user.host, - () => Instances.findOneBy({ host: user.host! }), - v => v != null, - ).then(instance => instance ? { - name: instance.name, - softwareName: instance.softwareName, - softwareVersion: instance.softwareVersion, - iconUrl: instance.iconUrl, - faviconUrl: instance.faviconUrl, - themeColor: instance.themeColor, - } : undefined) : undefined, + instance: user.host + ? userInstanceCache + .fetch( + user.host, + () => Instances.findOneBy({ host: user.host! }), + (v) => v != null, + ) + .then((instance) => + instance + ? { + name: instance.name, + softwareName: instance.softwareName, + softwareVersion: instance.softwareVersion, + iconUrl: instance.iconUrl, + faviconUrl: instance.faviconUrl, + themeColor: instance.themeColor, + } + : undefined, + ) + : undefined, emojis: populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), driveCapacityOverrideMb: user.driveCapacityOverrideMb, - ...(opts.detail ? { - url: profile!.url, - uri: user.uri, - movedToUri: user.movedToUri ? await this.userFromURI(user.movedToUri) : null, - alsoKnownAs: user.alsoKnownAs, - createdAt: user.createdAt.toISOString(), - updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, - lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, - bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null, - bannerBlurhash: user.banner?.blurhash || null, - bannerColor: null, // 後方互換性のため - isLocked: user.isLocked, - isSilenced: user.isSilenced || falsy, - isSuspended: user.isSuspended || falsy, - description: profile!.description, - location: profile!.location, - birthday: profile!.birthday, - lang: profile!.lang, - fields: profile!.fields, - followersCount: followersCount || 0, - followingCount: followingCount || 0, - notesCount: user.notesCount, - pinnedNoteIds: pins.map(pin => pin.noteId), - pinnedNotes: Notes.packMany(pins.map(pin => pin.note!), me, { - detail: true, - }), - pinnedPageId: profile!.pinnedPageId, - pinnedPage: profile!.pinnedPageId ? Pages.pack(profile!.pinnedPageId, me) : null, - publicReactions: profile!.publicReactions, - ffVisibility: profile!.ffVisibility, - twoFactorEnabled: profile!.twoFactorEnabled, - usePasswordLessLogin: profile!.usePasswordLessLogin, - securityKeys: profile!.twoFactorEnabled - ? UserSecurityKeys.countBy({ - userId: user.id, - }).then(result => result >= 1) - : false, - } : {}), + ...(opts.detail + ? { + url: profile!.url, + uri: user.uri, + movedToUri: user.movedToUri + ? await this.userFromURI(user.movedToUri) + : null, + alsoKnownAs: user.alsoKnownAs, + createdAt: user.createdAt.toISOString(), + updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, + lastFetchedAt: user.lastFetchedAt + ? user.lastFetchedAt.toISOString() + : null, + bannerUrl: user.banner + ? DriveFiles.getPublicUrl(user.banner, false) + : null, + bannerBlurhash: user.banner?.blurhash || null, + bannerColor: null, // 後方互換性のため + isLocked: user.isLocked, + isSilenced: user.isSilenced || falsy, + isSuspended: user.isSuspended || falsy, + description: profile!.description, + location: profile!.location, + birthday: profile!.birthday, + lang: profile!.lang, + fields: profile!.fields, + followersCount: followersCount || 0, + followingCount: followingCount || 0, + notesCount: user.notesCount, + pinnedNoteIds: pins.map((pin) => pin.noteId), + pinnedNotes: Notes.packMany( + pins.map((pin) => pin.note!), + me, + { + detail: true, + }, + ), + pinnedPageId: profile!.pinnedPageId, + pinnedPage: profile!.pinnedPageId + ? Pages.pack(profile!.pinnedPageId, me) + : null, + publicReactions: profile!.publicReactions, + ffVisibility: profile!.ffVisibility, + twoFactorEnabled: profile!.twoFactorEnabled, + usePasswordLessLogin: profile!.usePasswordLessLogin, + securityKeys: profile!.twoFactorEnabled + ? UserSecurityKeys.countBy({ + userId: user.id, + }).then((result) => result >= 1) + : false, + } + : {}), - ...(opts.detail && isMe ? { - avatarId: user.avatarId, - bannerId: user.bannerId, - injectFeaturedNote: profile!.injectFeaturedNote, - receiveAnnouncementEmail: profile!.receiveAnnouncementEmail, - alwaysMarkNsfw: profile!.alwaysMarkNsfw, - autoSensitive: profile!.autoSensitive, - carefulBot: profile!.carefulBot, - autoAcceptFollowed: profile!.autoAcceptFollowed, - noCrawle: profile!.noCrawle, - isExplorable: user.isExplorable, - isDeleted: user.isDeleted, - hideOnlineStatus: user.hideOnlineStatus, - hasUnreadSpecifiedNotes: NoteUnreads.count({ - where: { userId: user.id, isSpecified: true }, - take: 1, - }).then(count => count > 0), - hasUnreadMentions: NoteUnreads.count({ - where: { userId: user.id, isMentioned: true }, - take: 1, - }).then(count => count > 0), - hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id), - hasUnreadAntenna: this.getHasUnreadAntenna(user.id), - hasUnreadChannel: this.getHasUnreadChannel(user.id), - hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id), - hasUnreadNotification: this.getHasUnreadNotification(user.id), - hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id), - integrations: profile!.integrations, - mutedWords: profile!.mutedWords, - mutedInstances: profile!.mutedInstances, - mutingNotificationTypes: profile!.mutingNotificationTypes, - emailNotificationTypes: profile!.emailNotificationTypes, - showTimelineReplies: user.showTimelineReplies || falsy, - } : {}), + ...(opts.detail && isMe + ? { + avatarId: user.avatarId, + bannerId: user.bannerId, + injectFeaturedNote: profile!.injectFeaturedNote, + receiveAnnouncementEmail: profile!.receiveAnnouncementEmail, + alwaysMarkNsfw: profile!.alwaysMarkNsfw, + autoSensitive: profile!.autoSensitive, + carefulBot: profile!.carefulBot, + autoAcceptFollowed: profile!.autoAcceptFollowed, + noCrawle: profile!.noCrawle, + isExplorable: user.isExplorable, + isDeleted: user.isDeleted, + hideOnlineStatus: user.hideOnlineStatus, + hasUnreadSpecifiedNotes: NoteUnreads.count({ + where: { userId: user.id, isSpecified: true }, + take: 1, + }).then((count) => count > 0), + hasUnreadMentions: NoteUnreads.count({ + where: { userId: user.id, isMentioned: true }, + take: 1, + }).then((count) => count > 0), + hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id), + hasUnreadAntenna: this.getHasUnreadAntenna(user.id), + hasUnreadChannel: this.getHasUnreadChannel(user.id), + hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage( + user.id, + ), + hasUnreadNotification: this.getHasUnreadNotification(user.id), + hasPendingReceivedFollowRequest: + this.getHasPendingReceivedFollowRequest(user.id), + integrations: profile!.integrations, + mutedWords: profile!.mutedWords, + mutedInstances: profile!.mutedInstances, + mutingNotificationTypes: profile!.mutingNotificationTypes, + emailNotificationTypes: profile!.emailNotificationTypes, + showTimelineReplies: user.showTimelineReplies || falsy, + } + : {}), - ...(opts.includeSecrets ? { - email: profile!.email, - emailVerified: profile!.emailVerified, - securityKeysList: profile!.twoFactorEnabled - ? UserSecurityKeys.find({ - where: { - userId: user.id, - }, - select: { - id: true, - name: true, - lastUsed: true, - }, - }) - : [], - } : {}), + ...(opts.includeSecrets + ? { + email: profile!.email, + emailVerified: profile!.emailVerified, + securityKeysList: profile!.twoFactorEnabled + ? UserSecurityKeys.find({ + where: { + userId: user.id, + }, + select: { + id: true, + name: true, + lastUsed: true, + }, + }) + : [], + } + : {}), - ...(relation ? { - isFollowing: relation.isFollowing, - isFollowed: relation.isFollowed, - hasPendingFollowRequestFromYou: relation.hasPendingFollowRequestFromYou, - hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou, - isBlocking: relation.isBlocking, - isBlocked: relation.isBlocked, - isMuted: relation.isMuted, - } : {}), - } as Promiseable> as Promiseable>; + ...(relation + ? { + isFollowing: relation.isFollowing, + isFollowed: relation.isFollowed, + hasPendingFollowRequestFromYou: + relation.hasPendingFollowRequestFromYou, + hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou, + isBlocking: relation.isBlocking, + isBlocked: relation.isBlocked, + isMuted: relation.isMuted, + } + : {}), + } as Promiseable> as Promiseable< + IsMeAndIsUserDetailed + >; return await awaitAll(packed); }, packMany( - users: (User['id'] | User)[], - me?: { id: User['id'] } | null | undefined, + users: (User["id"] | User)[], + me?: { id: User["id"] } | null | undefined, options?: { - detail?: D, - includeSecrets?: boolean, + detail?: D; + includeSecrets?: boolean; }, ): Promise[]> { - return Promise.all(users.map(u => this.pack(u, me, options))); + return Promise.all(users.map((u) => this.pack(u, me, options))); }, isLocalUser, diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts index 9cf522802..c7eed092e 100644 --- a/packages/backend/src/models/schema/antenna.ts +++ b/packages/backend/src/models/schema/antenna.ts @@ -1,88 +1,107 @@ export const packedAntennaSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, keywords: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, excludeKeywords: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, src: { - type: 'string', - optional: false, nullable: false, - enum: ['home', 'all', 'users', 'list', 'group'], + type: "string", + optional: false, + nullable: false, + enum: ["home", "all", "users", "list", "group"], }, userListId: { - type: 'string', - optional: false, nullable: true, - format: 'id', + type: "string", + optional: false, + nullable: true, + format: "id", }, userGroupId: { - type: 'string', - optional: false, nullable: true, - format: 'id', + type: "string", + optional: false, + nullable: true, + format: "id", }, users: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, caseSensitive: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, default: false, }, notify: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, withReplies: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, default: false, }, withFile: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hasUnreadNote: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, default: false, }, }, diff --git a/packages/backend/src/models/schema/app.ts b/packages/backend/src/models/schema/app.ts index c80dc81c3..8ec71159a 100644 --- a/packages/backend/src/models/schema/app.ts +++ b/packages/backend/src/models/schema/app.ts @@ -1,33 +1,40 @@ export const packedAppSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, callbackUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, permission: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, secret: { - type: 'string', - optional: true, nullable: false, + type: "string", + optional: true, + nullable: false, }, isAuthorized: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/blocking.ts b/packages/backend/src/models/schema/blocking.ts index 553232242..1d491e939 100644 --- a/packages/backend/src/models/schema/blocking.ts +++ b/packages/backend/src/models/schema/blocking.ts @@ -1,26 +1,30 @@ export const packedBlockingSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, blockeeId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, blockee: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts index 7f4f2a48b..67833cb0d 100644 --- a/packages/backend/src/models/schema/channel.ts +++ b/packages/backend/src/models/schema/channel.ts @@ -1,51 +1,61 @@ export const packedChannelSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, lastNotedAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, description: { - type: 'string', - nullable: true, optional: false, + type: "string", + nullable: true, + optional: false, }, bannerUrl: { - type: 'string', - format: 'url', - nullable: true, optional: false, + type: "string", + format: "url", + nullable: true, + optional: false, }, notesCount: { - type: 'number', - nullable: false, optional: false, + type: "number", + nullable: false, + optional: false, }, usersCount: { - type: 'number', - nullable: false, optional: false, + type: "number", + nullable: false, + optional: false, }, isFollowing: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, userId: { - type: 'string', - nullable: true, optional: false, - format: 'id', + type: "string", + nullable: true, + optional: false, + format: "id", }, }, } as const; diff --git a/packages/backend/src/models/schema/clip.ts b/packages/backend/src/models/schema/clip.ts index f0ee2ce0c..651303ad9 100644 --- a/packages/backend/src/models/schema/clip.ts +++ b/packages/backend/src/models/schema/clip.ts @@ -1,38 +1,45 @@ export const packedClipSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, + type: "object", + ref: "UserLite", + optional: false, + nullable: false, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, description: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, isPublic: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/backend/src/models/schema/drive-file.ts index 435907661..30db9e7d4 100644 --- a/packages/backend/src/models/schema/drive-file.ts +++ b/packages/backend/src/models/schema/drive-file.ts @@ -1,107 +1,127 @@ export const packedDriveFileSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, - example: 'lenna.jpg', + type: "string", + optional: false, + nullable: false, + example: "lenna.jpg", }, type: { - type: 'string', - optional: false, nullable: false, - example: 'image/jpeg', + type: "string", + optional: false, + nullable: false, + example: "image/jpeg", }, md5: { - type: 'string', - optional: false, nullable: false, - format: 'md5', - example: '15eca7fba0480996e2245f5185bf39f2', + type: "string", + optional: false, + nullable: false, + format: "md5", + example: "15eca7fba0480996e2245f5185bf39f2", }, size: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, example: 51469, }, isSensitive: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, blurhash: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, properties: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { width: { - type: 'number', - optional: true, nullable: false, + type: "number", + optional: true, + nullable: false, example: 1280, }, height: { - type: 'number', - optional: true, nullable: false, + type: "number", + optional: true, + nullable: false, example: 720, }, orientation: { - type: 'number', - optional: true, nullable: false, + type: "number", + optional: true, + nullable: false, example: 8, }, avgColor: { - type: 'string', - optional: true, nullable: false, - example: 'rgb(40,65,87)', + type: "string", + optional: true, + nullable: false, + example: "rgb(40,65,87)", }, }, }, url: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, thumbnailUrl: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, comment: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, folderId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, folder: { - type: 'object', - optional: true, nullable: true, - ref: 'DriveFolder', + type: "object", + optional: true, + nullable: true, + ref: "DriveFolder", }, userId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, user: { - type: 'object', - optional: true, nullable: true, - ref: 'UserLite', + type: "object", + optional: true, + nullable: true, + ref: "UserLite", }, }, } as const; diff --git a/packages/backend/src/models/schema/drive-folder.ts b/packages/backend/src/models/schema/drive-folder.ts index 88cb8ab4a..2298b5420 100644 --- a/packages/backend/src/models/schema/drive-folder.ts +++ b/packages/backend/src/models/schema/drive-folder.ts @@ -1,39 +1,46 @@ export const packedDriveFolderSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, foldersCount: { - type: 'number', - optional: true, nullable: false, + type: "number", + optional: true, + nullable: false, }, filesCount: { - type: 'number', - optional: true, nullable: false, + type: "number", + optional: true, + nullable: false, }, parentId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, parent: { - type: 'object', - optional: true, nullable: true, - ref: 'DriveFolder', + type: "object", + optional: true, + nullable: true, + ref: "DriveFolder", }, }, } as const; diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts index e97fdd5ef..1e1dab868 100644 --- a/packages/backend/src/models/schema/emoji.ts +++ b/packages/backend/src/models/schema/emoji.ts @@ -1,37 +1,44 @@ export const packedEmojiSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, aliases: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, category: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', + type: "string", + optional: false, + nullable: true, + description: "The local host is represented with `null`.", }, url: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts index e35f5ecb8..ed3369bf1 100644 --- a/packages/backend/src/models/schema/federation-instance.ts +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -1,110 +1,133 @@ -import config from '@/config/index.js'; +import config from "@/config/index.js"; export const packedFederationInstanceSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, caughtAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, host: { - type: 'string', - optional: false, nullable: false, - example: 'calckey.example.com', + type: "string", + optional: false, + nullable: false, + example: "calckey.example.com", }, usersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, notesCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, followingCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, followersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, latestRequestSentAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, lastCommunicatedAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, isNotResponding: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isSuspended: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isBlocked: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, softwareName: { - type: 'string', - optional: false, nullable: true, - example: 'calckey', + type: "string", + optional: false, + nullable: true, + example: "calckey", }, softwareVersion: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, example: config.version, }, openRegistrations: { - type: 'boolean', - optional: false, nullable: true, + type: "boolean", + optional: false, + nullable: true, example: true, }, name: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, description: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, maintainerName: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, maintainerEmail: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, iconUrl: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, faviconUrl: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, themeColor: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, infoUpdatedAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, }, } as const; diff --git a/packages/backend/src/models/schema/following.ts b/packages/backend/src/models/schema/following.ts index 2bcffbfc4..f53cafaba 100644 --- a/packages/backend/src/models/schema/following.ts +++ b/packages/backend/src/models/schema/following.ts @@ -1,36 +1,42 @@ export const packedFollowingSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, followeeId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, followee: { - type: 'object', - optional: true, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: true, + nullable: false, + ref: "UserDetailed", }, followerId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, follower: { - type: 'object', - optional: true, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: true, + nullable: false, + ref: "UserDetailed", }, }, } as const; diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/backend/src/models/schema/gallery-post.ts index fc503d4a6..9ac348e1f 100644 --- a/packages/backend/src/models/schema/gallery-post.ts +++ b/packages/backend/src/models/schema/gallery-post.ts @@ -1,69 +1,83 @@ export const packedGalleryPostSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, updatedAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, title: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, description: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, + type: "object", + ref: "UserLite", + optional: false, + nullable: false, }, fileIds: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, files: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, tags: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, isSensitive: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/hashtag.ts b/packages/backend/src/models/schema/hashtag.ts index 7245535c4..dacc51507 100644 --- a/packages/backend/src/models/schema/hashtag.ts +++ b/packages/backend/src/models/schema/hashtag.ts @@ -1,34 +1,41 @@ export const packedHashtagSchema = { - type: 'object', + type: "object", properties: { tag: { - type: 'string', - optional: false, nullable: false, - example: 'calckey', + type: "string", + optional: false, + nullable: false, + example: "calckey", }, mentionedUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, mentionedLocalUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, mentionedRemoteUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, attachedUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, attachedLocalUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, attachedRemoteUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/backend/src/models/schema/messaging-message.ts index b1ffa4595..d598e6dbc 100644 --- a/packages/backend/src/models/schema/messaging-message.ts +++ b/packages/backend/src/models/schema/messaging-message.ts @@ -1,72 +1,86 @@ export const packedMessagingMessageSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - ref: 'UserLite', - optional: true, nullable: false, + type: "object", + ref: "UserLite", + optional: true, + nullable: false, }, text: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, fileId: { - type: 'string', - optional: true, nullable: true, - format: 'id', + type: "string", + optional: true, + nullable: true, + format: "id", }, file: { - type: 'object', - optional: true, nullable: true, - ref: 'DriveFile', + type: "object", + optional: true, + nullable: true, + ref: "DriveFile", }, recipientId: { - type: 'string', - optional: false, nullable: true, - format: 'id', + type: "string", + optional: false, + nullable: true, + format: "id", }, recipient: { - type: 'object', - optional: true, nullable: true, - ref: 'UserLite', + type: "object", + optional: true, + nullable: true, + ref: "UserLite", }, groupId: { - type: 'string', - optional: false, nullable: true, - format: 'id', + type: "string", + optional: false, + nullable: true, + format: "id", }, group: { - type: 'object', - optional: true, nullable: true, - ref: 'UserGroup', + type: "object", + optional: true, + nullable: true, + ref: "UserGroup", }, isRead: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, reads: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, }, diff --git a/packages/backend/src/models/schema/muting.ts b/packages/backend/src/models/schema/muting.ts index 3ab99e17e..d5815f86d 100644 --- a/packages/backend/src/models/schema/muting.ts +++ b/packages/backend/src/models/schema/muting.ts @@ -1,31 +1,36 @@ export const packedMutingSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, expiresAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, muteeId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, mutee: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/backend/src/models/schema/note-favorite.ts index d133f7367..17a42baf0 100644 --- a/packages/backend/src/models/schema/note-favorite.ts +++ b/packages/backend/src/models/schema/note-favorite.ts @@ -1,26 +1,30 @@ export const packedNoteFavoriteSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, note: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, noteId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, } as const; diff --git a/packages/backend/src/models/schema/note-reaction.ts b/packages/backend/src/models/schema/note-reaction.ts index 0d8fc5449..1080bdcf5 100644 --- a/packages/backend/src/models/schema/note-reaction.ts +++ b/packages/backend/src/models/schema/note-reaction.ts @@ -1,25 +1,29 @@ export const packedNoteReactionSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, user: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, type: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index 292bbb82f..4a7bd80fc 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -1,179 +1,217 @@ export const packedNoteSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, text: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, cw: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, + type: "object", + ref: "UserLite", + optional: false, + nullable: false, }, replyId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: true, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, renoteId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: true, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, reply: { - type: 'object', - optional: true, nullable: true, - ref: 'Note', + type: "object", + optional: true, + nullable: true, + ref: "Note", }, renote: { - type: 'object', - optional: true, nullable: true, - ref: 'Note', + type: "object", + optional: true, + nullable: true, + ref: "Note", }, visibility: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, mentions: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, visibleUserIds: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, fileIds: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, files: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, tags: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, poll: { - type: 'object', - optional: true, nullable: true, + type: "object", + optional: true, + nullable: true, }, channelId: { - type: 'string', - optional: true, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: true, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, channel: { - type: 'object', - optional: true, nullable: true, + type: "object", + optional: true, + nullable: true, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, name: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, }, }, }, localOnly: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, emojis: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, url: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, }, }, }, reactions: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, }, renoteCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, repliesCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, uri: { - type: 'string', - optional: true, nullable: false, + type: "string", + optional: true, + nullable: false, }, url: { - type: 'string', - optional: true, nullable: false, + type: "string", + optional: true, + nullable: false, }, myReaction: { - type: 'object', - optional: true, nullable: true, + type: "object", + optional: true, + nullable: true, }, }, } as const; diff --git a/packages/backend/src/models/schema/notification.ts b/packages/backend/src/models/schema/notification.ts index d3f2405cd..97fd16339 100644 --- a/packages/backend/src/models/schema/notification.ts +++ b/packages/backend/src/models/schema/notification.ts @@ -1,66 +1,79 @@ -import { notificationTypes } from '@/types.js'; +import { notificationTypes } from "@/types.js"; export const packedNotificationSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, isRead: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, type: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, enum: [...notificationTypes], }, user: { - type: 'object', - ref: 'UserLite', - optional: true, nullable: true, + type: "object", + ref: "UserLite", + optional: true, + nullable: true, }, userId: { - type: 'string', - optional: true, nullable: true, - format: 'id', + type: "string", + optional: true, + nullable: true, + format: "id", }, note: { - type: 'object', - ref: 'Note', - optional: true, nullable: true, + type: "object", + ref: "Note", + optional: true, + nullable: true, }, reaction: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, choice: { - type: 'number', - optional: true, nullable: true, + type: "number", + optional: true, + nullable: true, }, invitation: { - type: 'object', - optional: true, nullable: true, + type: "object", + optional: true, + nullable: true, }, body: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, header: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, icon: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, }, } as const; diff --git a/packages/backend/src/models/schema/page.ts b/packages/backend/src/models/schema/page.ts index 19074947b..a1b9144b5 100644 --- a/packages/backend/src/models/schema/page.ts +++ b/packages/backend/src/models/schema/page.ts @@ -1,55 +1,66 @@ export const packedPageSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, updatedAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, title: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, summary: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, content: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, }, variables: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - ref: 'UserLite', - optional: false, nullable: false, + type: "object", + ref: "UserLite", + optional: false, + nullable: false, }, isPublic: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/queue.ts b/packages/backend/src/models/schema/queue.ts index 7ceeda26a..954ac688b 100644 --- a/packages/backend/src/models/schema/queue.ts +++ b/packages/backend/src/models/schema/queue.ts @@ -1,25 +1,30 @@ export const packedQueueCountSchema = { - type: 'object', + type: "object", properties: { waiting: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, active: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, completed: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, failed: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, delayed: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, } as const; diff --git a/packages/backend/src/models/schema/user-group.ts b/packages/backend/src/models/schema/user-group.ts index a73bf82bb..a4a85f969 100644 --- a/packages/backend/src/models/schema/user-group.ts +++ b/packages/backend/src/models/schema/user-group.ts @@ -1,33 +1,39 @@ export const packedUserGroupSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, ownerId: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, userIds: { - type: 'array', - nullable: false, optional: true, + type: "array", + nullable: false, + optional: true, items: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, }, }, diff --git a/packages/backend/src/models/schema/user-list.ts b/packages/backend/src/models/schema/user-list.ts index 3ba5dc4a8..1e203b63a 100644 --- a/packages/backend/src/models/schema/user-list.ts +++ b/packages/backend/src/models/schema/user-list.ts @@ -1,28 +1,33 @@ export const packedUserListSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, userIds: { - type: 'array', - nullable: false, optional: true, + type: "array", + nullable: false, + optional: true, items: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, }, }, diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts index b70210a0f..9fb5aa8f3 100644 --- a/packages/backend/src/models/schema/user.ts +++ b/packages/backend/src/models/schema/user.ts @@ -1,422 +1,513 @@ export const packedUserLiteSchema = { - type: 'object', + type: "object", properties: { id: { - type: 'string', - nullable: false, optional: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + nullable: false, + optional: false, + format: "id", + example: "xxxxxxxxxx", }, name: { - type: 'string', - nullable: true, optional: false, - example: '藍', + type: "string", + nullable: true, + optional: false, + example: "藍", }, username: { - type: 'string', - nullable: false, optional: false, - example: 'calc', + type: "string", + nullable: false, + optional: false, + example: "calc", }, host: { - type: 'string', - nullable: true, optional: false, - example: 'misskey.example.com', - description: 'The local host is represented with `null`.', + type: "string", + nullable: true, + optional: false, + example: "misskey.example.com", + description: "The local host is represented with `null`.", }, avatarUrl: { - type: 'string', - format: 'url', - nullable: true, optional: false, + type: "string", + format: "url", + nullable: true, + optional: false, }, avatarBlurhash: { - type: 'any', - nullable: true, optional: false, + type: "any", + nullable: true, + optional: false, }, avatarColor: { - type: 'any', - nullable: true, optional: false, + type: "any", + nullable: true, + optional: false, default: null, }, isAdmin: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, default: false, }, isModerator: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, default: false, }, isBot: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, isCat: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, emojis: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'object', - nullable: false, optional: false, + type: "object", + nullable: false, + optional: false, properties: { name: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, url: { - type: 'string', - nullable: false, optional: false, - format: 'url', + type: "string", + nullable: false, + optional: false, + format: "url", }, }, }, }, onlineStatus: { - type: 'string', - format: 'url', - nullable: true, optional: false, - enum: ['unknown', 'online', 'active', 'offline'], + type: "string", + format: "url", + nullable: true, + optional: false, + enum: ["unknown", "online", "active", "offline"], }, }, } as const; export const packedUserDetailedNotMeOnlySchema = { - type: 'object', + type: "object", properties: { url: { - type: 'string', - format: 'url', - nullable: true, optional: false, + type: "string", + format: "url", + nullable: true, + optional: false, }, uri: { - type: 'string', - format: 'uri', - nullable: true, optional: false, + type: "string", + format: "uri", + nullable: true, + optional: false, }, movedToUri: { - type: 'string', - format: 'uri', - nullable: true, optional: false, + type: "string", + format: "uri", + nullable: true, + optional: false, }, alsoKnownAs: { - type: 'array', - format: 'uri', - nullable: true, optional: false, + type: "array", + format: "uri", + nullable: true, + optional: false, }, createdAt: { - type: 'string', - nullable: false, optional: false, - format: 'date-time', + type: "string", + nullable: false, + optional: false, + format: "date-time", }, updatedAt: { - type: 'string', - nullable: true, optional: false, - format: 'date-time', + type: "string", + nullable: true, + optional: false, + format: "date-time", }, lastFetchedAt: { - type: 'string', - nullable: true, optional: false, - format: 'date-time', + type: "string", + nullable: true, + optional: false, + format: "date-time", }, bannerUrl: { - type: 'string', - format: 'url', - nullable: true, optional: false, + type: "string", + format: "url", + nullable: true, + optional: false, }, bannerBlurhash: { - type: 'any', - nullable: true, optional: false, + type: "any", + nullable: true, + optional: false, }, bannerColor: { - type: 'any', - nullable: true, optional: false, + type: "any", + nullable: true, + optional: false, default: null, }, isLocked: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, isSilenced: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, isSuspended: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, example: false, }, description: { - type: 'string', - nullable: true, optional: false, - example: 'Hi masters, I am Ai!', + type: "string", + nullable: true, + optional: false, + example: "Hi masters, I am Ai!", }, location: { - type: 'string', - nullable: true, optional: false, + type: "string", + nullable: true, + optional: false, }, birthday: { - type: 'string', - nullable: true, optional: false, - example: '2018-03-12', + type: "string", + nullable: true, + optional: false, + example: "2018-03-12", }, lang: { - type: 'string', - nullable: true, optional: false, - example: 'ja-JP', + type: "string", + nullable: true, + optional: false, + example: "ja-JP", }, fields: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'object', - nullable: false, optional: false, + type: "object", + nullable: false, + optional: false, properties: { name: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, value: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, }, maxLength: 4, }, }, followersCount: { - type: 'number', - nullable: false, optional: false, + type: "number", + nullable: false, + optional: false, }, followingCount: { - type: 'number', - nullable: false, optional: false, + type: "number", + nullable: false, + optional: false, }, notesCount: { - type: 'number', - nullable: false, optional: false, + type: "number", + nullable: false, + optional: false, }, pinnedNoteIds: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, }, pinnedNotes: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'object', - nullable: false, optional: false, - ref: 'Note', + type: "object", + nullable: false, + optional: false, + ref: "Note", }, }, pinnedPageId: { - type: 'string', - nullable: true, optional: false, + type: "string", + nullable: true, + optional: false, }, pinnedPage: { - type: 'object', - nullable: true, optional: false, - ref: 'Page', + type: "object", + nullable: true, + optional: false, + ref: "Page", }, publicReactions: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, twoFactorEnabled: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, default: false, }, usePasswordLessLogin: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, default: false, }, securityKeys: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, default: false, }, //#region relations isFollowing: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, isFollowed: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, hasPendingFollowRequestFromYou: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, hasPendingFollowRequestToYou: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, isBlocking: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, isBlocked: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, isMuted: { - type: 'boolean', - nullable: false, optional: true, + type: "boolean", + nullable: false, + optional: true, }, //#endregion }, } as const; export const packedMeDetailedOnlySchema = { - type: 'object', + type: "object", properties: { avatarId: { - type: 'string', - nullable: true, optional: false, - format: 'id', + type: "string", + nullable: true, + optional: false, + format: "id", }, bannerId: { - type: 'string', - nullable: true, optional: false, - format: 'id', + type: "string", + nullable: true, + optional: false, + format: "id", }, injectFeaturedNote: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, receiveAnnouncementEmail: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, alwaysMarkNsfw: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, autoSensitive: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, carefulBot: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, autoAcceptFollowed: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, noCrawle: { - type: 'boolean', - nullable: true, optional: false, + type: "boolean", + nullable: true, + optional: false, }, isExplorable: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, isDeleted: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hideOnlineStatus: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadSpecifiedNotes: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadMentions: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadAnnouncement: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadAntenna: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadChannel: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadMessagingMessage: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasUnreadNotification: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, hasPendingReceivedFollowRequest: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, }, integrations: { - type: 'object', - nullable: true, optional: false, + type: "object", + nullable: true, + optional: false, }, mutedWords: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, }, }, mutedInstances: { - type: 'array', - nullable: true, optional: false, + type: "array", + nullable: true, + optional: false, items: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, }, mutingNotificationTypes: { - type: 'array', - nullable: true, optional: false, + type: "array", + nullable: true, + optional: false, items: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, }, emailNotificationTypes: { - type: 'array', - nullable: true, optional: false, + type: "array", + nullable: true, + optional: false, items: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, }, //#region secrets email: { - type: 'string', - nullable: true, optional: true, + type: "string", + nullable: true, + optional: true, }, emailVerified: { - type: 'boolean', - nullable: true, optional: true, + type: "boolean", + nullable: true, + optional: true, }, securityKeysList: { - type: 'array', - nullable: false, optional: true, + type: "array", + nullable: false, + optional: true, items: { - type: 'object', - nullable: false, optional: false, + type: "object", + nullable: false, + optional: false, }, }, //#endregion @@ -424,33 +515,33 @@ export const packedMeDetailedOnlySchema = { } as const; export const packedUserDetailedNotMeSchema = { - type: 'object', + type: "object", allOf: [ { - type: 'object', - ref: 'UserLite', + type: "object", + ref: "UserLite", }, { - type: 'object', - ref: 'UserDetailedNotMeOnly', + type: "object", + ref: "UserDetailedNotMeOnly", }, ], } as const; export const packedMeDetailedSchema = { - type: 'object', + type: "object", allOf: [ { - type: 'object', - ref: 'UserLite', + type: "object", + ref: "UserLite", }, { - type: 'object', - ref: 'UserDetailedNotMeOnly', + type: "object", + ref: "UserDetailedNotMeOnly", }, { - type: 'object', - ref: 'MeDetailedOnly', + type: "object", + ref: "MeDetailedOnly", }, ], } as const; @@ -458,12 +549,12 @@ export const packedMeDetailedSchema = { export const packedUserDetailedSchema = { oneOf: [ { - type: 'object', - ref: 'UserDetailedNotMe', + type: "object", + ref: "UserDetailedNotMe", }, { - type: 'object', - ref: 'MeDetailed', + type: "object", + ref: "MeDetailed", }, ], } as const; @@ -471,12 +562,12 @@ export const packedUserDetailedSchema = { export const packedUserSchema = { oneOf: [ { - type: 'object', - ref: 'UserLite', + type: "object", + ref: "UserLite", }, { - type: 'object', - ref: 'UserDetailed', + type: "object", + ref: "UserDetailed", }, ], } as const; diff --git a/packages/backend/src/prelude/array.ts b/packages/backend/src/prelude/array.ts index 0b2830cb7..71a24c89b 100644 --- a/packages/backend/src/prelude/array.ts +++ b/packages/backend/src/prelude/array.ts @@ -1,4 +1,4 @@ -import { EndoRelation, Predicate } from './relation.js'; +import type { EndoRelation, Predicate } from "./relation.js"; /** * Count the number of elements that satisfy the predicate @@ -12,7 +12,7 @@ export function countIf(f: Predicate, xs: T[]): number { * Count the number of elements that is equal to the element */ export function count(a: T, xs: T[]): number { - return countIf(x => x === a, xs); + return countIf((x) => x === a, xs); } /** @@ -27,14 +27,14 @@ export function concat(xss: T[][]): T[] { * @param sep The element to be interspersed */ export function intersperse(sep: T, xs: T[]): T[] { - return concat(xs.map(x => [sep, x])).slice(1); + return concat(xs.map((x) => [sep, x])).slice(1); } /** * Returns the array of elements that is not equal to the element */ export function erase(a: T, xs: T[]): T[] { - return xs.filter(x => x !== a); + return xs.filter((x) => x !== a); } /** @@ -42,7 +42,7 @@ export function erase(a: T, xs: T[]): T[] { * The order of result values are determined by the first array. */ export function difference(xs: T[], ys: T[]): T[] { - return xs.filter(x => !ys.includes(x)); + return xs.filter((x) => !ys.includes(x)); } /** diff --git a/packages/backend/src/prelude/await-all.ts b/packages/backend/src/prelude/await-all.ts index b955c3a5d..5b2a4177d 100644 --- a/packages/backend/src/prelude/await-all.ts +++ b/packages/backend/src/prelude/await-all.ts @@ -7,11 +7,13 @@ export async function awaitAll(obj: Promiseable): Promise { const keys = Object.keys(obj) as unknown as (keyof T)[]; const values = Object.values(obj) as any[]; - const resolvedValues = await Promise.all(values.map(value => - (!value || !value.constructor || value.constructor.name !== 'Object') - ? value - : awaitAll(value) - )); + const resolvedValues = await Promise.all( + values.map((value) => + !(value?.constructor ) || value.constructor.name !== "Object" + ? value + : awaitAll(value), + ), + ); for (let i = 0; i < keys.length; i++) { target[keys[i]] = resolvedValues[i]; diff --git a/packages/backend/src/prelude/string.ts b/packages/backend/src/prelude/string.ts index b907e0a2e..9588825cb 100644 --- a/packages/backend/src/prelude/string.ts +++ b/packages/backend/src/prelude/string.ts @@ -1,5 +1,5 @@ export function concat(xs: string[]): string { - return xs.join(''); + return xs.join(""); } export function capitalize(s: string): string { diff --git a/packages/backend/src/prelude/symbol.ts b/packages/backend/src/prelude/symbol.ts index 51e12f745..5b88467d4 100644 --- a/packages/backend/src/prelude/symbol.ts +++ b/packages/backend/src/prelude/symbol.ts @@ -1 +1 @@ -export const fallback = Symbol('fallback'); +export const fallback = Symbol("fallback"); diff --git a/packages/backend/src/prelude/time.ts b/packages/backend/src/prelude/time.ts index 0da1f7913..5901b9c48 100644 --- a/packages/backend/src/prelude/time.ts +++ b/packages/backend/src/prelude/time.ts @@ -1,19 +1,26 @@ const dateTimeIntervals = { - 'day': 86400000, - 'hour': 3600000, - 'ms': 1, + day: 86400000, + hour: 3600000, + ms: 1, }; export function dateUTC(time: number[]): Date { - const d = time.length === 2 ? Date.UTC(time[0], time[1]) - : time.length === 3 ? Date.UTC(time[0], time[1], time[2]) - : time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3]) - : time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) - : time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) - : time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) - : null; + const d = + time.length === 2 + ? Date.UTC(time[0], time[1]) + : time.length === 3 + ? Date.UTC(time[0], time[1], time[2]) + : time.length === 4 + ? Date.UTC(time[0], time[1], time[2], time[3]) + : time.length === 5 + ? Date.UTC(time[0], time[1], time[2], time[3], time[4]) + : time.length === 6 + ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5]) + : time.length === 7 + ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6]) + : null; - if (!d) throw new Error('wrong number of arguments'); + if (!d) throw new Error("wrong number of arguments"); return new Date(d); } @@ -23,17 +30,25 @@ export function isTimeSame(a: Date, b: Date): boolean { } export function isTimeBefore(a: Date, b: Date): boolean { - return (a.getTime() - b.getTime()) < 0; + return a.getTime() - b.getTime() < 0; } export function isTimeAfter(a: Date, b: Date): boolean { - return (a.getTime() - b.getTime()) > 0; + return a.getTime() - b.getTime() > 0; } -export function addTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date { - return new Date(x.getTime() + (value * dateTimeIntervals[span])); +export function addTime( + x: Date, + value: number, + span: keyof typeof dateTimeIntervals = "ms", +): Date { + return new Date(x.getTime() + value * dateTimeIntervals[span]); } -export function subtractTime(x: Date, value: number, span: keyof typeof dateTimeIntervals = 'ms'): Date { - return new Date(x.getTime() - (value * dateTimeIntervals[span])); +export function subtractTime( + x: Date, + value: number, + span: keyof typeof dateTimeIntervals = "ms", +): Date { + return new Date(x.getTime() - value * dateTimeIntervals[span]); } diff --git a/packages/backend/src/prelude/url.ts b/packages/backend/src/prelude/url.ts index a4f2f7f5a..9e3f3f7c1 100644 --- a/packages/backend/src/prelude/url.ts +++ b/packages/backend/src/prelude/url.ts @@ -1,13 +1,15 @@ export function query(obj: Record): string { const params = Object.entries(obj) - .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) - .reduce((a, [k, v]) => (a[k] = v, a), {} as Record); + .filter(([, v]) => (Array.isArray(v) ? v.length : v !== undefined)) + .reduce((a, [k, v]) => ((a[k] = v), a), {} as Record); return Object.entries(params) .map((e) => `${e[0]}=${encodeURIComponent(e[1])}`) - .join('&'); + .join("&"); } export function appendQuery(url: string, query: string): string { - return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`; + return `${url}${ + /\?/.test(url) ? (url.endsWith("?") ? "" : "&") : "?" + }${query}`; } diff --git a/packages/backend/src/prelude/xml.ts b/packages/backend/src/prelude/xml.ts index b4469a1d8..9dcc4c96f 100644 --- a/packages/backend/src/prelude/xml.ts +++ b/packages/backend/src/prelude/xml.ts @@ -1,21 +1,18 @@ const map: Record = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", }; -const beginingOfCDATA = ''; +const beginingOfCDATA = ""; export function escapeValue(x: string): string { let insideOfCDATA = false; - let builder = ''; - for ( - let i = 0; - i < x.length; - ) { + let builder = ""; + for (let i = 0; i < x.length; ) { if (insideOfCDATA) { if (x.slice(i, i + beginingOfCDATA.length) === beginingOfCDATA) { insideOfCDATA = true; diff --git a/packages/backend/src/queue/get-job-info.ts b/packages/backend/src/queue/get-job-info.ts index d33e349c3..ae3532cda 100644 --- a/packages/backend/src/queue/get-job-info.ts +++ b/packages/backend/src/queue/get-job-info.ts @@ -1,11 +1,14 @@ -import Bull from 'bull'; +import type Bull from "bull"; export function getJobInfo(job: Bull.Job, increment = false) { const age = Date.now() - job.timestamp; - const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m` - : age > 10000 ? `${Math.floor(age / 1000)}s` - : `${age}ms`; + const formated = + age > 60000 + ? `${Math.floor(age / 1000 / 60)}m` + : age > 10000 + ? `${Math.floor(age / 1000)}s` + : `${age}ms`; // onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする const currentAttempts = job.attemptsMade + (increment ? 1 : 0); diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index ebb3a77ca..c40b3c6ae 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -1,23 +1,31 @@ -import httpSignature from '@peertube/http-signature'; -import { v4 as uuid } from 'uuid'; +import type httpSignature from "@peertube/http-signature"; +import { v4 as uuid } from "uuid"; -import config from '@/config/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { IActivity } from '@/remote/activitypub/type.js'; -import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js'; -import { envOption } from '../env.js'; +import config from "@/config/index.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { IActivity } from "@/remote/activitypub/type.js"; +import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js"; +import { envOption } from "../env.js"; -import processDeliver from './processors/deliver.js'; -import processInbox from './processors/inbox.js'; -import processDb from './processors/db/index.js'; -import processObjectStorage from './processors/object-storage/index.js'; -import processSystemQueue from './processors/system/index.js'; -import processWebhookDeliver from './processors/webhook-deliver.js'; -import { endedPollNotification } from './processors/ended-poll-notification.js'; -import { queueLogger } from './logger.js'; -import { getJobInfo } from './get-job-info.js'; -import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js'; -import { ThinUser } from './types.js'; +import processDeliver from "./processors/deliver.js"; +import processInbox from "./processors/inbox.js"; +import processDb from "./processors/db/index.js"; +import processObjectStorage from "./processors/object-storage/index.js"; +import processSystemQueue from "./processors/system/index.js"; +import processWebhookDeliver from "./processors/webhook-deliver.js"; +import { endedPollNotification } from "./processors/ended-poll-notification.js"; +import { queueLogger } from "./logger.js"; +import { getJobInfo } from "./get-job-info.js"; +import { + systemQueue, + dbQueue, + deliverQueue, + inboxQueue, + objectStorageQueue, + endedPollNotificationQueue, + webhookDeliverQueue, +} from "./queues.js"; +import type { ThinUser } from "./types.js"; function renderError(e: Error): any { return { @@ -27,60 +35,125 @@ function renderError(e: Error): any { }; } -const systemLogger = queueLogger.createSubLogger('system'); -const deliverLogger = queueLogger.createSubLogger('deliver'); -const webhookLogger = queueLogger.createSubLogger('webhook'); -const inboxLogger = queueLogger.createSubLogger('inbox'); -const dbLogger = queueLogger.createSubLogger('db'); -const objectStorageLogger = queueLogger.createSubLogger('objectStorage'); +const systemLogger = queueLogger.createSubLogger("system"); +const deliverLogger = queueLogger.createSubLogger("deliver"); +const webhookLogger = queueLogger.createSubLogger("webhook"); +const inboxLogger = queueLogger.createSubLogger("inbox"); +const dbLogger = queueLogger.createSubLogger("db"); +const objectStorageLogger = queueLogger.createSubLogger("objectStorage"); systemQueue - .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => systemLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`)); + .on("waiting", (jobId) => systemLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => systemLogger.debug(`active id=${job.id}`)) + .on("completed", (job, result) => + systemLogger.debug(`completed(${result}) id=${job.id}`), + ) + .on("failed", (job, err) => + systemLogger.warn(`failed(${err}) id=${job.id}`, { + job, + e: renderError(err), + }), + ) + .on("error", (job: any, err: Error) => + systemLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => systemLogger.warn(`stalled id=${job.id}`)); deliverQueue - .on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); + .on("waiting", (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => + deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`), + ) + .on("completed", (job, result) => + deliverLogger.debug( + `completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`, + ), + ) + .on("failed", (job, err) => + deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`), + ) + .on("error", (job: any, err: Error) => + deliverLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => + deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`), + ); inboxQueue - .on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`)) - .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) - .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`)); + .on("waiting", (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`)) + .on("completed", (job, result) => + inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`), + ) + .on("failed", (job, err) => + inboxLogger.warn( + `failed(${err}) ${getJobInfo(job)} activity=${ + job.data.activity ? job.data.activity.id : "none" + }`, + { job, e: renderError(err) }, + ), + ) + .on("error", (job: any, err: Error) => + inboxLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => + inboxLogger.warn( + `stalled ${getJobInfo(job)} activity=${ + job.data.activity ? job.data.activity.id : "none" + }`, + ), + ); dbQueue - .on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => dbLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`)); + .on("waiting", (jobId) => dbLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => dbLogger.debug(`active id=${job.id}`)) + .on("completed", (job, result) => + dbLogger.debug(`completed(${result}) id=${job.id}`), + ) + .on("failed", (job, err) => + dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }), + ) + .on("error", (job: any, err: Error) => + dbLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => dbLogger.warn(`stalled id=${job.id}`)); objectStorageQueue - .on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`)) - .on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) })) - .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); + .on("waiting", (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => objectStorageLogger.debug(`active id=${job.id}`)) + .on("completed", (job, result) => + objectStorageLogger.debug(`completed(${result}) id=${job.id}`), + ) + .on("failed", (job, err) => + objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { + job, + e: renderError(err), + }), + ) + .on("error", (job: any, err: Error) => + objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); webhookDeliverQueue - .on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`)) - .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`)) - .on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) })) - .on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`)); + .on("waiting", (jobId) => webhookLogger.debug(`waiting id=${jobId}`)) + .on("active", (job) => + webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`), + ) + .on("completed", (job, result) => + webhookLogger.debug( + `completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`, + ), + ) + .on("failed", (job, err) => + webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`), + ) + .on("error", (job: any, err: Error) => + webhookLogger.error(`error ${err}`, { job, e: renderError(err) }), + ) + .on("stalled", (job) => + webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`), + ); export function deliver(user: ThinUser, content: unknown, to: string | null) { if (content == null) return null; @@ -96,16 +169,19 @@ export function deliver(user: ThinUser, content: unknown, to: string | null) { return deliverQueue.add(data, { attempts: config.deliverJobMaxAttempts || 12, - timeout: 1 * 60 * 1000, // 1min + timeout: 1 * 60 * 1000, // 1min backoff: { - type: 'apBackoff', + type: "apBackoff", }, removeOnComplete: true, removeOnFail: true, }); } -export function inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { +export function inbox( + activity: IActivity, + signature: httpSignature.IParsedSignature, +) { const data = { activity: activity, signature, @@ -113,9 +189,9 @@ export function inbox(activity: IActivity, signature: httpSignature.IParsedSigna return inboxQueue.add(data, { attempts: config.inboxJobMaxAttempts || 8, - timeout: 5 * 60 * 1000, // 5min + timeout: 5 * 60 * 1000, // 5min backoff: { - type: 'apBackoff', + type: "apBackoff", }, removeOnComplete: true, removeOnFail: true, @@ -123,147 +199,230 @@ export function inbox(activity: IActivity, signature: httpSignature.IParsedSigna } export function createDeleteDriveFilesJob(user: ThinUser) { - return dbQueue.add('deleteDriveFiles', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "deleteDriveFiles", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createExportCustomEmojisJob(user: ThinUser) { - return dbQueue.add('exportCustomEmojis', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "exportCustomEmojis", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createExportNotesJob(user: ThinUser) { - return dbQueue.add('exportNotes', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "exportNotes", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createExportFollowingJob(user: ThinUser, excludeMuting = false, excludeInactive = false) { - return dbQueue.add('exportFollowing', { - user: user, - excludeMuting, - excludeInactive, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createExportFollowingJob( + user: ThinUser, + excludeMuting = false, + excludeInactive = false, +) { + return dbQueue.add( + "exportFollowing", + { + user: user, + excludeMuting, + excludeInactive, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createExportMuteJob(user: ThinUser) { - return dbQueue.add('exportMute', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "exportMute", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createExportBlockingJob(user: ThinUser) { - return dbQueue.add('exportBlocking', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "exportBlocking", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createExportUserListsJob(user: ThinUser) { - return dbQueue.add('exportUserLists', { - user: user, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return dbQueue.add( + "exportUserLists", + { + user: user, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { - return dbQueue.add('importFollowing', { - user: user, - fileId: fileId, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createImportFollowingJob( + user: ThinUser, + fileId: DriveFile["id"], +) { + return dbQueue.add( + "importFollowing", + { + user: user, + fileId: fileId, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createImportMutingJob(user: ThinUser, fileId: DriveFile['id']) { - return dbQueue.add('importMuting', { - user: user, - fileId: fileId, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createImportMutingJob(user: ThinUser, fileId: DriveFile["id"]) { + return dbQueue.add( + "importMuting", + { + user: user, + fileId: fileId, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createImportBlockingJob(user: ThinUser, fileId: DriveFile['id']) { - return dbQueue.add('importBlocking', { - user: user, - fileId: fileId, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createImportBlockingJob( + user: ThinUser, + fileId: DriveFile["id"], +) { + return dbQueue.add( + "importBlocking", + { + user: user, + fileId: fileId, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { - return dbQueue.add('importUserLists', { - user: user, - fileId: fileId, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createImportUserListsJob( + user: ThinUser, + fileId: DriveFile["id"], +) { + return dbQueue.add( + "importUserLists", + { + user: user, + fileId: fileId, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { - return dbQueue.add('importCustomEmojis', { - user: user, - fileId: fileId, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createImportCustomEmojisJob( + user: ThinUser, + fileId: DriveFile["id"], +) { + return dbQueue.add( + "importCustomEmojis", + { + user: user, + fileId: fileId, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { - return dbQueue.add('deleteAccount', { - user: user, - soft: opts.soft, - }, { - removeOnComplete: true, - removeOnFail: true, - }); +export function createDeleteAccountJob( + user: ThinUser, + opts: { soft?: boolean } = {}, +) { + return dbQueue.add( + "deleteAccount", + { + user: user, + soft: opts.soft, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createDeleteObjectStorageFileJob(key: string) { - return objectStorageQueue.add('deleteFile', { - key: key, - }, { - removeOnComplete: true, - removeOnFail: true, - }); + return objectStorageQueue.add( + "deleteFile", + { + key: key, + }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } export function createCleanRemoteFilesJob() { - return objectStorageQueue.add('cleanRemoteFiles', {}, { - removeOnComplete: true, - removeOnFail: true, - }); + return objectStorageQueue.add( + "cleanRemoteFiles", + {}, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); } -export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) { +export function webhookDeliver( + webhook: Webhook, + type: typeof webhookEventTypes[number], + content: unknown, +) { const data = { type, content, @@ -277,16 +436,16 @@ export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[ return webhookDeliverQueue.add(data, { attempts: 4, - timeout: 1 * 60 * 1000, // 1min + timeout: 1 * 60 * 1000, // 1min backoff: { - type: 'apBackoff', + type: "apBackoff", }, removeOnComplete: true, removeOnFail: true, }); } -export default function() { +export default function () { if (envOption.onlyServer) return; deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver); @@ -296,47 +455,62 @@ export default function() { processDb(dbQueue); processObjectStorage(objectStorageQueue); - systemQueue.add('tickCharts', { - }, { - repeat: { cron: '55 * * * *' }, - removeOnComplete: true, - }); + systemQueue.add( + "tickCharts", + {}, + { + repeat: { cron: "55 * * * *" }, + removeOnComplete: true, + }, + ); - systemQueue.add('resyncCharts', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + systemQueue.add( + "resyncCharts", + {}, + { + repeat: { cron: "0 0 * * *" }, + removeOnComplete: true, + }, + ); - systemQueue.add('cleanCharts', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + systemQueue.add( + "cleanCharts", + {}, + { + repeat: { cron: "0 0 * * *" }, + removeOnComplete: true, + }, + ); - systemQueue.add('clean', { - }, { - repeat: { cron: '0 0 * * *' }, - removeOnComplete: true, - }); + systemQueue.add( + "clean", + {}, + { + repeat: { cron: "0 0 * * *" }, + removeOnComplete: true, + }, + ); - systemQueue.add('checkExpiredMutings', { - }, { - repeat: { cron: '*/5 * * * *' }, - removeOnComplete: true, - }); + systemQueue.add( + "checkExpiredMutings", + {}, + { + repeat: { cron: "*/5 * * * *" }, + removeOnComplete: true, + }, + ); processSystemQueue(systemQueue); } export function destroy() { - deliverQueue.once('cleaned', (jobs, status) => { + deliverQueue.once("cleaned", (jobs, status) => { deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); - deliverQueue.clean(0, 'delayed'); + deliverQueue.clean(0, "delayed"); - inboxQueue.once('cleaned', (jobs, status) => { + inboxQueue.once("cleaned", (jobs, status) => { inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); }); - inboxQueue.clean(0, 'delayed'); + inboxQueue.clean(0, "delayed"); } diff --git a/packages/backend/src/queue/initialize.ts b/packages/backend/src/queue/initialize.ts index eef4080af..d7945d5da 100644 --- a/packages/backend/src/queue/initialize.ts +++ b/packages/backend/src/queue/initialize.ts @@ -1,5 +1,5 @@ -import Bull from 'bull'; -import config from '@/config/index.js'; +import Bull from "bull"; +import config from "@/config/index.js"; export function initialize(name: string, limitPerSec = -1) { return new Bull(name, { @@ -10,11 +10,14 @@ export function initialize(name: string, limitPerSec = -1) { 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, + prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : "queue", + limiter: + limitPerSec > 0 + ? { + max: limitPerSec, + duration: 1000, + } + : undefined, settings: { backoffStrategies: { apBackoff, @@ -25,8 +28,8 @@ export function initialize(name: string, limitPerSec = -1) { // 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 + 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); diff --git a/packages/backend/src/queue/logger.ts b/packages/backend/src/queue/logger.ts index 2843a3c26..929c207e3 100644 --- a/packages/backend/src/queue/logger.ts +++ b/packages/backend/src/queue/logger.ts @@ -1,3 +1,3 @@ -import Logger from '@/services/logger.js'; +import Logger from "@/services/logger.js"; -export const queueLogger = new Logger('queue', 'orange'); +export const queueLogger = new Logger("queue", "orange"); diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index c1657b4be..764b83db2 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -1,16 +1,18 @@ -import Bull from 'bull'; -import { queueLogger } from '../../logger.js'; -import { DriveFiles, Notes, UserProfiles, Users } from '@/models/index.js'; -import { DbUserDeleteJobData } from '@/queue/types.js'; -import { Note } from '@/models/entities/note.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { MoreThan } from 'typeorm'; -import { deleteFileSync } from '@/services/drive/delete-file.js'; -import { sendEmail } from '@/services/send-email.js'; +import type Bull from "bull"; +import { queueLogger } from "../../logger.js"; +import { DriveFiles, Notes, UserProfiles, Users } from "@/models/index.js"; +import type { DbUserDeleteJobData } from "@/queue/types.js"; +import type { Note } from "@/models/entities/note.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { MoreThan } from "typeorm"; +import { deleteFileSync } from "@/services/drive/delete-file.js"; +import { sendEmail } from "@/services/send-email.js"; -const logger = queueLogger.createSubLogger('delete-account'); +const logger = queueLogger.createSubLogger("delete-account"); -export async function deleteAccount(job: Bull.Job): Promise { +export async function deleteAccount( + job: Bull.Job, +): Promise { logger.info(`Deleting account of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -18,11 +20,12 @@ export async function deleteAccount(job: Bull.Job): Promise return; } - { // Delete notes - let cursor: Note['id'] | null = null; + { + // Delete notes + let cursor: Note["id"] | null = null; while (true) { - const notes = await Notes.find({ + const notes = (await Notes.find({ where: { userId: user.id, ...(cursor ? { id: MoreThan(cursor) } : {}), @@ -31,7 +34,7 @@ export async function deleteAccount(job: Bull.Job): Promise order: { id: 1, }, - }) as Note[]; + })) as Note[]; if (notes.length === 0) { break; @@ -39,17 +42,18 @@ export async function deleteAccount(job: Bull.Job): Promise cursor = notes[notes.length - 1].id; - await Notes.delete(notes.map(note => note.id)); + await Notes.delete(notes.map((note) => note.id)); } - logger.succ(`All of notes deleted`); + logger.succ("All of notes deleted"); } - { // Delete files - let cursor: DriveFile['id'] | null = null; + { + // Delete files + let cursor: DriveFile["id"] | null = null; while (true) { - const files = await DriveFiles.find({ + const files = (await DriveFiles.find({ where: { userId: user.id, ...(cursor ? { id: MoreThan(cursor) } : {}), @@ -58,7 +62,7 @@ export async function deleteAccount(job: Bull.Job): Promise order: { id: 1, }, - }) as DriveFile[]; + })) as DriveFile[]; if (files.length === 0) { break; @@ -71,15 +75,19 @@ export async function deleteAccount(job: Bull.Job): Promise } } - logger.succ(`All of files deleted`); + logger.succ("All of files deleted"); } - { // Send email notification + { + // Send email notification const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { - sendEmail(profile.email, 'Account deleted', - `Your account has been deleted.`, - `Your account has been deleted.`); + sendEmail( + profile.email, + "Account deleted", + "Your account has been deleted.", + "Your account has been deleted.", + ); } } @@ -90,5 +98,5 @@ export async function deleteAccount(job: Bull.Job): Promise await Users.delete(job.data.user.id); } - return 'Account deleted'; + return "Account deleted"; } diff --git a/packages/backend/src/queue/processors/db/delete-drive-files.ts b/packages/backend/src/queue/processors/db/delete-drive-files.ts index b3832d9f0..28e477132 100644 --- a/packages/backend/src/queue/processors/db/delete-drive-files.ts +++ b/packages/backend/src/queue/processors/db/delete-drive-files.ts @@ -1,14 +1,17 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import { deleteFileSync } from '@/services/drive/delete-file.js'; -import { Users, DriveFiles } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; -import { DbUserJobData } from '@/queue/types.js'; +import { queueLogger } from "../../logger.js"; +import { deleteFileSync } from "@/services/drive/delete-file.js"; +import { Users, DriveFiles } from "@/models/index.js"; +import { MoreThan } from "typeorm"; +import type { DbUserJobData } from "@/queue/types.js"; -const logger = queueLogger.createSubLogger('delete-drive-files'); +const logger = queueLogger.createSubLogger("delete-drive-files"); -export async function deleteDriveFiles(job: Bull.Job, done: any): Promise { +export async function deleteDriveFiles( + job: Bull.Job, + done: any, +): Promise { logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -51,6 +54,8 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): job.progress(deletedCount / total); } - logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`); + logger.succ( + `All drive files (${deletedCount}) of ${user.id} has been deleted.`, + ); done(); } diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index f5e0424a7..2427564a3 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -1,18 +1,21 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { getFullApAccount } from '@/misc/convert-host.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { Users, Blockings } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; -import { DbUserJobData } from '@/queue/types.js'; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { getFullApAccount } from "@/misc/convert-host.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { Users, Blockings } from "@/models/index.js"; +import { MoreThan } from "typeorm"; +import type { DbUserJobData } from "@/queue/types.js"; -const logger = queueLogger.createSubLogger('export-blocking'); +const logger = queueLogger.createSubLogger("export-blocking"); -export async function exportBlocking(job: Bull.Job, done: any): Promise { +export async function exportBlocking( + job: Bull.Job, + done: any, +): Promise { logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -27,7 +30,7 @@ export async function exportBlocking(job: Bull.Job, done: any): P logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); + const stream = fs.createWriteStream(path, { flags: "a" }); let exportedCount = 0; let cursor: any = null; @@ -54,12 +57,13 @@ export async function exportBlocking(job: Bull.Job, done: any): P for (const block of blockings) { const u = await Users.findOneBy({ id: block.blockeeId }); if (u == null) { - exportedCount++; continue; + exportedCount++; + continue; } const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + '\n', err => { + stream.write(content + "\n", (err) => { if (err) { logger.error(err); rej(err); @@ -81,8 +85,14 @@ export async function exportBlocking(job: Bull.Job, done: any): P stream.end(); logger.succ(`Exported to: ${path}`); - const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = + `blocking-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`; + const driveFile = await addFile({ + user, + path, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 3da887cda..f49c1938a 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -1,23 +1,26 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { ulid } from 'ulid'; -import mime from 'mime-types'; -import archiver from 'archiver'; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { Users, Emojis } from '@/models/index.js'; -import { } from '@/queue/types.js'; -import { createTemp, createTempDir } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import config from '@/config/index.js'; -import { IsNull } from 'typeorm'; +import { ulid } from "ulid"; +import mime from "mime-types"; +import archiver from "archiver"; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { Users, Emojis } from "@/models/index.js"; +import {} from "@/queue/types.js"; +import { createTemp, createTempDir } from "@/misc/create-temp.js"; +import { downloadUrl } from "@/misc/download-url.js"; +import config from "@/config/index.js"; +import { IsNull } from "typeorm"; -const logger = queueLogger.createSubLogger('export-custom-emojis'); +const logger = queueLogger.createSubLogger("export-custom-emojis"); -export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promise { - logger.info(`Exporting custom emojis ...`); +export async function exportCustomEmojis( + job: Bull.Job, + done: () => void, +): Promise { + logger.info("Exporting custom emojis ..."); const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -29,15 +32,15 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi logger.info(`Temp dir is ${path}`); - const metaPath = path + '/meta.json'; + const metaPath = `${path}/meta.json`; - fs.writeFileSync(metaPath, '', 'utf-8'); + fs.writeFileSync(metaPath, "", "utf-8"); - const metaStream = fs.createWriteStream(metaPath, { flags: 'a' }); + const metaStream = fs.createWriteStream(metaPath, { flags: "a" }); const writeMeta = (text: string): Promise => { return new Promise((res, rej) => { - metaStream.write(text, err => { + metaStream.write(text, (err) => { if (err) { logger.error(err); rej(err); @@ -48,28 +51,33 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`); + await writeMeta( + `{"metaVersion":2,"host":"${ + config.host + }","exportedAt":"${new Date().toString()}","emojis":[`, + ); const customEmojis = await Emojis.find({ where: { host: IsNull(), }, order: { - id: 'ASC', + id: "ASC", }, }); for (const emoji of customEmojis) { const ext = mime.extension(emoji.type); - const fileName = emoji.name + (ext ? '.' + ext : ''); - const emojiPath = path + '/' + fileName; - fs.writeFileSync(emojiPath, '', 'binary'); + const fileName = emoji.name + (ext ? `.${ext}` : ""); + const emojiPath = `${path}/${fileName}`; + fs.writeFileSync(emojiPath, "", "binary"); let downloaded = false; try { await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; - } catch (e) { // TODO: 何度か再試行 + } catch (e) { + // TODO: 何度か再試行 logger.error(e instanceof Error ? e : new Error(e as string)); } @@ -84,24 +92,30 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); const isFirst = customEmojis.indexOf(emoji) === 0; - await writeMeta(isFirst ? content : ',\n' + content); + await writeMeta(isFirst ? content : ",\n" + content); } - await writeMeta(']}'); + await writeMeta("]}"); metaStream.end(); // Create archive const [archivePath, archiveCleanup] = await createTemp(); const archiveStream = fs.createWriteStream(archivePath); - const archive = archiver('zip', { + const archive = archiver("zip", { zlib: { level: 0 }, }); - archiveStream.on('close', async () => { + archiveStream.on("close", async () => { logger.succ(`Exported to: ${archivePath}`); - const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; - const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true }); + const fileName = + `custom-emojis-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.zip`; + const driveFile = await addFile({ + user, + path: archivePath, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 4ac165567..3f790d4c2 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -1,19 +1,22 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { getFullApAccount } from '@/misc/convert-host.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { Users, Followings, Mutings } from '@/models/index.js'; -import { In, MoreThan, Not } from 'typeorm'; -import { DbUserJobData } from '@/queue/types.js'; -import { Following } from '@/models/entities/following.js'; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { getFullApAccount } from "@/misc/convert-host.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { Users, Followings, Mutings } from "@/models/index.js"; +import { In, MoreThan, Not } from "typeorm"; +import type { DbUserJobData } from "@/queue/types.js"; +import type { Following } from "@/models/entities/following.js"; -const logger = queueLogger.createSubLogger('export-following'); +const logger = queueLogger.createSubLogger("export-following"); -export async function exportFollowing(job: Bull.Job, done: () => void): Promise { +export async function exportFollowing( + job: Bull.Job, + done: () => void, +): Promise { logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -28,26 +31,30 @@ export async function exportFollowing(job: Bull.Job, done: () => logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); + const stream = fs.createWriteStream(path, { flags: "a" }); - let cursor: Following['id'] | null = null; + let cursor: Following["id"] | null = null; - const mutings = job.data.excludeMuting ? await Mutings.findBy({ - muterId: user.id, - }) : []; + const mutings = job.data.excludeMuting + ? await Mutings.findBy({ + muterId: user.id, + }) + : []; while (true) { - const followings = await Followings.find({ + const followings = (await Followings.find({ where: { followerId: user.id, - ...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}), + ...(mutings.length > 0 + ? { followeeId: Not(In(mutings.map((x) => x.muteeId))) } + : {}), ...(cursor ? { id: MoreThan(cursor) } : {}), }, take: 100, order: { id: 1, }, - }) as Following[]; + })) as Following[]; if (followings.length === 0) { break; @@ -61,13 +68,17 @@ export async function exportFollowing(job: Bull.Job, done: () => continue; } - if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { + if ( + job.data.excludeInactive && + u.updatedAt && + Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90 + ) { continue; } const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + '\n', err => { + stream.write(content + "\n", (err) => { if (err) { logger.error(err); rej(err); @@ -82,8 +93,14 @@ export async function exportFollowing(job: Bull.Job, done: () => stream.end(); logger.succ(`Exported to: ${path}`); - const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = + `following-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`; + const driveFile = await addFile({ + user, + path, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 6a36cfa07..4cc50eb3e 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -1,18 +1,21 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { getFullApAccount } from '@/misc/convert-host.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { Users, Mutings } from '@/models/index.js'; -import { IsNull, MoreThan } from 'typeorm'; -import { DbUserJobData } from '@/queue/types.js'; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { getFullApAccount } from "@/misc/convert-host.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { Users, Mutings } from "@/models/index.js"; +import { IsNull, MoreThan } from "typeorm"; +import type { DbUserJobData } from "@/queue/types.js"; -const logger = queueLogger.createSubLogger('export-mute'); +const logger = queueLogger.createSubLogger("export-mute"); -export async function exportMute(job: Bull.Job, done: any): Promise { +export async function exportMute( + job: Bull.Job, + done: any, +): Promise { logger.info(`Exporting mute of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -27,7 +30,7 @@ export async function exportMute(job: Bull.Job, done: any): Promi logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); + const stream = fs.createWriteStream(path, { flags: "a" }); let exportedCount = 0; let cursor: any = null; @@ -55,12 +58,13 @@ export async function exportMute(job: Bull.Job, done: any): Promi for (const mute of mutes) { const u = await Users.findOneBy({ id: mute.muteeId }); if (u == null) { - exportedCount++; continue; + exportedCount++; + continue; } const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + '\n', err => { + stream.write(content + "\n", (err) => { if (err) { logger.error(err); rej(err); @@ -82,8 +86,14 @@ export async function exportMute(job: Bull.Job, done: any): Promi stream.end(); logger.succ(`Exported to: ${path}`); - const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = + `mute-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`; + const driveFile = await addFile({ + user, + path, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index 051fcdf38..3ab5971aa 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -1,19 +1,22 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { Users, Notes, Polls } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; -import { Note } from '@/models/entities/note.js'; -import { Poll } from '@/models/entities/poll.js'; -import { DbUserJobData } from '@/queue/types.js'; -import { createTemp } from '@/misc/create-temp.js'; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { Users, Notes, Polls } from "@/models/index.js"; +import { MoreThan } from "typeorm"; +import type { Note } from "@/models/entities/note.js"; +import type { Poll } from "@/models/entities/poll.js"; +import type { DbUserJobData } from "@/queue/types.js"; +import { createTemp } from "@/misc/create-temp.js"; -const logger = queueLogger.createSubLogger('export-notes'); +const logger = queueLogger.createSubLogger("export-notes"); -export async function exportNotes(job: Bull.Job, done: any): Promise { +export async function exportNotes( + job: Bull.Job, + done: any, +): Promise { logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -28,11 +31,11 @@ export async function exportNotes(job: Bull.Job, done: any): Prom logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); + const stream = fs.createWriteStream(path, { flags: "a" }); const write = (text: string): Promise => { return new Promise((res, rej) => { - stream.write(text, err => { + stream.write(text, (err) => { if (err) { logger.error(err); rej(err); @@ -43,13 +46,13 @@ export async function exportNotes(job: Bull.Job, done: any): Prom }); }; - await write('['); + await write("["); let exportedNotesCount = 0; - let cursor: Note['id'] | null = null; + let cursor: Note["id"] | null = null; while (true) { - const notes = await Notes.find({ + const notes = (await Notes.find({ where: { userId: user.id, ...(cursor ? { id: MoreThan(cursor) } : {}), @@ -58,7 +61,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom order: { id: 1, }, - }) as Note[]; + })) as Note[]; if (notes.length === 0) { job.progress(100); @@ -74,7 +77,7 @@ export async function exportNotes(job: Bull.Job, done: any): Prom } const content = JSON.stringify(serialize(note, poll)); const isFirst = exportedNotesCount === 0; - await write(isFirst ? content : ',\n' + content); + await write(isFirst ? content : ",\n" + content); exportedNotesCount++; } @@ -85,13 +88,19 @@ export async function exportNotes(job: Bull.Job, done: any): Prom job.progress(exportedNotesCount / total); } - await write(']'); + await write("]"); stream.end(); logger.succ(`Exported to: ${path}`); - const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = + `notes-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.json`; + const driveFile = await addFile({ + user, + path, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); } finally { @@ -101,7 +110,10 @@ export async function exportNotes(job: Bull.Job, done: any): Prom done(); } -function serialize(note: Note, poll: Poll | null = null): Record { +function serialize( + note: Note, + poll: Poll | null = null, +): Record { return { id: note.id, text: note.text, diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 71dd72df2..8ff7a3d8e 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -1,18 +1,21 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; +import type Bull from "bull"; +import * as fs from "node:fs"; -import { queueLogger } from '../../logger.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { format as dateFormat } from 'date-fns'; -import { getFullApAccount } from '@/misc/convert-host.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { Users, UserLists, UserListJoinings } from '@/models/index.js'; -import { In } from 'typeorm'; -import { DbUserJobData } from '@/queue/types.js'; +import { queueLogger } from "../../logger.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { format as dateFormat } from "date-fns"; +import { getFullApAccount } from "@/misc/convert-host.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { Users, UserLists, UserListJoinings } from "@/models/index.js"; +import { In } from "typeorm"; +import type { DbUserJobData } from "@/queue/types.js"; -const logger = queueLogger.createSubLogger('export-user-lists'); +const logger = queueLogger.createSubLogger("export-user-lists"); -export async function exportUserLists(job: Bull.Job, done: any): Promise { +export async function exportUserLists( + job: Bull.Job, + done: any, +): Promise { logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -31,19 +34,19 @@ export async function exportUserLists(job: Bull.Job, done: any): logger.info(`Temp file is ${path}`); try { - const stream = fs.createWriteStream(path, { flags: 'a' }); + const stream = fs.createWriteStream(path, { flags: "a" }); for (const list of lists) { const joinings = await UserListJoinings.findBy({ userListId: list.id }); const users = await Users.findBy({ - id: In(joinings.map(j => j.userId)), + id: In(joinings.map((j) => j.userId)), }); for (const u of users) { const acct = getFullApAccount(u.username, u.host); const content = `${list.name},${acct}`; await new Promise((res, rej) => { - stream.write(content + '\n', err => { + stream.write(content + "\n", (err) => { if (err) { logger.error(err); rej(err); @@ -58,8 +61,14 @@ export async function exportUserLists(job: Bull.Job, done: any): stream.end(); logger.succ(`Exported to: ${path}`); - const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = + `user-lists-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.csv`; + const driveFile = await addFile({ + user, + path, + name: fileName, + force: true, + }); logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/db/import-blocking.ts b/packages/backend/src/queue/processors/db/import-blocking.ts index 42a14fb3e..2fdf80a6e 100644 --- a/packages/backend/src/queue/processors/db/import-blocking.ts +++ b/packages/backend/src/queue/processors/db/import-blocking.ts @@ -1,18 +1,21 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import * as Acct from '@/misc/acct.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { downloadTextFile } from '@/misc/download-text-file.js'; -import { isSelfHost, toPuny } from '@/misc/convert-host.js'; -import { Users, DriveFiles, Blockings } from '@/models/index.js'; -import { DbUserImportJobData } from '@/queue/types.js'; -import block from '@/services/blocking/create.js'; -import { IsNull } from 'typeorm'; +import { queueLogger } from "../../logger.js"; +import * as Acct from "@/misc/acct.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { downloadTextFile } from "@/misc/download-text-file.js"; +import { isSelfHost, toPuny } from "@/misc/convert-host.js"; +import { Users, DriveFiles, Blockings } from "@/models/index.js"; +import type { DbUserImportJobData } from "@/queue/types.js"; +import block from "@/services/blocking/create.js"; +import { IsNull } from "typeorm"; -const logger = queueLogger.createSubLogger('import-blocking'); +const logger = queueLogger.createSubLogger("import-blocking"); -export async function importBlocking(job: Bull.Job, done: any): Promise { +export async function importBlocking( + job: Bull.Job, + done: any, +): Promise { logger.info(`Importing blocking of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -33,20 +36,22 @@ export async function importBlocking(job: Bull.Job, done: a let linenum = 0; - for (const line of csv.trim().split('\n')) { + for (const line of csv.trim().split("\n")) { linenum++; try { - const acct = line.split(',')[0].trim(); + const acct = line.split(",")[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOneBy({ - host: IsNull(), - usernameLower: username.toLowerCase(), - }) : await Users.findOneBy({ - host: toPuny(host!), - usernameLower: username.toLowerCase(), - }); + let target = isSelfHost(host!) + ? await Users.findOneBy({ + host: IsNull(), + usernameLower: username.toLowerCase(), + }) + : await Users.findOneBy({ + host: toPuny(host!), + usernameLower: username.toLowerCase(), + }); if (host == null && target == null) continue; @@ -69,7 +74,6 @@ export async function importBlocking(job: Bull.Job, done: a } } - logger.succ('Imported'); + logger.succ("Imported"); done(); } - diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index 64dfe8537..45ab5ea9a 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -1,21 +1,24 @@ -import Bull from 'bull'; -import * as fs from 'node:fs'; -import unzipper from 'unzipper'; +import type Bull from "bull"; +import * as fs from "node:fs"; +import unzipper from "unzipper"; -import { queueLogger } from '../../logger.js'; -import { createTempDir } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { DriveFiles, Emojis } from '@/models/index.js'; -import { DbUserImportJobData } from '@/queue/types.js'; -import { addFile } from '@/services/drive/add-file.js'; -import { genId } from '@/misc/gen-id.js'; -import { db } from '@/db/postgre.js'; +import { queueLogger } from "../../logger.js"; +import { createTempDir } from "@/misc/create-temp.js"; +import { downloadUrl } from "@/misc/download-url.js"; +import { DriveFiles, Emojis } from "@/models/index.js"; +import type { DbUserImportJobData } from "@/queue/types.js"; +import { addFile } from "@/services/drive/add-file.js"; +import { genId } from "@/misc/gen-id.js"; +import { db } from "@/db/postgre.js"; -const logger = queueLogger.createSubLogger('import-custom-emojis'); +const logger = queueLogger.createSubLogger("import-custom-emojis"); // TODO: 名前衝突時の動作を選べるようにする -export async function importCustomEmojis(job: Bull.Job, done: any): Promise { - logger.info(`Importing custom emojis ...`); +export async function importCustomEmojis( + job: Bull.Job, + done: any, +): Promise { + logger.info("Importing custom emojis ..."); const file = await DriveFiles.findOneBy({ id: job.data.fileId, @@ -29,33 +32,39 @@ export async function importCustomEmojis(job: Bull.Job, don logger.info(`Temp dir is ${path}`); - const destPath = path + '/emojis.zip'; + const destPath = `${path}/emojis.zip`; try { - fs.writeFileSync(destPath, '', 'binary'); + fs.writeFileSync(destPath, "", "binary"); await downloadUrl(file.url, destPath); - } catch (e) { // TODO: 何度か再試行 - if (e instanceof Error || typeof e === 'string') { + } catch (e) { + // TODO: 何度か再試行 + if (e instanceof Error || typeof e === "string") { logger.error(e); } throw e; } - const outputPath = path + '/emojis'; + const outputPath = `${path}/emojis`; const unzipStream = fs.createReadStream(destPath); const extractor = unzipper.Extract({ path: outputPath }); - extractor.on('close', async () => { - const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); + extractor.on("close", async () => { + const metaRaw = fs.readFileSync(`${outputPath}/meta.json`, "utf-8"); const meta = JSON.parse(metaRaw); for (const record of meta.emojis) { if (!record.downloaded) continue; const emojiInfo = record.emoji; - const emojiPath = outputPath + '/' + record.fileName; + const emojiPath = `${outputPath}/${record.fileName}`; await Emojis.delete({ name: emojiInfo.name, }); - const driveFile = await addFile({ user: null, path: emojiPath, name: record.fileName, force: true }); + const driveFile = await addFile({ + user: null, + path: emojiPath, + name: record.fileName, + force: true, + }); const emoji = await Emojis.insert({ id: genId(), updatedAt: new Date(), @@ -66,14 +75,14 @@ export async function importCustomEmojis(job: Bull.Job, don originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); + }).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); } - await db.queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(["meta_emojis"]); cleanup(); - - logger.succ('Imported'); + + logger.succ("Imported"); done(); }); unzipStream.pipe(extractor); diff --git a/packages/backend/src/queue/processors/db/import-following.ts b/packages/backend/src/queue/processors/db/import-following.ts index 6c40ebc1c..b1a7cd2c9 100644 --- a/packages/backend/src/queue/processors/db/import-following.ts +++ b/packages/backend/src/queue/processors/db/import-following.ts @@ -1,18 +1,21 @@ -import { IsNull } from 'typeorm'; -import follow from '@/services/following/create.js'; +import { IsNull } from "typeorm"; +import follow from "@/services/following/create.js"; -import * as Acct from '@/misc/acct.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { downloadTextFile } from '@/misc/download-text-file.js'; -import { isSelfHost, toPuny } from '@/misc/convert-host.js'; -import { Users, DriveFiles } from '@/models/index.js'; -import type { DbUserImportJobData } from '@/queue/types.js'; -import { queueLogger } from '../../logger.js'; -import type Bull from 'bull'; +import * as Acct from "@/misc/acct.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { downloadTextFile } from "@/misc/download-text-file.js"; +import { isSelfHost, toPuny } from "@/misc/convert-host.js"; +import { Users, DriveFiles } from "@/models/index.js"; +import type { DbUserImportJobData } from "@/queue/types.js"; +import { queueLogger } from "../../logger.js"; +import type Bull from "bull"; -const logger = queueLogger.createSubLogger('import-following'); +const logger = queueLogger.createSubLogger("import-following"); -export async function importFollowing(job: Bull.Job, done: any): Promise { +export async function importFollowing( + job: Bull.Job, + done: any, +): Promise { logger.info(`Importing following of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -33,18 +36,20 @@ export async function importFollowing(job: Bull.Job, done: let linenum = 0; - if (file.type.endsWith('json')) { + if (file.type.endsWith("json")) { for (const acct of JSON.parse(csv)) { try { const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOneBy({ - host: IsNull(), - usernameLower: username.toLowerCase(), - }) : await Users.findOneBy({ - host: toPuny(host!), - usernameLower: username.toLowerCase(), - }); + let target = isSelfHost(host!) + ? await Users.findOneBy({ + host: IsNull(), + usernameLower: username.toLowerCase(), + }) + : await Users.findOneBy({ + host: toPuny(host!), + usernameLower: username.toLowerCase(), + }); if (host == null && target == null) continue; @@ -66,22 +71,23 @@ export async function importFollowing(job: Bull.Job, done: logger.warn(`Error in line:${linenum} ${e}`); } } - } - else { - for (const line of csv.trim().split('\n')) { + } else { + for (const line of csv.trim().split("\n")) { linenum++; try { - const acct = line.split(',')[0].trim(); + const acct = line.split(",")[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOneBy({ - host: IsNull(), - usernameLower: username.toLowerCase(), - }) : await Users.findOneBy({ - host: toPuny(host!), - usernameLower: username.toLowerCase(), - }); + let target = isSelfHost(host!) + ? await Users.findOneBy({ + host: IsNull(), + usernameLower: username.toLowerCase(), + }) + : await Users.findOneBy({ + host: toPuny(host!), + usernameLower: username.toLowerCase(), + }); if (host == null && target == null) continue; @@ -105,6 +111,6 @@ export async function importFollowing(job: Bull.Job, done: } } - logger.succ('Imported'); + logger.succ("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-muting.ts b/packages/backend/src/queue/processors/db/import-muting.ts index aa14ff526..80e056739 100644 --- a/packages/backend/src/queue/processors/db/import-muting.ts +++ b/packages/backend/src/queue/processors/db/import-muting.ts @@ -1,19 +1,22 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import * as Acct from '@/misc/acct.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { downloadTextFile } from '@/misc/download-text-file.js'; -import { isSelfHost, toPuny } from '@/misc/convert-host.js'; -import { Users, DriveFiles, Mutings } from '@/models/index.js'; -import { DbUserImportJobData } from '@/queue/types.js'; -import { User } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { IsNull } from 'typeorm'; +import { queueLogger } from "../../logger.js"; +import * as Acct from "@/misc/acct.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { downloadTextFile } from "@/misc/download-text-file.js"; +import { isSelfHost, toPuny } from "@/misc/convert-host.js"; +import { Users, DriveFiles, Mutings } from "@/models/index.js"; +import type { DbUserImportJobData } from "@/queue/types.js"; +import type { User } from "@/models/entities/user.js"; +import { genId } from "@/misc/gen-id.js"; +import { IsNull } from "typeorm"; -const logger = queueLogger.createSubLogger('import-muting'); +const logger = queueLogger.createSubLogger("import-muting"); -export async function importMuting(job: Bull.Job, done: any): Promise { +export async function importMuting( + job: Bull.Job, + done: any, +): Promise { logger.info(`Importing muting of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -34,20 +37,22 @@ export async function importMuting(job: Bull.Job, done: any let linenum = 0; - for (const line of csv.trim().split('\n')) { + for (const line of csv.trim().split("\n")) { linenum++; try { - const acct = line.split(',')[0].trim(); + const acct = line.split(",")[0].trim(); const { username, host } = Acct.parse(acct); - let target = isSelfHost(host!) ? await Users.findOneBy({ - host: IsNull(), - usernameLower: username.toLowerCase(), - }) : await Users.findOneBy({ - host: toPuny(host!), - usernameLower: username.toLowerCase(), - }); + let target = isSelfHost(host!) + ? await Users.findOneBy({ + host: IsNull(), + usernameLower: username.toLowerCase(), + }) + : await Users.findOneBy({ + host: toPuny(host!), + usernameLower: username.toLowerCase(), + }); if (host == null && target == null) continue; @@ -70,7 +75,7 @@ export async function importMuting(job: Bull.Job, done: any } } - logger.succ('Imported'); + logger.succ("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index 9919b7c53..0c23f0699 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -1,19 +1,27 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import * as Acct from '@/misc/acct.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { pushUserToUserList } from '@/services/user-list/push.js'; -import { downloadTextFile } from '@/misc/download-text-file.js'; -import { isSelfHost, toPuny } from '@/misc/convert-host.js'; -import { DriveFiles, Users, UserLists, UserListJoinings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { DbUserImportJobData } from '@/queue/types.js'; -import { IsNull } from 'typeorm'; +import { queueLogger } from "../../logger.js"; +import * as Acct from "@/misc/acct.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { pushUserToUserList } from "@/services/user-list/push.js"; +import { downloadTextFile } from "@/misc/download-text-file.js"; +import { isSelfHost, toPuny } from "@/misc/convert-host.js"; +import { + DriveFiles, + Users, + UserLists, + UserListJoinings, +} from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { DbUserImportJobData } from "@/queue/types.js"; +import { IsNull } from "typeorm"; -const logger = queueLogger.createSubLogger('import-user-lists'); +const logger = queueLogger.createSubLogger("import-user-lists"); -export async function importUserLists(job: Bull.Job, done: any): Promise { +export async function importUserLists( + job: Bull.Job, + done: any, +): Promise { logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); @@ -34,12 +42,12 @@ export async function importUserLists(job: Bull.Job, done: let linenum = 0; - for (const line of csv.trim().split('\n')) { + for (const line of csv.trim().split("\n")) { linenum++; try { - const listName = line.split(',')[0].trim(); - const { username, host } = Acct.parse(line.split(',')[1].trim()); + const listName = line.split(",")[0].trim(); + const { username, host } = Acct.parse(line.split(",")[1].trim()); let list = await UserLists.findOneBy({ userId: user.id, @@ -52,22 +60,30 @@ export async function importUserLists(job: Bull.Job, done: createdAt: new Date(), userId: user.id, name: listName, - }).then(x => UserLists.findOneByOrFail(x.identifiers[0])); + }).then((x) => UserLists.findOneByOrFail(x.identifiers[0])); } - let target = isSelfHost(host!) ? await Users.findOneBy({ - host: IsNull(), - usernameLower: username.toLowerCase(), - }) : await Users.findOneBy({ - host: toPuny(host!), - usernameLower: username.toLowerCase(), - }); + let target = isSelfHost(host!) + ? await Users.findOneBy({ + host: IsNull(), + usernameLower: username.toLowerCase(), + }) + : await Users.findOneBy({ + host: toPuny(host!), + usernameLower: username.toLowerCase(), + }); if (target == null) { target = await resolveUser(username, host); } - if (await UserListJoinings.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; + if ( + (await UserListJoinings.findOneBy({ + userListId: list!.id, + userId: target.id, + })) != null + ) + continue; pushUserToUserList(target, list!); } catch (e) { @@ -75,6 +91,6 @@ export async function importUserLists(job: Bull.Job, done: } } - logger.succ('Imported'); + logger.succ("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts index e91d56977..90173053f 100644 --- a/packages/backend/src/queue/processors/db/index.ts +++ b/packages/backend/src/queue/processors/db/index.ts @@ -1,18 +1,18 @@ -import Bull from 'bull'; -import { DbJobData } from '@/queue/types.js'; -import { deleteDriveFiles } from './delete-drive-files.js'; -import { exportCustomEmojis } from './export-custom-emojis.js'; -import { exportNotes } from './export-notes.js'; -import { exportFollowing } from './export-following.js'; -import { exportMute } from './export-mute.js'; -import { exportBlocking } from './export-blocking.js'; -import { exportUserLists } from './export-user-lists.js'; -import { importFollowing } from './import-following.js'; -import { importUserLists } from './import-user-lists.js'; -import { deleteAccount } from './delete-account.js'; -import { importMuting } from './import-muting.js'; -import { importBlocking } from './import-blocking.js'; -import { importCustomEmojis } from './import-custom-emojis.js'; +import type Bull from "bull"; +import type { DbJobData } from "@/queue/types.js"; +import { deleteDriveFiles } from "./delete-drive-files.js"; +import { exportCustomEmojis } from "./export-custom-emojis.js"; +import { exportNotes } from "./export-notes.js"; +import { exportFollowing } from "./export-following.js"; +import { exportMute } from "./export-mute.js"; +import { exportBlocking } from "./export-blocking.js"; +import { exportUserLists } from "./export-user-lists.js"; +import { importFollowing } from "./import-following.js"; +import { importUserLists } from "./import-user-lists.js"; +import { deleteAccount } from "./delete-account.js"; +import { importMuting } from "./import-muting.js"; +import { importBlocking } from "./import-blocking.js"; +import { importCustomEmojis } from "./import-custom-emojis.js"; const jobs = { deleteDriveFiles, @@ -28,9 +28,13 @@ const jobs = { importUserLists, importCustomEmojis, deleteAccount, -} as Record | Bull.ProcessPromiseFunction>; +} as Record< + string, + | Bull.ProcessCallbackFunction + | Bull.ProcessPromiseFunction +>; -export default function(dbQueue: Bull.Queue) { +export default function (dbQueue: Bull.Queue) { for (const [k, v] of Object.entries(jobs)) { dbQueue.process(k, v); } diff --git a/packages/backend/src/queue/processors/deliver.ts b/packages/backend/src/queue/processors/deliver.ts index df8dbb23e..65471a559 100644 --- a/packages/backend/src/queue/processors/deliver.ts +++ b/packages/backend/src/queue/processors/deliver.ts @@ -1,17 +1,21 @@ -import { URL } from 'node:url'; -import request from '@/remote/activitypub/request.js'; -import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; -import Logger from '@/services/logger.js'; -import { Instances } from '@/models/index.js'; -import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js'; -import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { StatusError } from '@/misc/fetch.js'; -import { shouldSkipInstance } from '@/misc/skipped-instances.js'; -import type { DeliverJobData } from '@/queue/types.js'; -import type Bull from 'bull'; +import { URL } from "node:url"; +import request from "@/remote/activitypub/request.js"; +import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; +import Logger from "@/services/logger.js"; +import { Instances } from "@/models/index.js"; +import { + apRequestChart, + federationChart, + instanceChart, +} from "@/services/chart/index.js"; +import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; +import { toPuny } from "@/misc/convert-host.js"; +import { StatusError } from "@/misc/fetch.js"; +import { shouldSkipInstance } from "@/misc/skipped-instances.js"; +import type { DeliverJobData } from "@/queue/types.js"; +import type Bull from "bull"; -const logger = new Logger('deliver'); +const logger = new Logger("deliver"); let latest: string | null = null; @@ -19,7 +23,7 @@ export default async (job: Bull.Job) => { const { host } = new URL(job.data.to); const puny = toPuny(host); - if (await shouldSkipInstance(puny)) return 'skip'; + if (await shouldSkipInstance(puny)) return "skip"; try { if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) { @@ -29,7 +33,7 @@ export default async (job: Bull.Job) => { await request(job.data.user, job.data.to, job.data.content); // Update stats - registerOrFetchInstanceDoc(host).then(i => { + registerOrFetchInstanceDoc(host).then((i) => { Instances.update(i.id, { latestRequestSentAt: new Date(), latestStatus: 200, @@ -44,10 +48,10 @@ export default async (job: Bull.Job) => { federationChart.deliverd(i.host, true); }); - return 'Success'; + return "Success"; } catch (res) { // Update stats - registerOrFetchInstanceDoc(host).then(i => { + registerOrFetchInstanceDoc(host).then((i) => { Instances.update(i.id, { latestRequestSentAt: new Date(), latestStatus: res instanceof StatusError ? res.statusCode : null, diff --git a/packages/backend/src/queue/processors/ended-poll-notification.ts b/packages/backend/src/queue/processors/ended-poll-notification.ts index 6151c96ad..9fe57d8da 100644 --- a/packages/backend/src/queue/processors/ended-poll-notification.ts +++ b/packages/backend/src/queue/processors/ended-poll-notification.ts @@ -1,30 +1,33 @@ -import Bull from 'bull'; -import { In } from 'typeorm'; -import { Notes, Polls, PollVotes } from '@/models/index.js'; -import { queueLogger } from '../logger.js'; -import { EndedPollNotificationJobData } from '@/queue/types.js'; -import { createNotification } from '@/services/create-notification.js'; +import type Bull from "bull"; +import { In } from "typeorm"; +import { Notes, Polls, PollVotes } from "@/models/index.js"; +import { queueLogger } from "../logger.js"; +import type { EndedPollNotificationJobData } from "@/queue/types.js"; +import { createNotification } from "@/services/create-notification.js"; -const logger = queueLogger.createSubLogger('ended-poll-notification'); +const logger = queueLogger.createSubLogger("ended-poll-notification"); -export async function endedPollNotification(job: Bull.Job, done: any): Promise { +export async function endedPollNotification( + job: Bull.Job, + done: any, +): Promise { const note = await Notes.findOneBy({ id: job.data.noteId }); if (note == null || !note.hasPoll) { done(); return; } - const votes = await PollVotes.createQueryBuilder('vote') - .select('vote.userId') - .where('vote.noteId = :noteId', { noteId: note.id }) - .innerJoinAndSelect('vote.user', 'user') - .andWhere('user.host IS NULL') + const votes = await PollVotes.createQueryBuilder("vote") + .select("vote.userId") + .where("vote.noteId = :noteId", { noteId: note.id }) + .innerJoinAndSelect("vote.user", "user") + .andWhere("user.host IS NULL") .getMany(); - const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])]; + const userIds = [...new Set([note.userId, ...votes.map((v) => v.userId)])]; for (const userId of userIds) { - createNotification(userId, 'pollEnded', { + createNotification(userId, "pollEnded", { noteId: note.id, }); } diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 27a335791..ca063a6f3 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -1,34 +1,38 @@ -import { URL } from 'node:url'; -import Bull from 'bull'; -import httpSignature from '@peertube/http-signature'; -import perform from '@/remote/activitypub/perform.js'; -import Logger from '@/services/logger.js'; -import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; -import { Instances } from '@/models/index.js'; -import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { toPuny, extractDbHost } from '@/misc/convert-host.js'; -import { getApId } from '@/remote/activitypub/type.js'; -import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; -import { InboxJobData } from '../types.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import { resolvePerson } from '@/remote/activitypub/models/person.js'; -import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; -import { StatusError } from '@/misc/fetch.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +import { URL } from "node:url"; +import type Bull from "bull"; +import httpSignature from "@peertube/http-signature"; +import perform from "@/remote/activitypub/perform.js"; +import Logger from "@/services/logger.js"; +import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; +import { Instances } from "@/models/index.js"; +import { + apRequestChart, + federationChart, + instanceChart, +} from "@/services/chart/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { toPuny, extractDbHost } from "@/misc/convert-host.js"; +import { getApId } from "@/remote/activitypub/type.js"; +import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; +import type { InboxJobData } from "../types.js"; +import DbResolver from "@/remote/activitypub/db-resolver.js"; +import { resolvePerson } from "@/remote/activitypub/models/person.js"; +import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js"; +import { StatusError } from "@/misc/fetch.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { UserPublickey } from "@/models/entities/user-publickey.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; -const logger = new Logger('inbox'); +const logger = new Logger("inbox"); // Processing when an activity arrives in the user's inbox export default async (job: Bull.Job): Promise => { - const signature = job.data.signature; // HTTP-signature + const signature = job.data.signature; // HTTP-signature const activity = job.data.activity; //#region Log const info = Object.assign({}, activity) as any; - delete info['@context']; + info["@context"] = undefined; logger.debug(JSON.stringify(info, null, 2)); //#endregion const host = toPuny(new URL(signature.keyId).hostname); @@ -45,7 +49,7 @@ export default async (job: Bull.Job): Promise => { } const keyIdLower = signature.keyId.toLowerCase(); - if (keyIdLower.startsWith('acct:')) { + if (keyIdLower.startsWith("acct:")) { return `Old keyId is no longer supported. ${keyIdLower}`; } @@ -67,54 +71,63 @@ export default async (job: Bull.Job): Promise => { if (e.isClientError) { return `skip: Ignored deleted actors on both ends ${activity.actor} - ${e.statusCode}`; } - throw new Error(`Error in actor ${activity.actor} - ${e.statusCode || e}`); + throw new Error( + `Error in actor ${activity.actor} - ${e.statusCode || e}`, + ); } } } // それでもわからなければ終了 if (authUser == null) { - return `skip: failed to resolve user`; + return "skip: failed to resolve user"; } // publicKey がなくても終了 if (authUser.key == null) { - return `skip: failed to resolve user publicKey`; + return "skip: failed to resolve user publicKey"; } // HTTP-Signatureの検証 - const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem); + const httpSignatureValidated = httpSignature.verifySignature( + signature, + authUser.key.keyPem, + ); // また、signatureのsignerは、activity.actorと一致する必要がある if (!httpSignatureValidated || authUser.user.uri !== activity.actor) { // 一致しなくても、でもLD-Signatureがありそうならそっちも見る if (activity.signature) { - if (activity.signature.type !== 'RsaSignature2017') { + if (activity.signature.type !== "RsaSignature2017") { return `skip: unsupported LD-signature type ${activity.signature.type}`; } // activity.signature.creator: https://example.oom/users/user#main-key // みたいになっててUserを引っ張れば公開キーも入ることを期待する if (activity.signature.creator) { - const candicate = activity.signature.creator.replace(/#.*/, ''); + const candicate = activity.signature.creator.replace(/#.*/, ""); await resolvePerson(candicate).catch(() => null); } // keyIdからLD-Signatureのユーザーを取得 - authUser = await dbResolver.getAuthUserFromKeyId(activity.signature.creator); + authUser = await dbResolver.getAuthUserFromKeyId( + activity.signature.creator, + ); if (authUser == null) { - return `skip: LD-Signatureのユーザーが取得できませんでした`; + return "skip: LD-Signatureのユーザーが取得できませんでした"; } if (authUser.key == null) { - return `skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした`; + return "skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした"; } // LD-Signature検証 const ldSignature = new LdSignature(); - const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false); + const verified = await ldSignature + .verifyRsaSignature2017(activity, authUser.key.keyPem) + .catch(() => false); if (!verified) { - return `skip: LD-Signatureの検証に失敗しました`; + return "skip: LD-Signatureの検証に失敗しました"; } // もう一度actorチェック @@ -133,7 +146,7 @@ export default async (job: Bull.Job): Promise => { } // activity.idがあればホストが署名者のホストであることを確認する - if (typeof activity.id === 'string') { + if (typeof activity.id === "string") { const signerHost = extractDbHost(authUser.user.uri!); const activityIdHost = extractDbHost(activity.id); if (signerHost !== activityIdHost) { @@ -142,7 +155,7 @@ export default async (job: Bull.Job): Promise => { } // Update stats - registerOrFetchInstanceDoc(authUser.user.host).then(i => { + registerOrFetchInstanceDoc(authUser.user.host).then((i) => { Instances.update(i.id, { latestRequestReceivedAt: new Date(), lastCommunicatedAt: new Date(), @@ -158,5 +171,5 @@ export default async (job: Bull.Job): Promise => { // アクティビティを処理 await perform(authUser.user, activity); - return `ok`; + return "ok"; }; diff --git a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts index 77da162f6..fdfe05d1a 100644 --- a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts +++ b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts @@ -1,14 +1,17 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import { deleteFileSync } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; -import { MoreThan, Not, IsNull } from 'typeorm'; +import { queueLogger } from "../../logger.js"; +import { deleteFileSync } from "@/services/drive/delete-file.js"; +import { DriveFiles } from "@/models/index.js"; +import { MoreThan, Not, IsNull } from "typeorm"; -const logger = queueLogger.createSubLogger('clean-remote-files'); +const logger = queueLogger.createSubLogger("clean-remote-files"); -export default async function cleanRemoteFiles(job: Bull.Job>, done: any): Promise { - logger.info(`Deleting cached remote files...`); +export default async function cleanRemoteFiles( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Deleting cached remote files..."); let deletedCount = 0; let cursor: any = null; @@ -33,7 +36,7 @@ export default async function cleanRemoteFiles(job: Bull.Job deleteFileSync(file, true))); + await Promise.all(files.map((file) => deleteFileSync(file, true))); deletedCount += 8; @@ -45,6 +48,6 @@ export default async function cleanRemoteFiles(job: Bull.Job) => { const key: string = job.data.key; await deleteObjectStorageFile(key); - return 'Success'; + return "Success"; }; diff --git a/packages/backend/src/queue/processors/object-storage/index.ts b/packages/backend/src/queue/processors/object-storage/index.ts index ae6c481fe..5f90d4cd0 100644 --- a/packages/backend/src/queue/processors/object-storage/index.ts +++ b/packages/backend/src/queue/processors/object-storage/index.ts @@ -1,14 +1,18 @@ -import Bull from 'bull'; -import { ObjectStorageJobData } from '@/queue/types.js'; -import deleteFile from './delete-file.js'; -import cleanRemoteFiles from './clean-remote-files.js'; +import type Bull from "bull"; +import type { ObjectStorageJobData } from "@/queue/types.js"; +import deleteFile from "./delete-file.js"; +import cleanRemoteFiles from "./clean-remote-files.js"; const jobs = { deleteFile, cleanRemoteFiles, -} as Record | Bull.ProcessPromiseFunction>; +} as Record< + string, + | Bull.ProcessCallbackFunction + | Bull.ProcessPromiseFunction +>; -export default function(q: Bull.Queue) { +export default function (q: Bull.Queue) { for (const [k, v] of Object.entries(jobs)) { q.process(k, 16, v); } diff --git a/packages/backend/src/queue/processors/system/check-expired-mutings.ts b/packages/backend/src/queue/processors/system/check-expired-mutings.ts index 621269e7e..a482d0218 100644 --- a/packages/backend/src/queue/processors/system/check-expired-mutings.ts +++ b/packages/backend/src/queue/processors/system/check-expired-mutings.ts @@ -1,30 +1,33 @@ -import Bull from 'bull'; -import { In } from 'typeorm'; -import { Mutings } from '@/models/index.js'; -import { queueLogger } from '../../logger.js'; -import { publishUserEvent } from '@/services/stream.js'; +import type Bull from "bull"; +import { In } from "typeorm"; +import { Mutings } from "@/models/index.js"; +import { queueLogger } from "../../logger.js"; +import { publishUserEvent } from "@/services/stream.js"; -const logger = queueLogger.createSubLogger('check-expired-mutings'); +const logger = queueLogger.createSubLogger("check-expired-mutings"); -export async function checkExpiredMutings(job: Bull.Job>, done: any): Promise { - logger.info(`Checking expired mutings...`); +export async function checkExpiredMutings( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Checking expired mutings..."); - const expired = await Mutings.createQueryBuilder('muting') - .where('muting.expiresAt IS NOT NULL') - .andWhere('muting.expiresAt < :now', { now: new Date() }) - .innerJoinAndSelect('muting.mutee', 'mutee') + const expired = await Mutings.createQueryBuilder("muting") + .where("muting.expiresAt IS NOT NULL") + .andWhere("muting.expiresAt < :now", { now: new Date() }) + .innerJoinAndSelect("muting.mutee", "mutee") .getMany(); if (expired.length > 0) { await Mutings.delete({ - id: In(expired.map(m => m.id)), + id: In(expired.map((m) => m.id)), }); for (const m of expired) { - publishUserEvent(m.muterId, 'unmute', m.mutee!); + publishUserEvent(m.muterId, "unmute", m.mutee!); } } - logger.succ(`All expired mutings checked.`); + logger.succ("All expired mutings checked."); done(); } diff --git a/packages/backend/src/queue/processors/system/clean-charts.ts b/packages/backend/src/queue/processors/system/clean-charts.ts index c9169d5ac..dde5d95fe 100644 --- a/packages/backend/src/queue/processors/system/clean-charts.ts +++ b/packages/backend/src/queue/processors/system/clean-charts.ts @@ -1,12 +1,28 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from '@/services/chart/index.js'; +import { queueLogger } from "../../logger.js"; +import { + activeUsersChart, + driveChart, + federationChart, + hashtagChart, + instanceChart, + notesChart, + perUserDriveChart, + perUserFollowingChart, + perUserNotesChart, + perUserReactionsChart, + usersChart, + apRequestChart, +} from "@/services/chart/index.js"; -const logger = queueLogger.createSubLogger('clean-charts'); +const logger = queueLogger.createSubLogger("clean-charts"); -export async function cleanCharts(job: Bull.Job>, done: any): Promise { - logger.info(`Clean charts...`); +export async function cleanCharts( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Clean charts..."); await Promise.all([ federationChart.clean(), @@ -23,6 +39,6 @@ export async function cleanCharts(job: Bull.Job>, done: apRequestChart.clean(), ]); - logger.succ(`All charts successfully cleaned.`); + logger.succ("All charts successfully cleaned."); done(); } diff --git a/packages/backend/src/queue/processors/system/clean.ts b/packages/backend/src/queue/processors/system/clean.ts index c4f978d7c..fbd45b0bb 100644 --- a/packages/backend/src/queue/processors/system/clean.ts +++ b/packages/backend/src/queue/processors/system/clean.ts @@ -1,18 +1,21 @@ -import Bull from 'bull'; -import { LessThan } from 'typeorm'; -import { UserIps } from '@/models/index.js'; +import type Bull from "bull"; +import { LessThan } from "typeorm"; +import { UserIps } from "@/models/index.js"; -import { queueLogger } from '../../logger.js'; +import { queueLogger } from "../../logger.js"; -const logger = queueLogger.createSubLogger('clean'); +const logger = queueLogger.createSubLogger("clean"); -export async function clean(job: Bull.Job>, done: any): Promise { - logger.info('Cleaning...'); +export async function clean( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Cleaning..."); UserIps.delete({ - createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))), + createdAt: LessThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }); - logger.succ('Cleaned.'); + logger.succ("Cleaned."); done(); } diff --git a/packages/backend/src/queue/processors/system/index.ts b/packages/backend/src/queue/processors/system/index.ts index 9527d40b0..68833d76f 100644 --- a/packages/backend/src/queue/processors/system/index.ts +++ b/packages/backend/src/queue/processors/system/index.ts @@ -1,9 +1,9 @@ -import Bull from 'bull'; -import { tickCharts } from './tick-charts.js'; -import { resyncCharts } from './resync-charts.js'; -import { cleanCharts } from './clean-charts.js'; -import { checkExpiredMutings } from './check-expired-mutings.js'; -import { clean } from './clean.js'; +import type Bull from "bull"; +import { tickCharts } from "./tick-charts.js"; +import { resyncCharts } from "./resync-charts.js"; +import { cleanCharts } from "./clean-charts.js"; +import { checkExpiredMutings } from "./check-expired-mutings.js"; +import { clean } from "./clean.js"; const jobs = { tickCharts, @@ -11,9 +11,13 @@ const jobs = { cleanCharts, checkExpiredMutings, clean, -} as Record> | Bull.ProcessPromiseFunction>>; +} as Record< + string, + | Bull.ProcessCallbackFunction> + | Bull.ProcessPromiseFunction> +>; -export default function(dbQueue: Bull.Queue>) { +export default function (dbQueue: Bull.Queue>) { for (const [k, v] of Object.entries(jobs)) { dbQueue.process(k, v); } diff --git a/packages/backend/src/queue/processors/system/resync-charts.ts b/packages/backend/src/queue/processors/system/resync-charts.ts index 20012513a..dbea0df73 100644 --- a/packages/backend/src/queue/processors/system/resync-charts.ts +++ b/packages/backend/src/queue/processors/system/resync-charts.ts @@ -1,12 +1,15 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import { driveChart, notesChart, usersChart } from '@/services/chart/index.js'; +import { queueLogger } from "../../logger.js"; +import { driveChart, notesChart, usersChart } from "@/services/chart/index.js"; -const logger = queueLogger.createSubLogger('resync-charts'); +const logger = queueLogger.createSubLogger("resync-charts"); -export async function resyncCharts(job: Bull.Job>, done: any): Promise { - logger.info(`Resync charts...`); +export async function resyncCharts( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Resync charts..."); // TODO: ユーザーごとのチャートも更新する // TODO: インスタンスごとのチャートも更新する @@ -16,6 +19,6 @@ export async function resyncCharts(job: Bull.Job>, done: usersChart.resync(), ]); - logger.succ(`All charts successfully resynced.`); + logger.succ("All charts successfully resynced."); done(); } diff --git a/packages/backend/src/queue/processors/system/tick-charts.ts b/packages/backend/src/queue/processors/system/tick-charts.ts index 13403f8f7..33eed8a59 100644 --- a/packages/backend/src/queue/processors/system/tick-charts.ts +++ b/packages/backend/src/queue/processors/system/tick-charts.ts @@ -1,12 +1,28 @@ -import Bull from 'bull'; +import type Bull from "bull"; -import { queueLogger } from '../../logger.js'; -import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from '@/services/chart/index.js'; +import { queueLogger } from "../../logger.js"; +import { + activeUsersChart, + driveChart, + federationChart, + hashtagChart, + instanceChart, + notesChart, + perUserDriveChart, + perUserFollowingChart, + perUserNotesChart, + perUserReactionsChart, + usersChart, + apRequestChart, +} from "@/services/chart/index.js"; -const logger = queueLogger.createSubLogger('tick-charts'); +const logger = queueLogger.createSubLogger("tick-charts"); -export async function tickCharts(job: Bull.Job>, done: any): Promise { - logger.info(`Tick charts...`); +export async function tickCharts( + job: Bull.Job>, + done: any, +): Promise { + logger.info("Tick charts..."); await Promise.all([ federationChart.tick(false), @@ -23,6 +39,6 @@ export async function tickCharts(job: Bull.Job>, done: a apRequestChart.tick(false), ]); - logger.succ(`All charts successfully ticked.`); + logger.succ("All charts successfully ticked."); done(); } diff --git a/packages/backend/src/queue/processors/webhook-deliver.ts b/packages/backend/src/queue/processors/webhook-deliver.ts index f69518388..a130fcd38 100644 --- a/packages/backend/src/queue/processors/webhook-deliver.ts +++ b/packages/backend/src/queue/processors/webhook-deliver.ts @@ -1,12 +1,12 @@ -import { URL } from 'node:url'; -import Bull from 'bull'; -import Logger from '@/services/logger.js'; -import { WebhookDeliverJobData } from '../types.js'; -import { getResponse, StatusError } from '@/misc/fetch.js'; -import { Webhooks } from '@/models/index.js'; -import config from '@/config/index.js'; +import { URL } from "node:url"; +import type Bull from "bull"; +import Logger from "@/services/logger.js"; +import type { WebhookDeliverJobData } from "../types.js"; +import { getResponse, StatusError } from "@/misc/fetch.js"; +import { Webhooks } from "@/models/index.js"; +import config from "@/config/index.js"; -const logger = new Logger('webhook'); +const logger = new Logger("webhook"); export default async (job: Bull.Job) => { try { @@ -14,12 +14,12 @@ export default async (job: Bull.Job) => { const res = await getResponse({ url: job.data.to, - method: 'POST', + method: "POST", headers: { - 'User-Agent': 'Calckey-Hooks', - 'X-Calckey-Host': config.host, - 'X-Calckey-Hook-Id': job.data.webhookId, - 'X-Calckey-Hook-Secret': job.data.secret, + "User-Agent": "Calckey-Hooks", + "X-Calckey-Host": config.host, + "X-Calckey-Hook-Id": job.data.webhookId, + "X-Calckey-Hook-Secret": job.data.secret, }, body: JSON.stringify({ hookId: job.data.webhookId, @@ -31,17 +31,23 @@ export default async (job: Bull.Job) => { }), }); - Webhooks.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), - latestStatus: res.status, - }); + Webhooks.update( + { id: job.data.webhookId }, + { + latestSentAt: new Date(), + latestStatus: res.status, + }, + ); - return 'Success'; + return "Success"; } catch (res) { - Webhooks.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), - latestStatus: res instanceof StatusError ? res.statusCode : 1, - }); + Webhooks.update( + { id: job.data.webhookId }, + { + latestSentAt: new Date(), + latestStatus: res instanceof StatusError ? res.statusCode : 1, + }, + ); if (res instanceof StatusError) { // 4xx diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts index f3a267790..12d9d6620 100644 --- a/packages/backend/src/queue/queues.ts +++ b/packages/backend/src/queue/queues.ts @@ -1,14 +1,32 @@ -import config from '@/config/index.js'; -import { initialize as initializeQueue } from './initialize.js'; -import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from './types.js'; +import config from "@/config/index.js"; +import { initialize as initializeQueue } from "./initialize.js"; +import type { + DeliverJobData, + InboxJobData, + DbJobData, + ObjectStorageJobData, + EndedPollNotificationJobData, + WebhookDeliverJobData, +} from "./types.js"; -export const systemQueue = initializeQueue>('system'); -export const endedPollNotificationQueue = initializeQueue('endedPollNotification'); -export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128); -export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); -export const dbQueue = initializeQueue('db'); -export const objectStorageQueue = initializeQueue('objectStorage'); -export const webhookDeliverQueue = initializeQueue('webhookDeliver', 64); +export const systemQueue = initializeQueue>("system"); +export const endedPollNotificationQueue = + initializeQueue("endedPollNotification"); +export const deliverQueue = initializeQueue( + "deliver", + config.deliverJobPerSec || 128, +); +export const inboxQueue = initializeQueue( + "inbox", + config.inboxJobPerSec || 16, +); +export const dbQueue = initializeQueue("db"); +export const objectStorageQueue = + initializeQueue("objectStorage"); +export const webhookDeliverQueue = initializeQueue( + "webhookDeliver", + 64, +); export const queues = [ systemQueue, diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 5ea472556..90e88f736 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -1,9 +1,9 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { Note } from '@/models/entities/note'; -import { User } from '@/models/entities/user.js'; -import { Webhook } from '@/models/entities/webhook'; -import { IActivity } from '@/remote/activitypub/type.js'; -import httpSignature from '@peertube/http-signature'; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { Note } from "@/models/entities/note"; +import type { User } from "@/models/entities/user.js"; +import type { Webhook } from "@/models/entities/webhook"; +import type { IActivity } from "@/remote/activitypub/type.js"; +import type httpSignature from "@peertube/http-signature"; export type DeliverJobData = { /** Actor */ @@ -19,7 +19,10 @@ export type InboxJobData = { signature: httpSignature.IParsedSignature; }; -export type DbJobData = DbUserJobData | DbUserImportJobData | DbUserDeleteJobData; +export type DbJobData = + | DbUserJobData + | DbUserImportJobData + | DbUserDeleteJobData; export type DbUserJobData = { user: ThinUser; @@ -34,24 +37,26 @@ export type DbUserDeleteJobData = { export type DbUserImportJobData = { user: ThinUser; - fileId: DriveFile['id']; + fileId: DriveFile["id"]; }; -export type ObjectStorageJobData = ObjectStorageFileJobData | Record; +export type ObjectStorageJobData = + | ObjectStorageFileJobData + | Record; export type ObjectStorageFileJobData = { key: string; }; export type EndedPollNotificationJobData = { - noteId: Note['id']; + noteId: Note["id"]; }; export type WebhookDeliverJobData = { type: string; content: unknown; - webhookId: Webhook['id']; - userId: User['id']; + webhookId: Webhook["id"]; + userId: User["id"]; to: string; secret: string; createdAt: number; @@ -59,5 +64,5 @@ export type WebhookDeliverJobData = { }; export type ThinUser = { - id: User['id']; + id: User["id"]; }; diff --git a/packages/backend/src/remote/activitypub/ap-request.ts b/packages/backend/src/remote/activitypub/ap-request.ts index 8b55f2247..d5a9ec053 100644 --- a/packages/backend/src/remote/activitypub/ap-request.ts +++ b/packages/backend/src/remote/activitypub/ap-request.ts @@ -1,5 +1,5 @@ -import * as crypto from 'node:crypto'; -import { URL } from 'node:url'; +import * as crypto from "node:crypto"; +import { URL } from "node:url"; type Request = { url: string; @@ -12,22 +12,38 @@ type PrivateKey = { keyId: string; }; -export function createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }) { +export function createSignedPost(args: { + key: PrivateKey; + url: string; + body: string; + additionalHeaders: Record; +}) { const u = new URL(args.url); - const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; + const digestHeader = `SHA-256=${crypto + .createHash("sha256") + .update(args.body) + .digest("base64")}`; const request: Request = { url: u.href, - method: 'POST', - headers: objectAssignWithLcKey({ - 'Date': new Date().toUTCString(), - 'Host': u.hostname, - 'Content-Type': 'application/activity+json', - 'Digest': digestHeader, - }, args.additionalHeaders), + method: "POST", + headers: objectAssignWithLcKey( + { + Date: new Date().toUTCString(), + Host: u.hostname, + "Content-Type": "application/activity+json", + Digest: digestHeader, + }, + args.additionalHeaders, + ), }; - const result = signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'digest']); + const result = signToRequest(request, args.key, [ + "(request-target)", + "date", + "host", + "digest", + ]); return { request, @@ -37,20 +53,32 @@ export function createSignedPost(args: { key: PrivateKey, url: string, body: str }; } -export function createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }) { +export function createSignedGet(args: { + key: PrivateKey; + url: string; + additionalHeaders: Record; +}) { const u = new URL(args.url); const request: Request = { url: u.href, - method: 'GET', - headers: objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', - 'Date': new Date().toUTCString(), - 'Host': new URL(args.url).hostname, - }, args.additionalHeaders), + method: "GET", + headers: objectAssignWithLcKey( + { + Accept: "application/activity+json, application/ld+json", + Date: new Date().toUTCString(), + Host: new URL(args.url).hostname, + }, + args.additionalHeaders, + ), }; - const result = signToRequest(request, args.key, ['(request-target)', 'date', 'host', 'accept']); + const result = signToRequest(request, args.key, [ + "(request-target)", + "date", + "host", + "accept", + ]); return { request, @@ -60,10 +88,20 @@ export function createSignedGet(args: { key: PrivateKey, url: string, additional }; } -function signToRequest(request: Request, key: PrivateKey, includeHeaders: string[]) { +function signToRequest( + request: Request, + key: PrivateKey, + includeHeaders: string[], +) { const signingString = 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}"`; + 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 = objectAssignWithLcKey(request.headers, { Signature: signatureHeader, @@ -82,23 +120,33 @@ function genSigningString(request: Request, includeHeaders: string[]) { 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}`); + 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'); + return results.join("\n"); } function lcObjectKey(src: Record) { const dst: Record = {}; - for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; + for (const key of Object.keys(src).filter( + (x) => x !== "__proto__" && typeof src[x] === "string", + )) + dst[key.toLowerCase()] = src[key]; return dst; } -function objectAssignWithLcKey(a: Record, b: Record) { +function objectAssignWithLcKey( + a: Record, + b: Record, +) { return Object.assign(lcObjectKey(a), lcObjectKey(b)); } diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts index 846ccf9c0..210d47573 100644 --- a/packages/backend/src/remote/activitypub/audience.ts +++ b/packages/backend/src/remote/activitypub/audience.ts @@ -1,32 +1,46 @@ -import { ApObject, getApIds } from './type.js'; -import Resolver from './resolver.js'; -import { resolvePerson } from './models/person.js'; -import { unique, concat } from '@/prelude/array.js'; -import promiseLimit from 'promise-limit'; -import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; +import type { ApObject } from "./type.js"; +import { getApIds } from "./type.js"; +import type Resolver from "./resolver.js"; +import { resolvePerson } from "./models/person.js"; +import { unique, concat } from "@/prelude/array.js"; +import promiseLimit from "promise-limit"; +import type { + CacheableRemoteUser, + CacheableUser, +} from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; -type Visibility = 'public' | 'home' | 'followers' | 'specified'; +type Visibility = "public" | "home" | "followers" | "specified"; type AudienceInfo = { - visibility: Visibility, - mentionedUsers: CacheableUser[], - visibleUsers: CacheableUser[], + visibility: Visibility; + mentionedUsers: CacheableUser[]; + visibleUsers: CacheableUser[]; }; -export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { +export async function parseAudience( + actor: CacheableRemoteUser, + to?: ApObject, + cc?: ApObject, + resolver?: Resolver, +): Promise { const toGroups = groupingAudience(getApIds(to), actor); const ccGroups = 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(() => resolvePerson(id, resolver).catch(() => null))) - )).filter((x): x is CacheableUser => x != null); + const mentionedUsers = ( + await Promise.all( + others.map((id) => + limit(() => resolvePerson(id, resolver).catch(() => null)), + ), + ) + ).filter((x): x is CacheableUser => x != null); if (toGroups.public.length > 0) { return { - visibility: 'public', + visibility: "public", mentionedUsers, visibleUsers: [], }; @@ -34,7 +48,7 @@ export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, c if (ccGroups.public.length > 0) { return { - visibility: 'home', + visibility: "home", mentionedUsers, visibleUsers: [], }; @@ -42,14 +56,14 @@ export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, c if (toGroups.followers.length > 0) { return { - visibility: 'followers', + visibility: "followers", mentionedUsers, visibleUsers: [], }; } return { - visibility: 'specified', + visibility: "specified", mentionedUsers, visibleUsers: mentionedUsers, }; @@ -79,14 +93,12 @@ function groupingAudience(ids: string[], actor: CacheableRemoteUser) { function isPublic(id: string) { return [ - 'https://www.w3.org/ns/activitystreams#Public', - 'as#Public', - 'Public', + "https://www.w3.org/ns/activitystreams#Public", + "as#Public", + "Public", ].includes(id); } function isFollowers(id: string, actor: CacheableRemoteUser) { - return ( - id === (actor.followersUri || `${actor.uri}/followers`) - ); + return id === (actor.followersUri || `${actor.uri}/followers`); } diff --git a/packages/backend/src/remote/activitypub/check-fetch.ts b/packages/backend/src/remote/activitypub/check-fetch.ts index 8412093a2..a8bbe61b8 100644 --- a/packages/backend/src/remote/activitypub/check-fetch.ts +++ b/packages/backend/src/remote/activitypub/check-fetch.ts @@ -1,26 +1,26 @@ -import { URL } from 'url'; -import httpSignature from '@peertube/http-signature'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { toPuny } from '@/misc/convert-host.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import { getApId } from '@/remote/activitypub/type.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; -import type { IncomingMessage } from 'http'; +import { URL } from "url"; +import httpSignature from "@peertube/http-signature"; +import config from "@/config/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { toPuny } from "@/misc/convert-host.js"; +import DbResolver from "@/remote/activitypub/db-resolver.js"; +import { getApId } from "@/remote/activitypub/type.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; +import type { IncomingMessage } from "http"; export async function hasSignature(req: IncomingMessage): Promise { const meta = await fetchMeta(); - const required = (meta.secureMode || meta.privateMode) + const required = meta.secureMode || meta.privateMode; try { - httpSignature.parseRequest(req, { 'headers': [] }); + httpSignature.parseRequest(req, { headers: [] }); } catch (e) { - if (e instanceof Error && e.name === 'MissingHeaderError') { - return required ? 'missing' : 'optional'; + if (e instanceof Error && e.name === "MissingHeaderError") { + return required ? "missing" : "optional"; } - return 'invalid'; + return "invalid"; } - return required ? 'supplied' : 'unneeded'; + return required ? "supplied" : "unneeded"; } export async function checkFetch(req: IncomingMessage): Promise { @@ -29,7 +29,7 @@ export async function checkFetch(req: IncomingMessage): Promise { let signature; try { - signature = httpSignature.parseRequest(req, { 'headers': [] }); + signature = httpSignature.parseRequest(req, { headers: [] }); } catch (e) { return 401; } @@ -41,12 +41,16 @@ export async function checkFetch(req: IncomingMessage): Promise { return 403; } - if (meta.privateMode && host !== config.host && !meta.allowedHosts.includes(host)) { + if ( + meta.privateMode && + host !== config.host && + !meta.allowedHosts.includes(host) + ) { return 403; } const keyIdLower = signature.keyId.toLowerCase(); - if (keyIdLower.startsWith('acct:')) { + if (keyIdLower.startsWith("acct:")) { // Old keyId is no longer supported. return 401; } @@ -59,8 +63,10 @@ export async function checkFetch(req: IncomingMessage): Promise { // keyIdでわからなければ、resolveしてみる if (authUser == null) { try { - keyId.hash = ''; - authUser = await dbResolver.getAuthUserFromApId(getApId(keyId.toString())); + keyId.hash = ""; + authUser = await dbResolver.getAuthUserFromApId( + getApId(keyId.toString()), + ); } catch (e) { // できなければ駄目 return 403; @@ -78,7 +84,10 @@ export async function checkFetch(req: IncomingMessage): Promise { } // HTTP-Signatureの検証 - const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem); + const httpSignatureValidated = httpSignature.verifySignature( + signature, + authUser.key.keyPem, + ); if (!httpSignatureValidated) { return 403; diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 1a02f675c..0a2aec9e8 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -1,39 +1,54 @@ -import escapeRegexp from 'escape-regexp'; -import config from '@/config/index.js'; -import { Note } from '@/models/entities/note.js'; -import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; -import { Cache } from '@/misc/cache.js'; -import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; -import { IObject, getApId } from './type.js'; -import { resolvePerson } from './models/person.js'; +import escapeRegexp from "escape-regexp"; +import config from "@/config/index.js"; +import type { Note } from "@/models/entities/note.js"; +import type { + CacheableRemoteUser, + CacheableUser, +} from "@/models/entities/user.js"; +import { User, IRemoteUser } from "@/models/entities/user.js"; +import type { UserPublickey } from "@/models/entities/user-publickey.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { + Notes, + Users, + UserPublickeys, + MessagingMessages, +} from "@/models/index.js"; +import { Cache } from "@/misc/cache.js"; +import { uriPersonCache, userByIdCache } from "@/services/user-cache.js"; +import type { IObject } from "./type.js"; +import { getApId } from "./type.js"; +import { resolvePerson } from "./models/person.js"; const publicKeyCache = new Cache(Infinity); const publicKeyByUserIdCache = new Cache(Infinity); -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; -}; +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; + }; export function 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(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); + const localRegex = new RegExp( + `^${escapeRegexp(config.url)}/(\\w+)/(\\w+)(?:/(.+))?`, + "i", + ); const matchLocal = uri.match(localRegex); if (matchLocal) { @@ -52,8 +67,7 @@ export function parseUri(value: string | IObject): UriParseResult { } export default class DbResolver { - constructor() { - } + constructor() {} /** * AP Note => Misskey Note in DB @@ -62,7 +76,7 @@ export default class DbResolver { const parsed = parseUri(value); if (parsed.local) { - if (parsed.type !== 'notes') return null; + if (parsed.type !== "notes") return null; return await Notes.findOneBy({ id: parsed.id, @@ -74,11 +88,13 @@ export default class DbResolver { } } - public async getMessageFromApId(value: string | IObject): Promise { + public async getMessageFromApId( + value: string | IObject, + ): Promise { const parsed = parseUri(value); if (parsed.local) { - if (parsed.type !== 'notes') return null; + if (parsed.type !== "notes") return null; return await MessagingMessages.findOneBy({ id: parsed.id, @@ -93,19 +109,27 @@ export default class DbResolver { /** * AP Person => Misskey User in DB */ - public async getUserFromApId(value: string | IObject): Promise { + public async getUserFromApId( + value: string | IObject, + ): Promise { const parsed = parseUri(value); if (parsed.local) { - if (parsed.type !== 'users') return null; + if (parsed.type !== "users") return null; - return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({ - id: parsed.id, - }).then(x => x ?? undefined)) ?? null; + return ( + (await userByIdCache.fetchMaybe(parsed.id, () => + Users.findOneBy({ + id: parsed.id, + }).then((x) => x ?? undefined), + )) ?? null + ); } else { - return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({ - uri: parsed.uri, - })); + return await uriPersonCache.fetch(parsed.uri, () => + Users.findOneBy({ + uri: parsed.uri, + }), + ); } } @@ -116,20 +140,26 @@ export default class DbResolver { user: CacheableRemoteUser; key: UserPublickey; } | null> { - const key = await publicKeyCache.fetch(keyId, async () => { - const key = await UserPublickeys.findOneBy({ - keyId, - }); - - if (key == null) return null; + const key = await publicKeyCache.fetch( + keyId, + async () => { + const key = await UserPublickeys.findOneBy({ + keyId, + }); - return key; - }, key => key != null); + if (key == null) return null; + + return key; + }, + (key) => key != null, + ); if (key == null) return null; return { - user: await userByIdCache.fetch(key.userId, () => Users.findOneByOrFail({ id: key.userId })) as CacheableRemoteUser, + user: (await userByIdCache.fetch(key.userId, () => + Users.findOneByOrFail({ id: key.userId }), + )) as CacheableRemoteUser, key, }; } @@ -141,11 +171,15 @@ export default class DbResolver { user: CacheableRemoteUser; key: UserPublickey | null; } | null> { - const user = await resolvePerson(uri) as CacheableRemoteUser; + const user = (await resolvePerson(uri)) as CacheableRemoteUser; if (user == null) return null; - const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOneBy({ userId: user.id }), v => v != null); + const key = await publicKeyByUserIdCache.fetch( + user.id, + () => UserPublickeys.findOneBy({ userId: user.id }), + (v) => v != null, + ); return { user, diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 1bcdcdfdb..400e04777 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -1,8 +1,8 @@ -import { IsNull, Not } from 'typeorm'; -import { Users, Followings } from '@/models/index.js'; -import type { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js'; -import { deliver } from '@/queue/index.js'; -import { skippedInstances } from '@/misc/skipped-instances.js'; +import { IsNull, Not } from "typeorm"; +import { Users, Followings } from "@/models/index.js"; +import type { ILocalUser, IRemoteUser, User } from "@/models/entities/user.js"; +import { deliver } from "@/queue/index.js"; +import { skippedInstances } from "@/misc/skipped-instances.js"; //#region types interface IRecipe { @@ -10,23 +10,23 @@ interface IRecipe { } interface IFollowersRecipe extends IRecipe { - type: 'Followers'; + type: "Followers"; } interface IDirectRecipe extends IRecipe { - type: 'Direct'; + type: "Direct"; to: IRemoteUser; } const isFollowers = (recipe: any): recipe is IFollowersRecipe => - recipe.type === 'Followers'; + recipe.type === "Followers"; const isDirect = (recipe: any): recipe is IDirectRecipe => - recipe.type === 'Direct'; + recipe.type === "Direct"; //#endregion export default class DeliverManager { - private actor: { id: User['id']; host: null; }; + private actor: { id: User["id"]; host: null }; private activity: any; private recipes: IRecipe[] = []; @@ -35,7 +35,7 @@ export default class DeliverManager { * @param actor Actor * @param activity Activity to deliver */ - constructor(actor: { id: User['id']; host: null; }, activity: any) { + constructor(actor: { id: User["id"]; host: null }, activity: any) { this.actor = actor; this.activity = activity; } @@ -45,7 +45,7 @@ export default class DeliverManager { */ public addFollowersRecipe() { const deliver = { - type: 'Followers', + type: "Followers", } as IFollowersRecipe; this.addRecipe(deliver); @@ -57,7 +57,7 @@ export default class DeliverManager { */ public addDirectRecipe(to: IRemoteUser) { const recipe = { - type: 'Direct', + type: "Direct", to, } as IDirectRecipe; @@ -86,11 +86,11 @@ export default class DeliverManager { Process follower recipes first to avoid duplication when processing direct recipes later. */ - if (this.recipes.some(r => isFollowers(r))) { + if (this.recipes.some((r) => isFollowers(r))) { // followers deliver // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? - const followers = await Followings.find({ + const followers = (await Followings.find({ where: { followeeId: this.actor.id, followerHost: Not(IsNull()), @@ -99,7 +99,7 @@ export default class DeliverManager { followerSharedInbox: true, followerInbox: true, }, - }) as { + })) as { followerSharedInbox: string | null; followerInbox: string; }[]; @@ -110,22 +110,23 @@ export default class DeliverManager { } } - 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!)); + 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!)); const instancesToSkip = await skippedInstances( // get (unique) list of hosts - Array.from(new Set( - Array.from(inboxes) - .map(inbox => new URL(inbox).host), - )), + Array.from( + new Set(Array.from(inboxes).map((inbox) => new URL(inbox).host)), + ), ); // deliver @@ -144,7 +145,10 @@ export default class DeliverManager { * @param activity Activity * @param from Followee */ -export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { +export async function deliverToFollowers( + actor: { id: ILocalUser["id"]; host: null }, + activity: any, +) { const manager = new DeliverManager(actor, activity); manager.addFollowersRecipe(); await manager.execute(); @@ -155,7 +159,11 @@ export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: nu * @param activity Activity * @param to Target user */ -export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { +export async function deliverToUser( + actor: { id: ILocalUser["id"]; host: null }, + activity: any, + to: IRemoteUser, +) { const manager = new DeliverManager(actor, activity); manager.addDirectRecipe(to); await manager.execute(); diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts index 4350ef133..e430bbf57 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts @@ -1,21 +1,24 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import accept from '@/services/following/requests/accept.js'; -import { IFollow } from '../../type.js'; -import DbResolver from '../../db-resolver.js'; -import { relayAccepted } from '@/services/relay.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import accept from "@/services/following/requests/accept.js"; +import type { IFollow } from "../../type.js"; +import DbResolver from "../../db-resolver.js"; +import { relayAccepted } from "@/services/relay.js"; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IFollow, +): Promise => { // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.actor); if (follower == null) { - return `skip: follower not found`; + return "skip: follower not found"; } if (follower.host != null) { - return `skip: follower is not a local user`; + return "skip: follower is not a local user"; } // relay @@ -25,5 +28,5 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IAccept, +): Promise => { const uri = activity.id || activity; logger.info(`Accept: ${uri}`); const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { + const object = await resolver.resolve(activity.object).catch((e) => { logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts index c813414f9..b3606e5d9 100644 --- a/packages/backend/src/remote/activitypub/kernel/add/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts @@ -1,20 +1,23 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IAdd } from '../../type.js'; -import { resolveNote } from '../../models/note.js'; -import { addPinned } from '@/services/i/pin.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { IAdd } from "../../type.js"; +import { resolveNote } from "../../models/note.js"; +import { addPinned } from "@/services/i/pin.js"; -export default async (actor: CacheableRemoteUser, activity: IAdd): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); +export default async ( + 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'); + throw new Error("target is null"); } if (activity.target === actor.featured) { const note = await resolveNote(activity.object); - if (note == null) throw new Error('note not found'); + if (note == null) throw new Error("note not found"); await addPinned(actor, note.id); return; } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index ae7e507c9..975e070f9 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -1,12 +1,16 @@ -import Resolver from '../../resolver.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import announceNote from './note.js'; -import { IAnnounce, getApId } from '../../type.js'; -import { apLogger } from '../../logger.js'; +import Resolver from "../../resolver.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import announceNote from "./note.js"; +import type { IAnnounce } from "../../type.js"; +import { getApId } from "../../type.js"; +import { apLogger } from "../../logger.js"; const logger = apLogger; -export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IAnnounce, +): Promise => { const uri = getApId(activity); logger.info(`Announce: ${uri}`); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 464a8d13a..6cdaa6166 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,22 +1,28 @@ -import Resolver from '../../resolver.js'; -import post from '@/services/note/create.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IAnnounce, getApId } from '../../type.js'; -import { fetchNote, resolveNote } from '../../models/note.js'; -import { apLogger } from '../../logger.js'; -import { extractDbHost } from '@/misc/convert-host.js'; -import { getApLock } from '@/misc/app-lock.js'; -import { parseAudience } from '../../audience.js'; -import { StatusError } from '@/misc/fetch.js'; -import { Notes } from '@/models/index.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +import type Resolver from "../../resolver.js"; +import post from "@/services/note/create.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { IAnnounce } from "../../type.js"; +import { getApId } from "../../type.js"; +import { fetchNote, resolveNote } from "../../models/note.js"; +import { apLogger } from "../../logger.js"; +import { extractDbHost } from "@/misc/convert-host.js"; +import { getApLock } from "@/misc/app-lock.js"; +import { parseAudience } from "../../audience.js"; +import { StatusError } from "@/misc/fetch.js"; +import { Notes } from "@/models/index.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; const logger = apLogger; /** * Handle announcement activities */ -export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { +export default async function ( + resolver: Resolver, + actor: CacheableRemoteUser, + activity: IAnnounce, + targetUri: string, +): Promise { const uri = getApId(activity); if (actor.isSuspended) { @@ -47,16 +53,23 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac return; } - logger.warn(`Error in announce target ${targetUri} - ${e.statusCode || e}`); + logger.warn( + `Error in announce target ${targetUri} - ${e.statusCode || e}`, + ); } throw e; } - if (!await Notes.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; + if (!(await Notes.isVisibleForMe(renote, actor.id))) + return "skip: invalid actor for this activity"; logger.info(`Creating the (Re)Note: ${uri}`); - const activityAudience = await parseAudience(actor, activity.to, activity.cc); + const activityAudience = await parseAudience( + actor, + activity.to, + activity.cc, + ); await post(actor, { createdAt: activity.published ? new Date(activity.published) : null, diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts index c8b60f7b9..4dc868ba1 100644 --- a/packages/backend/src/remote/activitypub/kernel/block/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts @@ -1,23 +1,29 @@ -import { IBlock } from '../../type.js'; -import block from '@/services/blocking/create.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import DbResolver from '../../db-resolver.js'; -import { Users } from '@/models/index.js'; +import type { IBlock } from "../../type.js"; +import block from "@/services/blocking/create.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import DbResolver from "../../db-resolver.js"; +import { Users } from "@/models/index.js"; -export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IBlock, +): Promise => { // ※ There is a block target in activity.object, which should be a local user that exists. const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); if (blockee == null) { - return `skip: blockee not found`; + return "skip: blockee not found"; } if (blockee.host != null) { - return `skip: The user you are trying to block is not a local user`; + return "skip: The user you are trying to block is not a local user"; } - await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id })); - return `ok`; + await block( + await Users.findOneByOrFail({ id: actor.id }), + await Users.findOneByOrFail({ id: blockee.id }), + ); + return "ok"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index c253f9f66..3dcf64824 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -1,21 +1,29 @@ -import Resolver from '../../resolver.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import createNote from './note.js'; -import { ICreate, getApId, isPost, getApType } from '../../type.js'; -import { apLogger } from '../../logger.js'; -import { toArray, concat, unique } from '@/prelude/array.js'; +import Resolver from "../../resolver.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import createNote from "./note.js"; +import type { ICreate } from "../../type.js"; +import { getApId, isPost, getApType } from "../../type.js"; +import { apLogger } from "../../logger.js"; +import { toArray, concat, unique } from "@/prelude/array.js"; const logger = apLogger; -export default async (actor: CacheableRemoteUser, activity: ICreate): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: ICreate, +): Promise => { const uri = getApId(activity); 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)])); + 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; @@ -24,13 +32,13 @@ export default async (actor: CacheableRemoteUser, activity: ICreate): Promise { + const object = await resolver.resolve(activity.object).catch((e) => { logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts index d7ee2d8c7..09c492730 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts @@ -1,25 +1,32 @@ -import Resolver from '../../resolver.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { createNote, fetchNote } from '../../models/note.js'; -import { getApId, IObject, ICreate } from '../../type.js'; -import { getApLock } from '@/misc/app-lock.js'; -import { extractDbHost } from '@/misc/convert-host.js'; -import { StatusError } from '@/misc/fetch.js'; +import type Resolver from "../../resolver.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { createNote, fetchNote } from "../../models/note.js"; +import type { IObject, ICreate } from "../../type.js"; +import { getApId } from "../../type.js"; +import { getApLock } from "@/misc/app-lock.js"; +import { extractDbHost } from "@/misc/convert-host.js"; +import { StatusError } from "@/misc/fetch.js"; /** * Handle post creation activity */ -export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { +export default async function ( + resolver: Resolver, + actor: CacheableRemoteUser, + note: IObject, + silent = false, + activity?: ICreate, +): Promise { const uri = getApId(note); - if (typeof note === 'object') { + if (typeof note === "object") { if (actor.uri !== note.attributedTo) { - return `skip: actor.uri !== note.attributedTo`; + return "skip: actor.uri !== note.attributedTo"; } - if (typeof note.id === 'string') { + if (typeof note.id === "string") { if (extractDbHost(actor.uri) !== extractDbHost(note.id)) { - return `skip: host in actor.uri !== note.id`; + return "skip: host in actor.uri !== note.id"; } } } @@ -28,10 +35,10 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, no try { const exist = await fetchNote(note); - if (exist) return 'skip: note exists'; + if (exist) return "skip: note exists"; await createNote(note, resolver, silent); - return 'ok'; + return "ok"; } catch (e) { if (e instanceof StatusError && e.isClientError) { return `skip ${e.statusCode}`; diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 1f94df033..3571135aa 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -1,11 +1,14 @@ -import { apLogger } from '../../logger.js'; -import { createDeleteAccountJob } from '@/queue/index.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; +import { apLogger } from "../../logger.js"; +import { createDeleteAccountJob } from "@/queue/index.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; const logger = apLogger; -export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise { +export async function deleteActor( + actor: CacheableRemoteUser, + uri: string, +): Promise { logger.info(`Deleting the Actor: ${uri}`); if (actor.uri !== uri) { @@ -14,7 +17,7 @@ export async function deleteActor(actor: CacheableRemoteUser, uri: string): Prom const user = await Users.findOneByOrFail({ id: actor.id }); if (user.isDeleted) { - logger.info(`skip: already deleted`); + logger.info("skip: already deleted"); } const job = await createDeleteAccountJob(actor); diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index 01ff8f5db..f9ad52de5 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -1,51 +1,55 @@ -import deleteNote from './note.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js'; -import { toSingle } from '@/prelude/array.js'; -import { deleteActor } from './actor.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { toSingle } from "@/prelude/array.js"; +import { getApId, isTombstone, validPost, validActor } from "../../type.js"; +import deleteNote from "./note.js"; +import { deleteActor } from "./actor.js"; +import type { IDelete, IObject } from "../../type.js"; /** * Handle delete activity */ -export default async (actor: CacheableRemoteUser, activity: IDelete): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); - } - - // Type of object to be deleted - let formerType: string | undefined; - - if (typeof activity.object === 'string') { +export default async ( + actor: CacheableRemoteUser, + activity: IDelete, +): Promise => { + if ("actor" in activity && actor.uri !== activity.actor) { + throw new Error("invalid actor"); + } + + // Type of object to be deleted + let formerType: string | undefined; + + if (typeof activity.object === "string") { // The type is unknown, but it has disappeared // anyway, so it does not remote resolve - formerType = undefined; + formerType = undefined; } else { - const object = activity.object as IObject; - if (isTombstone(object)) { + const object = activity.object as IObject; + if (isTombstone(object)) { formerType = toSingle(object.formerType); - } else { + } else { formerType = toSingle(object.type); - } + } + } + + const uri = getApId(activity.object); + + // Even if type is unknown, if actor and object are the same, + // it must be `Person`. + if (!formerType && actor.uri === uri) { + formerType = "Person"; } - - const uri = getApId(activity.object); - - // Even if type is unknown, if actor and object are the same, - // it must be `Person`. - if (!formerType && actor.uri === uri) { - formerType = 'Person'; - } // If not, fallback to `Note`. - if (!formerType) { - formerType = 'Note'; - } + if (!formerType) { + formerType = "Note"; + } - if (validPost.includes(formerType)) { + if (validPost.includes(formerType)) { return await deleteNote(actor, uri); - } else if (validActor.includes(formerType)) { + } else if (validActor.includes(formerType)) { return await deleteActor(actor, uri); - } else { + } else { return `Unknown type ${formerType}`; - } + } }; diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index df416b2ba..69298e917 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -1,13 +1,16 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import deleteNode from '@/services/note/delete.js'; -import { apLogger } from '../../logger.js'; -import DbResolver from '../../db-resolver.js'; -import { getApLock } from '@/misc/app-lock.js'; -import { deleteMessage } from '@/services/messages/delete.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import deleteNode from "@/services/note/delete.js"; +import { apLogger } from "../../logger.js"; +import DbResolver from "../../db-resolver.js"; +import { getApLock } from "@/misc/app-lock.js"; +import { deleteMessage } from "@/services/messages/delete.js"; const logger = apLogger; -export default async function(actor: CacheableRemoteUser, uri: string): Promise { +export default async function ( + actor: CacheableRemoteUser, + uri: string, +): Promise { logger.info(`Deleting the Note: ${uri}`); const unlock = await getApLock(uri); @@ -18,23 +21,23 @@ export default async function(actor: CacheableRemoteUser, uri: string): Promise< if (note == null) { const message = await dbResolver.getMessageFromApId(uri); - if (message == null) return 'message not found'; + if (message == null) return "message not found"; if (message.userId !== actor.id) { - return 'The user trying to delete the post is not the post author'; + return "The user trying to delete the post is not the post author"; } await deleteMessage(message); - return 'ok: message deleted'; + return "ok: message deleted"; } if (note.userId !== actor.id) { - return 'The user trying to delete the post is not the post author'; + return "The user trying to delete the post is not the post author"; } await deleteNode(actor, note); - return 'ok: note deleted'; + return "ok: note deleted"; } finally { unlock(); } diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts index 53e5daa51..39ba8b3f4 100644 --- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts @@ -1,21 +1,27 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import config from '@/config/index.js'; -import { IFlag, getApIds } from '../../type.js'; -import { AbuseUserReports, Users } from '@/models/index.js'; -import { In } from 'typeorm'; -import { genId } from '@/misc/gen-id.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import config from "@/config/index.js"; +import type { IFlag } from "../../type.js"; +import { getApIds } from "../../type.js"; +import { AbuseUserReports, Users } from "@/models/index.js"; +import { In } from "typeorm"; +import { genId } from "@/misc/gen-id.js"; -export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IFlag, +): Promise => { // The object is `(User | Note) | (User | Note) []`, but it cannot be - // matched with all patterns of the DB schema, so the target user is the first - // user and it is stored as a comment. + // matched with all patterns of the DB schema, so the target user is the first + // user and it is stored as a comment. const uris = getApIds(activity.object); - const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop()!); + const userIds = uris + .filter((uri) => uri.startsWith(`${config.url}/users/`)) + .map((uri) => uri.split("/").pop()!); const users = await Users.findBy({ id: In(userIds), }); - if (users.length < 1) return `skip`; + if (users.length < 1) return "skip"; await AbuseUserReports.insert({ id: genId(), @@ -27,5 +33,5 @@ export default async (actor: CacheableRemoteUser, activity: IFlag): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IFollow, +): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); if (followee == null) { - return `skip: followee not found`; + return "skip: followee not found"; } if (followee.host != null) { - return `skip: user you are trying to follow is not a local user`; + return "skip: user you are trying to follow is not a local user"; } await follow(actor, followee, activity.id); - return `ok`; + return "ok"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index e24af2b38..58e354a51 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -1,5 +1,5 @@ -import type { CacheableRemoteUser } from '@/models/entities/user.js'; -import { toArray } from '@/prelude/array.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { toArray } from "@/prelude/array.js"; import { isCreate, isDelete, @@ -19,37 +19,42 @@ import { isFlag, isMove, getApId, -} from '../type.js'; -import { apLogger } from '../logger.js'; -import Resolver from '../resolver.js'; -import create from './create/index.js'; -import performDeleteActivity from './delete/index.js'; -import performUpdateActivity from './update/index.js'; -import { performReadActivity } from './read.js'; -import follow from './follow.js'; -import undo from './undo/index.js'; -import like from './like.js'; -import announce from './announce/index.js'; -import accept from './accept/index.js'; -import reject from './reject/index.js'; -import add from './add/index.js'; -import remove from './remove/index.js'; -import block from './block/index.js'; -import flag from './flag/index.js'; -import move from './move/index.js'; -import type { IObject } from '../type.js'; -import { extractDbHost } from '@/misc/convert-host.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +} from "../type.js"; +import { apLogger } from "../logger.js"; +import Resolver from "../resolver.js"; +import create from "./create/index.js"; +import performDeleteActivity from "./delete/index.js"; +import performUpdateActivity from "./update/index.js"; +import { performReadActivity } from "./read.js"; +import follow from "./follow.js"; +import undo from "./undo/index.js"; +import like from "./like.js"; +import announce from "./announce/index.js"; +import accept from "./accept/index.js"; +import reject from "./reject/index.js"; +import add from "./add/index.js"; +import remove from "./remove/index.js"; +import block from "./block/index.js"; +import flag from "./flag/index.js"; +import move from "./move/index.js"; +import type { IObject } from "../type.js"; +import { extractDbHost } from "@/misc/convert-host.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; -export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { +export async function performActivity( + actor: CacheableRemoteUser, + activity: IObject, +) { if (isCollectionOrOrderedCollection(activity)) { const resolver = new Resolver(); - for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { + for (const item of toArray( + isCollection(activity) ? activity.items : activity.orderedItems, + )) { const act = await resolver.resolve(item); try { await performOneActivity(actor, act); } catch (err) { - if (err instanceof Error || typeof err === 'string') { + if (err instanceof Error || typeof err === "string") { apLogger.error(err); } } @@ -59,15 +64,17 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj } } -async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { +async function performOneActivity( + actor: CacheableRemoteUser, + activity: IObject, +): Promise { if (actor.isSuspended) return; - if (typeof activity.id !== 'undefined') { + if (typeof activity.id !== "undefined") { const host = extractDbHost(getApId(activity)); if (await shouldBlockInstance(host)) return; } - if (isCreate(activity)) { await create(actor, activity); } else if (isDelete(activity)) { @@ -83,9 +90,9 @@ async function performOneActivity(actor: CacheableRemoteUser, activity: IObject) } else if (isReject(activity)) { await reject(actor, activity); } else if (isAdd(activity)) { - await add(actor, activity).catch(err => apLogger.error(err)); + await add(actor, activity).catch((err) => apLogger.error(err)); } else if (isRemove(activity)) { - await remove(actor, activity).catch(err => apLogger.error(err)); + await remove(actor, activity).catch((err) => apLogger.error(err)); } else if (isAnnounce(activity)) { await announce(actor, activity); } else if (isLike(activity)) { @@ -97,7 +104,7 @@ async function performOneActivity(actor: CacheableRemoteUser, activity: IObject) } else if (isFlag(activity)) { await flag(actor, activity); } else if (isMove(activity)) { - await move(actor,activity); + await move(actor, activity); } else { apLogger.warn(`unrecognized activity type: ${(activity as any).type}`); } diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 2b65ff738..7b30d1cd5 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -1,7 +1,8 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { ILike, getApId } from '../type.js'; -import create from '@/services/note/reaction/create.js'; -import { fetchNote, extractEmojis } from '../models/note.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { ILike } from "../type.js"; +import { getApId } from "../type.js"; +import create from "@/services/note/reaction/create.js"; +import { fetchNote, extractEmojis } from "../models/note.js"; export default async (actor: CacheableRemoteUser, activity: ILike) => { const targetUri = getApId(activity.object); @@ -11,11 +12,17 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => { await extractEmojis(activity.tag || [], actor.host).catch(() => null); - return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { - if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { - return 'skip: already reacted'; - } else { - throw e; - } - }).then(() => 'ok'); + return await create( + actor, + note, + activity._misskey_reaction || activity.content || activity.name, + ) + .catch((e) => { + if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") { + return "skip: already reacted"; + } else { + throw e; + } + }) + .then(() => "ok"); }; diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts index 660e106c0..47a55b8ab 100644 --- a/packages/backend/src/remote/activitypub/kernel/move/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/move/index.ts @@ -1,56 +1,69 @@ -import type { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IRemoteUser, User } from '@/models/entities/user.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import { getRemoteUser } from '@/server/api/common/getters.js'; -import { updatePerson } from '@/remote/activitypub/models/person.js'; -import { Followings, Users } from '@/models/index.js'; -import { makePaginationQuery } from '@/server/api/common/make-pagination-query.js'; -import deleteFollowing from '@/services/following/delete.js'; -import create from '@/services/following/create.js'; -import { getUser } from '@/server/api/common/getters.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { ApiError } from '@/server/api/error.js'; -import { meta } from '@/server/api/endpoints/following/create.js'; -import { IObject, IActor } from '../../type.js'; -import type { IMove } from '../../type.js'; -import Resolver from '@/remote/activitypub/resolver.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { IRemoteUser, User } from "@/models/entities/user.js"; +import DbResolver from "@/remote/activitypub/db-resolver.js"; +import { getRemoteUser } from "@/server/api/common/getters.js"; +import { updatePerson } from "@/remote/activitypub/models/person.js"; +import { Followings, Users } from "@/models/index.js"; +import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; +import deleteFollowing from "@/services/following/delete.js"; +import create from "@/services/following/create.js"; +import { getUser } from "@/server/api/common/getters.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import { ApiError } from "@/server/api/error.js"; +import { meta } from "@/server/api/endpoints/following/create.js"; +import { IObject } from "../../type.js"; +import type { IMove, IActor } from "../../type.js"; +import Resolver from "@/remote/activitypub/resolver.js"; -export default async (actor: CacheableRemoteUser, activity: IMove): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IMove, +): Promise => { // ※ There is a block target in activity.object, which should be a local user that exists. const dbResolver = new DbResolver(); const resolver = new Resolver(); let new_acc = await dbResolver.getUserFromApId(activity.target); let actor_new; - if (!new_acc) actor_new = await resolver.resolve(activity.target) as IActor; + if (!new_acc) + actor_new = (await resolver.resolve(activity.target)) as IActor; - if ((!new_acc || new_acc.uri === null) && (!actor_new || actor_new.id === null)) { - return 'move: new acc not found'; + if ( + (!new_acc || new_acc.uri === null) && + (!actor_new || actor_new.id === null) + ) { + return "move: new acc not found"; } - let newUri: string | null | undefined - newUri = new_acc ? new_acc.uri : - actor_new?.url?.toString(); + const newUri = new_acc ? new_acc.uri : actor_new?.url?.toString(); - if(newUri === null || newUri === undefined) return 'move: new acc not found #2'; + if (newUri === null || newUri === undefined) + return "move: new acc not found #2"; await updatePerson(newUri); await updatePerson(actor.uri!); new_acc = await dbResolver.getUserFromApId(newUri); - let old = await dbResolver.getUserFromApId(actor.uri!); + const old = await dbResolver.getUserFromApId(actor.uri!); - if (old === null || old.uri === null || !new_acc?.alsoKnownAs?.includes(old.uri)) return 'move: accounts invalid'; + if ( + old === null || + old.uri === null || + !new_acc?.alsoKnownAs?.includes(old.uri) + ) + return "move: accounts invalid"; old.movedToUri = new_acc.uri; - const followee = await getUser(actor.id).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const followee = await getUser(actor.id).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); - const followeeNew = await getUser(new_acc.id).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const followeeNew = await getUser(new_acc.id).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -58,19 +71,22 @@ export default async (actor: CacheableRemoteUser, activity: IMove): Promise { + followings.forEach(async (following) => { //if follower is local if (!following.followerHost) { - const follower = await getUser(following.followerId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const follower = await getUser(following.followerId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); await deleteFollowing(follower!, followee); try { await create(follower!, followeeNew); - } catch (e) { /* empty */ } + } catch (e) { + /* empty */ + } } }); - return 'ok'; + return "ok"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts index f7b0bcecd..7cc70976c 100644 --- a/packages/backend/src/remote/activitypub/kernel/read.ts +++ b/packages/backend/src/remote/activitypub/kernel/read.ts @@ -1,27 +1,33 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IRead, getApId } from '../type.js'; -import { isSelfHost, extractDbHost } from '@/misc/convert-host.js'; -import { MessagingMessages } from '@/models/index.js'; -import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { IRead } from "../type.js"; +import { getApId } from "../type.js"; +import { isSelfHost, extractDbHost } from "@/misc/convert-host.js"; +import { MessagingMessages } from "@/models/index.js"; +import { readUserMessagingMessage } from "../../../server/api/common/read-messaging-message.js"; -export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise => { +export const performReadActivity = async ( + actor: CacheableRemoteUser, + activity: IRead, +): Promise => { const id = await getApId(activity.object); if (!isSelfHost(extractDbHost(id))) { return `skip: Read to foreign host (${id})`; } - const messageId = id.split('/').pop(); + const messageId = id.split("/").pop(); const message = await MessagingMessages.findOneBy({ id: messageId }); if (message == null) { - return `skip: message not found`; + return "skip: message not found"; } if (actor.id !== message.recipientId) { - return `skip: actor is not a message recipient`; + return "skip: actor is not a message recipient"; } - await readUserMessagingMessage(message.recipientId!, message.userId, [message.id]); + await readUserMessagingMessage(message.recipientId!, message.userId, [ + message.id, + ]); return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`; }; diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts index 6e24fe752..670c1556f 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts @@ -1,22 +1,25 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { remoteReject } from '@/services/following/reject.js'; -import { IFollow } from '../../type.js'; -import DbResolver from '../../db-resolver.js'; -import { relayRejected } from '@/services/relay.js'; -import { Users } from '@/models/index.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { remoteReject } from "@/services/following/reject.js"; +import type { IFollow } from "../../type.js"; +import DbResolver from "../../db-resolver.js"; +import { relayRejected } from "@/services/relay.js"; +import { Users } from "@/models/index.js"; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IFollow, +): Promise => { // ※ `activity.actor` must be an existing local user, since `activity` is a follow request thrown from us. const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.actor); if (follower == null) { - return `skip: follower not found`; + return "skip: follower not found"; } if (!Users.isLocalUser(follower)) { - return `skip: follower is not a local user`; + return "skip: follower is not a local user"; } // relay @@ -26,5 +29,5 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IReject, +): Promise => { const uri = activity.id || activity; logger.info(`Reject: ${uri}`); const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { + const object = await resolver.resolve(activity.object).catch((e) => { logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts index 11a994a83..0b4be6b5f 100644 --- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts @@ -1,20 +1,23 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { IRemove } from '../../type.js'; -import { resolveNote } from '../../models/note.js'; -import { removePinned } from '@/services/i/pin.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { IRemove } from "../../type.js"; +import { resolveNote } from "../../models/note.js"; +import { removePinned } from "@/services/i/pin.js"; -export default async (actor: CacheableRemoteUser, activity: IRemove): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); +export default async ( + 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'); + throw new Error("target is null"); } if (activity.target === actor.featured) { const note = await resolveNote(activity.object); - if (note == null) throw new Error('note not found'); + if (note == null) throw new Error("note not found"); await removePinned(actor, note.id); return; } diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts index 39ef92363..2cd05a77d 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts @@ -1,16 +1,19 @@ -import unfollow from '@/services/following/delete.js'; -import cancelRequest from '@/services/following/requests/cancel.js'; -import { IAccept } from '../../type.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { Followings } from '@/models/index.js'; -import DbResolver from '../../db-resolver.js'; +import unfollow from "@/services/following/delete.js"; +import cancelRequest from "@/services/following/requests/cancel.js"; +import type { IAccept } from "../../type.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { Followings } from "@/models/index.js"; +import DbResolver from "../../db-resolver.js"; -export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IAccept, +): Promise => { const dbResolver = new DbResolver(); const follower = await dbResolver.getUserFromApId(activity.object); if (follower == null) { - return `skip: follower not found`; + return "skip: follower not found"; } const following = await Followings.findOneBy({ @@ -20,8 +23,8 @@ export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { +export const undoAnnounce = async ( + actor: CacheableRemoteUser, + activity: IAnnounce, +): Promise => { const uri = getApId(activity); const note = await Notes.findOneBy({ @@ -11,8 +15,8 @@ export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnoun userId: actor.id, }); - if (!note) return 'skip: no such Announce'; + if (!note) return "skip: no such Announce"; await deleteNote(actor, note); - return 'ok: deleted'; + return "ok: deleted"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts index 152905167..b4e1d8ee4 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts @@ -1,21 +1,24 @@ -import { IBlock } from '../../type.js'; -import unblock from '@/services/blocking/delete.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import DbResolver from '../../db-resolver.js'; -import { Users } from '@/models/index.js'; +import type { IBlock } from "../../type.js"; +import unblock from "@/services/blocking/delete.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import DbResolver from "../../db-resolver.js"; +import { Users } from "@/models/index.js"; -export default async (actor: CacheableRemoteUser, activity: IBlock): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IBlock, +): Promise => { const dbResolver = new DbResolver(); const blockee = await dbResolver.getUserFromApId(activity.object); if (blockee == null) { - return `skip: blockee not found`; + return "skip: blockee not found"; } if (blockee.host != null) { - return `skip: The user you are trying to unblock is not a local user`; + return "skip: The user you are trying to unblock is not a local user"; } await unblock(await Users.findOneByOrFail({ id: actor.id }), blockee); - return `ok`; + return "ok"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts index 0fa58f735..1c4648cf9 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts @@ -1,20 +1,23 @@ -import unfollow from '@/services/following/delete.js'; -import cancelRequest from '@/services/following/requests/cancel.js'; -import { IFollow } from '../../type.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { FollowRequests, Followings } from '@/models/index.js'; -import DbResolver from '../../db-resolver.js'; +import unfollow from "@/services/following/delete.js"; +import cancelRequest from "@/services/following/requests/cancel.js"; +import type { IFollow } from "../../type.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { FollowRequests, Followings } from "@/models/index.js"; +import DbResolver from "../../db-resolver.js"; -export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IFollow, +): Promise => { const dbResolver = new DbResolver(); const followee = await dbResolver.getUserFromApId(activity.object); if (followee == null) { - return `skip: followee not found`; + return "skip: followee not found"; } if (followee.host != null) { - return `skip: The user you are trying to unfollow is not a local user`; + return "skip: The user you are trying to unfollow is not a local user"; } const req = await FollowRequests.findOneBy({ @@ -29,13 +32,13 @@ export default async (actor: CacheableRemoteUser, activity: IFollow): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { - throw new Error('invalid actor'); +export default async ( + actor: CacheableRemoteUser, + activity: IUndo, +): Promise => { + if ("actor" in activity && actor.uri !== activity.actor) { + throw new Error("invalid actor"); } const uri = activity.id || activity; @@ -21,7 +32,7 @@ export default async (actor: CacheableRemoteUser, activity: IUndo): Promise { + const object = await resolver.resolve(activity.object).catch((e) => { logger.error(`Resolution failed: ${e}`); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts index 01aeba1fb..90220e203 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts @@ -1,7 +1,8 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { ILike, getApId } from '../../type.js'; -import deleteReaction from '@/services/note/reaction/delete.js'; -import { fetchNote } from '../../models/note.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { ILike } from "../../type.js"; +import { getApId } from "../../type.js"; +import deleteReaction from "@/services/note/reaction/delete.js"; +import { fetchNote } from "../../models/note.js"; /** * Process Undo.Like activity @@ -12,10 +13,10 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => { const note = await fetchNote(targetUri); if (!note) return `skip: target note not found ${targetUri}`; - await deleteReaction(actor, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return; + await deleteReaction(actor, note).catch((e) => { + if (e.id === "60527ec9-b4cb-4a88-a6bd-32d3ad26817d") return; throw e; }); - return `ok`; + return "ok"; }; diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 704980c46..4f1514ddd 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,33 +1,37 @@ -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { getApType, IUpdate, isActor } from '../../type.js'; -import { apLogger } from '../../logger.js'; -import { updateQuestion } from '../../models/question.js'; -import Resolver from '../../resolver.js'; -import { updatePerson } from '../../models/person.js'; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import type { IUpdate } from "../../type.js"; +import { getApType, isActor } from "../../type.js"; +import { apLogger } from "../../logger.js"; +import { updateQuestion } from "../../models/question.js"; +import Resolver from "../../resolver.js"; +import { updatePerson } from "../../models/person.js"; /** * Handler for the Update activity */ -export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { - if ('actor' in activity && actor.uri !== activity.actor) { - return `skip: invalid actor`; +export default async ( + actor: CacheableRemoteUser, + activity: IUpdate, +): Promise => { + if ("actor" in activity && actor.uri !== activity.actor) { + return "skip: invalid actor"; } - apLogger.debug('Update'); + apLogger.debug("Update"); const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { + const object = await resolver.resolve(activity.object).catch((e) => { apLogger.error(`Resolution failed: ${e}`); throw e; }); if (isActor(object)) { await updatePerson(actor.uri!, resolver, object); - return `ok: Person updated`; - } else if (getApType(object) === 'Question') { - await updateQuestion(object, resolver).catch(e => console.log(e)); - return `ok: Question updated`; + return "ok: Person updated"; + } else if (getApType(object) === "Question") { + await updateQuestion(object, resolver).catch((e) => console.log(e)); + return "ok: Question updated"; } else { return `skip: Unknown type: ${getApType(object)}`; } diff --git a/packages/backend/src/remote/activitypub/logger.ts b/packages/backend/src/remote/activitypub/logger.ts index cab51b3bf..47383cf7f 100644 --- a/packages/backend/src/remote/activitypub/logger.ts +++ b/packages/backend/src/remote/activitypub/logger.ts @@ -1,3 +1,3 @@ -import { remoteLogger } from '../logger.js'; +import { remoteLogger } from "../logger.js"; -export const apLogger = remoteLogger.createSubLogger('ap', 'magenta'); +export const apLogger = remoteLogger.createSubLogger("ap", "magenta"); diff --git a/packages/backend/src/remote/activitypub/misc/contexts.ts b/packages/backend/src/remote/activitypub/misc/contexts.ts index aee0d3629..1e4739231 100644 --- a/packages/backend/src/remote/activitypub/misc/contexts.ts +++ b/packages/backend/src/remote/activitypub/misc/contexts.ts @@ -1,526 +1,526 @@ /* eslint:disable:quotemark indent */ const id_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', + "@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#', + 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', + 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', + 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', + 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', + identityService: { "@id": "identity:identityService", "@type": "@id" }, + idp: { "@id": "identity:idp", "@type": "@id" }, + Identity: "identity:Identity", - 'paymentProcessor': 'ps:processor', - 'preferences': { '@id': 'ps:preferences', '@type': '@vocab' }, + 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', + 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' }, + accessControl: { "@id": "perm:accessControl", "@type": "@id" }, + writePermission: { "@id": "perm:writePermission", "@type": "@id" }, }, }; const security_v1 = { - '@context': { - 'id': '@id', - 'type': '@type', + "@context": { + id: "@id", + type: "@type", - 'dc': 'http://purl.org/dc/terms/', - 'sec': 'https://w3id.org/security#', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', + 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', + 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', + 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', + "@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, + "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/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts index 389039ebe..cb5294f73 100644 --- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts +++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts @@ -1,8 +1,8 @@ -import * as mfm from 'mfm-js'; -import { Note } from '@/models/entities/note.js'; -import { toHtml } from '../../../mfm/to-html.js'; +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 ''; +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/remote/activitypub/misc/html-to-mfm.ts b/packages/backend/src/remote/activitypub/misc/html-to-mfm.ts index bb1ba7925..9d71a46a3 100644 --- a/packages/backend/src/remote/activitypub/misc/html-to-mfm.ts +++ b/packages/backend/src/remote/activitypub/misc/html-to-mfm.ts @@ -1,9 +1,11 @@ -import { IObject } from '../type.js'; -import { extractApHashtagObjects } from '../models/tag.js'; -import { fromHtml } from '../../../mfm/from-html.js'; +import type { IObject } from "../type.js"; +import { extractApHashtagObjects } from "../models/tag.js"; +import { fromHtml } from "../../../mfm/from-html.js"; export function htmlToMfm(html: string, tag?: IObject | IObject[]) { - const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); + const hashtagNames = extractApHashtagObjects(tag) + .map((x) => x.name) + .filter((x): x is string => x != null); return fromHtml(html, hashtagNames); } diff --git a/packages/backend/src/remote/activitypub/misc/ld-signature.ts b/packages/backend/src/remote/activitypub/misc/ld-signature.ts index 7c2083a27..0a4ec3a53 100644 --- a/packages/backend/src/remote/activitypub/misc/ld-signature.ts +++ b/packages/backend/src/remote/activitypub/misc/ld-signature.ts @@ -1,8 +1,8 @@ -import * as crypto from 'node:crypto'; -import jsonld from 'jsonld'; -import { CONTEXTS } from './contexts.js'; -import fetch from 'node-fetch'; -import { httpAgent, httpsAgent } from '@/misc/fetch.js'; +import * as crypto from "node:crypto"; +import jsonld from "jsonld"; +import { CONTEXTS } from "./contexts.js"; +import fetch from "node-fetch"; +import { httpAgent, httpsAgent } from "@/misc/fetch.js"; // RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 @@ -11,15 +11,20 @@ export class LdSignature { public preLoad = true; public loderTimeout = 10 * 1000; - constructor() { - } + constructor() {} - public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { + public async signRsaSignature2017( + data: any, + privateKey: string, + creator: string, + domain?: string, + created?: Date, + ): Promise { const options = { - type: 'RsaSignature2017', + type: "RsaSignature2017", creator, domain, - nonce: crypto.randomBytes(16).toString('hex'), + nonce: crypto.randomBytes(16).toString("hex"), created: (created || new Date()).toISOString(), } as { type: string; @@ -30,12 +35,12 @@ export class LdSignature { }; if (!domain) { - delete options.domain; + options.domain = undefined; } const toBeSigned = await this.createVerifyData(data, options); - const signer = crypto.createSign('sha256'); + const signer = crypto.createSign("sha256"); signer.update(toBeSigned); signer.end(); @@ -45,30 +50,33 @@ export class LdSignature { ...data, signature: { ...options, - signatureValue: signature.toString('base64'), + signatureValue: signature.toString("base64"), }, }; } - public async verifyRsaSignature2017(data: any, publicKey: string): Promise { + public async verifyRsaSignature2017( + data: any, + publicKey: string, + ): Promise { const toBeSigned = await this.createVerifyData(data, data.signature); - const verifier = crypto.createVerify('sha256'); + const verifier = crypto.createVerify("sha256"); verifier.update(toBeSigned); - return verifier.verify(publicKey, data.signature.signatureValue, 'base64'); + return verifier.verify(publicKey, data.signature.signatureValue, "base64"); } public async createVerifyData(data: any, options: any) { const transformedOptions = { ...options, - '@context': 'https://w3id.org/identity/v1', + "@context": "https://w3id.org/identity/v1", }; - delete transformedOptions['type']; - delete transformedOptions['id']; - delete transformedOptions['signatureValue']; + transformedOptions["type"] = undefined; + transformedOptions["id"] = undefined; + transformedOptions["signatureValue"] = undefined; const canonizedOptions = await this.normalize(transformedOptions); const optionsHash = this.sha256(canonizedOptions); const transformedData = { ...data }; - delete transformedData['signature']; + transformedData["signature"] = undefined; const cannonidedData = await this.normalize(transformedData); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); const documentHash = this.sha256(cannonidedData); @@ -85,7 +93,7 @@ export class LdSignature { private getLoader() { return async (url: string): Promise => { - if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`); + if (!url.match("^https?://")) throw new Error(`Invalid URL ${url}`); if (this.preLoad) { if (url in CONTEXTS) { @@ -111,12 +119,12 @@ export class LdSignature { private async fetchDocument(url: string) { const json = await fetch(url, { headers: { - Accept: 'application/ld+json, application/json', + Accept: "application/ld+json, application/json", }, // TODO //timeout: this.loderTimeout, - agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent, - }).then(res => { + agent: (u) => (u.protocol === "http:" ? httpAgent : httpsAgent), + }).then((res) => { if (!res.ok) { throw new Error(`${res.status} ${res.statusText}`); } else { @@ -128,8 +136,8 @@ export class LdSignature { } public sha256(data: string): string { - const hash = crypto.createHash('sha256'); + const hash = crypto.createHash("sha256"); hash.update(data); - return hash.digest('hex'); + return hash.digest("hex"); } } diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index 1fa90ff50..415f7c400 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,28 +1,32 @@ -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; -import Resolver from '../resolver.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { apLogger } from '../logger.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles, Users } from '@/models/index.js'; -import { truncate } from '@/misc/truncate.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { uploadFromUrl } from "@/services/drive/upload-from-url.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { IRemoteUser } from "@/models/entities/user.js"; +import Resolver from "../resolver.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { apLogger } from "../logger.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles, Users } from "@/models/index.js"; +import { truncate } from "@/misc/truncate.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; const logger = apLogger; /** * create an Image. */ -export async function createImage(actor: CacheableRemoteUser, value: any): Promise { +export async function createImage( + actor: CacheableRemoteUser, + value: any, +): Promise { // Skip if author is frozen. if (actor.isSuspended) { - throw new Error('actor has been suspended'); + throw new Error("actor has been suspended"); } - const image = await new Resolver().resolve(value) as any; + const image = (await new Resolver().resolve(value)) as any; if (image.url == null) { - throw new Error('invalid image: url not privided'); + throw new Error("invalid image: url not privided"); } logger.info(`Creating the Image: ${image.url}`); @@ -35,17 +39,20 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi uri: image.url, sensitive: image.sensitive, isLink: !instance.cacheRemoteFiles, - comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH) + comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), }); if (file.isLink) { - // If the URL is different, it means that the same image was previously - // registered with a different URL, so update the URL + // If the URL is different, it means that the same image was previously + // registered with a different URL, so update the URL if (file.url !== image.url) { - await DriveFiles.update({ id: file.id }, { - url: image.url, - uri: image.url, - }); + await DriveFiles.update( + { id: file.id }, + { + url: image.url, + uri: image.url, + }, + ); file = await DriveFiles.findOneByOrFail({ id: file.id }); } @@ -60,7 +67,10 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi * If the target Image is registered in Calckey, return it, otherwise * Fetch from remote server, register with Calckey and return it. */ -export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { +export async function resolveImage( + actor: CacheableRemoteUser, + value: any, +): Promise { // TODO // Fetch from remote server and register diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index 13f77424e..b888fa21a 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -1,24 +1,36 @@ -import promiseLimit from 'promise-limit'; -import { toArray, unique } from '@/prelude/array.js'; -import { CacheableUser, User } from '@/models/entities/user.js'; -import { IObject, isMention, IApMention } from '../type.js'; -import Resolver from '../resolver.js'; -import { resolvePerson } from './person.js'; +import promiseLimit from "promise-limit"; +import { toArray, unique } from "@/prelude/array.js"; +import type { CacheableUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import type { IObject, IApMention } from "../type.js"; +import { isMention } from "../type.js"; +import Resolver from "../resolver.js"; +import { resolvePerson } from "./person.js"; -export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { - const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); +export async function extractApMentions( + tags: IObject | IObject[] | null | undefined, +) { + const hrefs = unique( + extractApMentionObjects(tags).map((x) => x.href as string), + ); const resolver = new Resolver(); const limit = promiseLimit(2); - const mentionedUsers = (await Promise.all( - hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), - )).filter((x): x is CacheableUser => x != null); + const mentionedUsers = ( + await Promise.all( + hrefs.map((x) => + limit(() => resolvePerson(x, resolver).catch(() => null)), + ), + ) + ).filter((x): x is CacheableUser => x != null); return mentionedUsers; } -export function extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] { +export function extractApMentionObjects( + tags: IObject | IObject[] | null | undefined, +): IApMention[] { if (tags == null) return []; return toArray(tags).filter(isMention); } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index ea74dddba..72953c5bf 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -1,33 +1,41 @@ -import promiseLimit from 'promise-limit'; +import promiseLimit from "promise-limit"; -import config from '@/config/index.js'; -import Resolver from '../resolver.js'; -import post from '@/services/note/create.js'; -import { resolvePerson } from './person.js'; -import { resolveImage } from './image.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { htmlToMfm } from '../misc/html-to-mfm.js'; -import { extractApHashtags } from './tag.js'; -import { unique, toArray, toSingle } from '@/prelude/array.js'; -import { extractPollFromQuestion } from './question.js'; -import vote from '@/services/note/polls/vote.js'; -import { apLogger } from '../logger.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; -import { extractDbHost, toPuny } from '@/misc/convert-host.js'; -import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { genId } from '@/misc/gen-id.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { getApLock } from '@/misc/app-lock.js'; -import { createMessage } from '@/services/messages/create.js'; -import { parseAudience } from '../audience.js'; -import { extractApMentions } from './mention.js'; -import DbResolver from '../db-resolver.js'; -import { StatusError } from '@/misc/fetch.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +import config from "@/config/index.js"; +import Resolver from "../resolver.js"; +import post from "@/services/note/create.js"; +import { resolvePerson } from "./person.js"; +import { resolveImage } from "./image.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { htmlToMfm } from "../misc/html-to-mfm.js"; +import { extractApHashtags } from "./tag.js"; +import { unique, toArray, toSingle } from "@/prelude/array.js"; +import { extractPollFromQuestion } from "./question.js"; +import vote from "@/services/note/polls/vote.js"; +import { apLogger } from "../logger.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; +import { extractDbHost, toPuny } from "@/misc/convert-host.js"; +import { Emojis, Polls, MessagingMessages } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; +import type { IObject, IPost } from "../type.js"; +import { + getOneApId, + getApId, + getOneApHrefNullable, + validPost, + isEmoji, + getApType, +} from "../type.js"; +import type { Emoji } from "@/models/entities/emoji.js"; +import { genId } from "@/misc/gen-id.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { getApLock } from "@/misc/app-lock.js"; +import { createMessage } from "@/services/messages/create.js"; +import { parseAudience } from "../audience.js"; +import { extractApMentions } from "./mention.js"; +import DbResolver from "../db-resolver.js"; +import { StatusError } from "@/misc/fetch.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; const logger = apLogger; @@ -35,7 +43,7 @@ export function validateNote(object: any, uri: string) { const expectHost = extractDbHost(uri); if (object == null) { - return new Error('invalid Note: object is null'); + return new Error("invalid Note: object is null"); } if (!validPost.includes(getApType(object))) { @@ -43,11 +51,22 @@ export function validateNote(object: any, uri: string) { } if (object.id && extractDbHost(object.id) !== expectHost) { - return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`); + return new Error( + `invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost( + object.id, + )}`, + ); } - if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { - return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`); + if ( + object.attributedTo && + extractDbHost(getOneApId(object.attributedTo)) !== expectHost + ) { + return new Error( + `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost( + object.attributedTo, + )}`, + ); } return null; @@ -58,7 +77,9 @@ export function validateNote(object: any, uri: string) { * * If the target Note is registered in Calckey, it will be returned. */ -export async function fetchNote(object: string | IObject): Promise { +export async function fetchNote( + object: string | IObject, +): Promise { const dbResolver = new DbResolver(); return await dbResolver.getNoteFromApId(object); } @@ -66,7 +87,11 @@ export async function fetchNote(object: string | IObject): Promise /** * Create a Note. */ -export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { +export async function createNote( + value: string | IObject, + resolver?: Resolver, + silent = false, +): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -81,7 +106,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s value: value, object: object, }); - throw new Error('invalid note'); + throw new Error("invalid note"); } const note: IPost = object; @@ -91,11 +116,14 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s logger.info(`Creating the Note: ${note.id}`); // Fetch author - const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; + const actor = (await resolvePerson( + getOneApId(note.attributedTo), + resolver, + )) as CacheableRemoteUser; // Skip if author is suspended. if (actor.isSuspended) { - throw new Error('actor has been suspended'); + throw new Error("actor has been suspended"); } const noteAudience = await parseAudience(actor, note.to, note.cc); @@ -103,14 +131,15 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const visibleUsers = noteAudience.visibleUsers; // If Audience (to, cc) was not specified - if (visibility === 'specified' && visibleUsers.length === 0) { - if (typeof value === 'string') { // If the input is a string, GET occurs in resolver + if (visibility === "specified" && visibleUsers.length === 0) { + if (typeof value === "string") { + // If the input is a string, GET occurs in resolver // Public if you can GET anonymously from here - visibility = 'public'; + visibility = "public"; } } - let isTalk = note._misskey_talk && visibility === 'specified'; + let isTalk = note._misskey_talk && visibility === "specified"; const apMentions = await extractApMentions(note.tag); const apHashtags = await extractApHashtags(note.tag); @@ -121,101 +150,141 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s // 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(() => resolveImage(actor, x)) as Promise))) - .filter(image => image != null) + 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(() => resolveImage(actor, x)) as Promise, + ), + ) + ).filter((image) => image != null) : []; // Reply const reply: Note | null = note.inReplyTo - ? await resolveNote(note.inReplyTo, resolver).then(x => { - if (x == null) { - logger.warn(`Specified inReplyTo, but nout found`); - throw new Error('inReplyTo not found'); - } else { - return x; - } - }).catch(async e => { - // トークだったらinReplyToのエラーは無視 - const uri = getApId(note.inReplyTo); - if (uri.startsWith(config.url + '/')) { - const id = uri.split('/').pop(); - const talk = await MessagingMessages.findOneBy({ id }); - if (talk) { - isTalk = true; - return null; - } - } + ? await resolveNote(note.inReplyTo, resolver) + .then((x) => { + if (x == null) { + logger.warn("Specified inReplyTo, but nout found"); + throw new Error("inReplyTo not found"); + } else { + return x; + } + }) + .catch(async (e) => { + // トークだったらinReplyToのエラーは無視 + const uri = getApId(note.inReplyTo); + if (uri.startsWith(`${config.url}/`)) { + const id = uri.split("/").pop(); + const talk = await MessagingMessages.findOneBy({ id }); + if (talk) { + isTalk = true; + return null; + } + } - logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`); - throw e; - }) + logger.warn( + `Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`, + ); + throw e; + }) : null; // Quote let quote: Note | undefined | null; if (note._misskey_quote || note.quoteUrl || note.quoteUri) { - const tryResolveNote = async (uri: string): Promise<{ - status: 'ok'; - res: Note | null; - } | { - status: 'permerror' | 'temperror'; - }> => { - if (typeof uri !== 'string' || !uri.match(/^https?:/)) return { status: 'permerror' }; + 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 resolveNote(uri); if (res) { return { - status: 'ok', + status: "ok", res, }; } else { return { - status: 'permerror', + status: "permerror", }; } } catch (e) { return { - status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', + status: + e instanceof StatusError && e.isClientError + ? "permerror" + : "temperror", }; } }; - const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter((x): x is string => typeof x === 'string')); - const results = await Promise.all(uris.map(uri => tryResolveNote(uri))); + const uris = unique( + [note._misskey_quote, note.quoteUrl, note.quoteUri].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); + 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 new Error('quote resolve failed'); + if (results.some((x) => x.status === "temperror")) { + throw new Error("quote resolve failed"); } } } - const cw = note.summary === '' ? null : note.summary; + const cw = note.summary === "" ? null : note.summary; // Text parsing let text: string | null = null; - if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { + if ( + note.source?.mediaType === "text/x.misskeymarkdown" && + typeof note.source?.content === "string" + ) { text = note.source.content; - } else if (typeof note._misskey_content !== 'undefined') { + } else if (typeof note._misskey_content !== "undefined") { text = note._misskey_content; - } else if (typeof note.content === 'string') { + } else if (typeof note.content === "string") { text = htmlToMfm(note.content, note.tag); } // vote - if (reply && reply.hasPoll) { + if (reply?.hasPoll) { const poll = await Polls.findOneByOrFail({ noteId: reply.id }); - const tryCreateVote = async (name: string, index: number): Promise => { + const tryCreateVote = async ( + name: string, + index: number, + ): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + logger.warn( + `vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, + ); } else if (index >= 0) { - logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); + logger.info( + `vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, + ); await vote(actor, reply, index); // リモートフォロワーにUpdate配信 @@ -225,44 +294,60 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s }; if (note.name) { - return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name)); + return await tryCreateVote( + note.name, + poll.choices.findIndex((x) => x === note.name), + ); } } - const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => { + const emojis = await extractEmojis(note.tag || [], actor.host).catch((e) => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); - const apEmojis = emojis.map(emoji => emoji.name); + const apEmojis = emojis.map((emoji) => emoji.name); - const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined); + const poll = await extractPollFromQuestion(note, resolver).catch( + () => undefined, + ); if (isTalk) { for (const recipient of visibleUsers) { - await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id); + await createMessage( + actor, + recipient, + undefined, + text || undefined, + files && files.length > 0 ? files[0] : null, + object.id, + ); return null; } } - return await post(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); + return await post( + 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, + ); } /** @@ -271,12 +356,20 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s * If the target Note is registered in Calckey, return it, otherwise * Fetch from remote server, register with Calckey and return it. */ -export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { - const uri = typeof value === 'string' ? value : value.id; - if (uri == null) throw new Error('missing uri'); +export async function resolveNote( + value: string | IObject, + resolver?: Resolver, +): Promise { + const uri = typeof value === "string" ? value : value.id; + if (uri == null) throw new Error("missing uri"); // Abort if origin host is blocked - if (await shouldBlockInstance(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); + if (await shouldBlockInstance(extractDbHost(uri))) + throw new StatusError( + "host blocked", + 451, + `host ${extractDbHost(uri)} is blocked`, + ); const unlock = await getApLock(uri); @@ -290,7 +383,11 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): //#endregion if (uri.startsWith(config.url)) { - throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); + throw new StatusError( + "cannot resolve local note", + 400, + "cannot resolve local note", + ); } // Fetch from remote server and register @@ -302,58 +399,71 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): } } -export async function extractEmojis(tags: IObject | IObject[], host: string): Promise { +export async function extractEmojis( + tags: IObject | IObject[], + host: string, +): Promise { host = 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); + return await Promise.all( + eomjiTags.map(async (tag) => { + const name = tag.name!.replace(/^:/, "").replace(/:$/, ""); + tag.icon = toSingle(tag.icon); - const exists = await Emojis.findOneBy({ - host, - name, - }); + const exists = await Emojis.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 Emojis.update({ - host, - name, - }, { - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - }); + 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 Emojis.update( + { + host, + name, + }, + { + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + }, + ); - return await Emojis.findOneBy({ - host, - name, - }) as Emoji; + return (await Emojis.findOneBy({ + host, + name, + })) as Emoji; + } + + return exists; } - return exists; - } + logger.info(`register emoji host=${host}, name=${name}`); - logger.info(`register emoji host=${host}, name=${name}`); - - return await Emojis.insert({ - id: genId(), - host, - name, - uri: tag.id, - originalUrl: tag.icon!.url, - publicUrl: tag.icon!.url, - updatedAt: new Date(), - aliases: [], - } as Partial).then(x => Emojis.findOneByOrFail(x.identifiers[0])); - })); + return await Emojis.insert({ + id: genId(), + host, + name, + uri: tag.id, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, + updatedAt: new Date(), + aliases: [], + } as Partial).then((x) => + Emojis.findOneByOrFail(x.identifiers[0]), + ); + }), + ); } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index b23479be0..0ec671f0a 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -1,36 +1,53 @@ -import { URL } from 'node:url'; -import promiseLimit from 'promise-limit'; +import { URL } from "node:url"; +import promiseLimit from "promise-limit"; -import config from '@/config/index.js'; -import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; -import { Note } from '@/models/entities/note.js'; -import { updateUsertags } from '@/services/update-hashtag.js'; -import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; -import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { UserNotePining } from '@/models/entities/user-note-pining.js'; -import { genId } from '@/misc/gen-id.js'; -import { instanceChart, usersChart } from '@/services/chart/index.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { toArray } from '@/prelude/array.js'; -import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { truncate } from '@/misc/truncate.js'; -import { StatusError } from '@/misc/fetch.js'; -import { uriPersonCache } from '@/services/user-cache.js'; -import { publishInternalEvent } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; -import { apLogger } from '../logger.js'; -import { htmlToMfm } from '../misc/html-to-mfm.js'; -import { fromHtml } from '../../../mfm/from-html.js'; -import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; -import Resolver from '../resolver.js'; -import { extractApHashtags } from './tag.js'; -import { resolveNote, extractEmojis } from './note.js'; -import { resolveImage } from './image.js'; +import config from "@/config/index.js"; +import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; +import type { Note } from "@/models/entities/note.js"; +import { updateUsertags } from "@/services/update-hashtag.js"; +import { + Users, + Instances, + DriveFiles, + Followings, + UserProfiles, + UserPublickeys, +} from "@/models/index.js"; +import type { IRemoteUser, CacheableUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import type { Emoji } from "@/models/entities/emoji.js"; +import { UserNotePining } from "@/models/entities/user-note-pining.js"; +import { genId } from "@/misc/gen-id.js"; +import { instanceChart, usersChart } from "@/services/chart/index.js"; +import { UserPublickey } from "@/models/entities/user-publickey.js"; +import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; +import { toPuny } from "@/misc/convert-host.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { toArray } from "@/prelude/array.js"; +import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; +import { truncate } from "@/misc/truncate.js"; +import { StatusError } from "@/misc/fetch.js"; +import { uriPersonCache } from "@/services/user-cache.js"; +import { publishInternalEvent } from "@/services/stream.js"; +import { db } from "@/db/postgre.js"; +import { apLogger } from "../logger.js"; +import { htmlToMfm } from "../misc/html-to-mfm.js"; +import { fromHtml } from "../../../mfm/from-html.js"; +import type { IActor, IObject, IApPropertyValue } from "../type.js"; +import { + isCollectionOrOrderedCollection, + isCollection, + getApId, + getOneApHrefNullable, + isPropertyValue, + getApType, + isActor, +} from "../type.js"; +import Resolver from "../resolver.js"; +import { extractApHashtags } from "./tag.js"; +import { resolveNote, extractEmojis } from "./note.js"; +import { resolveImage } from "./image.js"; const logger = apLogger; @@ -46,54 +63,61 @@ function validateActor(x: IObject, uri: string): IActor { const expectHost = toPuny(new URL(uri).hostname); if (x == null) { - throw new Error('invalid Actor: object is 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.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.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'); + 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'); + 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'); + if (!(typeof x.summary === "string" && x.summary.length > 0)) { + throw new Error("invalid Actor: wrong summary"); } x.summary = truncate(x.summary, summaryLength); } const idHost = toPuny(new URL(x.id!).hostname); if (idHost !== expectHost) { - throw new Error('invalid Actor: id has different host'); + 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'); + if (typeof x.publicKey.id !== "string") { + throw new Error("invalid Actor: publicKey.id is not a string"); } const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname); if (publicKeyIdHost !== expectHost) { - throw new Error('invalid Actor: publicKey.id has different host'); + throw new Error("invalid Actor: publicKey.id has different host"); } } @@ -105,15 +129,18 @@ function validateActor(x: IObject, uri: string): IActor { * * If the target Person is registered in Calckey, it will be returned. */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); +export async function fetchPerson( + uri: string, + resolver?: Resolver, +): Promise { + if (typeof uri !== "string") throw new Error("uri is not string"); const cached = uriPersonCache.get(uri); if (cached) return cached; // Fetch from the database if the URI points to this server - if (uri.startsWith(config.url + '/')) { - const id = uri.split('/').pop(); + if (uri.startsWith(`${config.url}/`)) { + const id = uri.split("/").pop(); const u = await Users.findOneBy({ id }); if (u) uriPersonCache.set(uri, u); return u; @@ -134,16 +161,23 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); +export async function createPerson( + uri: string, + resolver?: Resolver, +): Promise { + if (typeof uri !== "string") throw new Error("uri is not string"); if (uri.startsWith(config.url)) { - throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); + throw new StatusError( + "cannot resolve local user", + 400, + "cannot resolve local user", + ); } if (resolver == null) resolver = new Resolver(); - const object = await resolver.resolve(uri) as any; + const object = (await resolver.resolve(uri)) as any; const person = validateActor(object, uri); @@ -153,58 +187,72 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise normalizeForSearch(tag)).splice(0, 32); + const tags = extractApHashtags(person.tag) + .map((tag) => normalizeForSearch(tag)) + .splice(0, 32); - const isBot = getApType(object) === 'Service'; + const isBot = getApType(object) === "Service"; - const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/); // Create user let user: IRemoteUser; try { // Start transaction - await db.transaction(async transactionalEntityManager => { - user = await transactionalEntityManager.save(new User({ - id: genId(), - avatarId: null, - bannerId: null, - createdAt: new Date(), - lastFetchedAt: new Date(), - name: truncate(person.name, nameLength), - isLocked: !!person.manuallyApprovesFollowers, - movedToUri: person.movedTo, - alsoKnownAs: person.alsoKnownAs, - 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 db.transaction(async (transactionalEntityManager) => { + user = (await transactionalEntityManager.save( + new User({ + id: genId(), + avatarId: null, + bannerId: null, + createdAt: new Date(), + lastFetchedAt: new Date(), + name: truncate(person.name, nameLength), + isLocked: !!person.manuallyApprovesFollowers, + movedToUri: person.movedTo, + alsoKnownAs: person.alsoKnownAs, + 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 ? 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, - })); + await transactionalEntityManager.save( + new UserProfile({ + userId: user.id, + description: person.summary + ? 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, - })); + await transactionalEntityManager.save( + new UserPublickey({ + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + }), + ); } }); } catch (e) { @@ -218,7 +266,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { - Instances.increment({ id: i.id }, 'usersCount', 1); + registerOrFetchInstanceDoc(host).then((i) => { + Instances.increment({ id: i.id }, "usersCount", 1); instanceChart.newUser(i.host); fetchInstanceMetadata(i); }); @@ -239,14 +287,13 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise - img == null - ? Promise.resolve(null) - : resolveImage(user!, img).catch(() => null), - )); + const [avatar, banner] = await Promise.all( + [person.icon, person.image].map((img) => + img == null + ? Promise.resolve(null) + : resolveImage(user!, img).catch(() => null), + ), + ); const avatarId = avatar ? avatar.id : null; const bannerId = banner ? banner.id : null; @@ -261,19 +308,19 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + const emojis = await extractEmojis(person.tag || [], host).catch((e) => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); - const emojiNames = emojis.map(emoji => emoji.name); + const emojiNames = emojis.map((emoji) => emoji.name); await Users.update(user!.id, { emojis: emojiNames, }); //#endregion - await updateFeatured(user!.id, resolver).catch(err => logger.error(err)); + await updateFeatured(user!.id, resolver).catch((err) => logger.error(err)); return user!; } @@ -285,16 +332,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); +export async function updatePerson( + uri: string, + resolver?: Resolver | null, + hint?: IObject, +): Promise { + if (typeof uri !== "string") throw new Error("uri is not string"); // Skip if the URI points to this server - if (uri.startsWith(config.url + '/')) { + if (uri.startsWith(`${config.url}/`)) { return; } //#region Already registered on this server? - const exist = await Users.findOneBy({ uri }) as IRemoteUser; + const exist = (await Users.findOneBy({ uri })) as IRemoteUser; if (exist == null) { return; @@ -303,46 +354,51 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint if (resolver == null) resolver = new Resolver(); - const object = hint || await resolver.resolve(uri); + const object = hint || (await resolver.resolve(uri)); const person = validateActor(object, uri); logger.info(`Updating the Person: ${person.id}`); // Fetch avatar and header image - const [avatar, banner] = await Promise.all([ - person.icon, - person.image, - ].map(img => - img == null - ? Promise.resolve(null) - : resolveImage(exist, img).catch(() => null), - )); + const [avatar, banner] = await Promise.all( + [person.icon, person.image].map((img) => + img == null + ? Promise.resolve(null) + : resolveImage(exist, img).catch(() => null), + ), + ); // Custom pictogram acquisition - const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { - logger.info(`extractEmojis: ${e}`); - return [] as Emoji[]; - }); + const emojis = await extractEmojis(person.tag || [], exist.host).catch( + (e) => { + logger.info(`extractEmojis: ${e}`); + return [] as Emoji[]; + }, + ); - const emojiNames = emojis.map(emoji => emoji.name); + const emojiNames = emojis.map((emoji) => emoji.name); const { fields } = analyzeAttachments(person.attachment || []); - const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); + 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 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), + 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', + isBot: getApType(object) === "Service", isCat: (person as any).isCat === true, isLocked: !!person.manuallyApprovesFollowers, movedToUri: person.movedTo, @@ -362,43 +418,59 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint await Users.update(exist.id, updates); if (person.publicKey) { - await UserPublickeys.update({ userId: exist.id }, { - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem, - }); + await UserPublickeys.update( + { userId: exist.id }, + { + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem, + }, + ); } - await UserProfiles.update({ userId: exist.id }, { - url: getOneApHrefNullable(person.url), - fields, - description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - birthday: bday ? bday[0] : null, - location: person['vcard:Address'] || null, - }); + await UserProfiles.update( + { userId: exist.id }, + { + url: getOneApHrefNullable(person.url), + fields, + description: person.summary + ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) + : null, + birthday: bday ? bday[0] : null, + location: person["vcard:Address"] || null, + }, + ); - publishInternalEvent('remoteUserUpdated', { id: exist.id }); + publishInternalEvent("remoteUserUpdated", { id: exist.id }); // Hashtag Update updateUsertags(exist, tags); // If the user in question is a follower, followers will also be updated. - await Followings.update({ - followerId: exist.id, - }, { - followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), - }); + await Followings.update( + { + followerId: exist.id, + }, + { + followerSharedInbox: + person.sharedInbox || + (person.endpoints ? person.endpoints.sharedInbox : undefined), + }, + ); - await updateFeatured(exist.id, resolver).catch(err => logger.error(err)); + await updateFeatured(exist.id, resolver).catch((err) => logger.error(err)); } /** * Resolve Person. * - * If the target person is registered in Calckey, it returns it; + * If the target person is registered in Calckey, it returns it; * otherwise, it fetches it from the remote server, registers it in Calckey, and returns it. */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); +export async function resolvePerson( + uri: string, + resolver?: Resolver, +): Promise { + if (typeof uri !== "string") throw new Error("uri is not string"); //#region If already registered on this server, return it. const exist = await fetchPerson(uri); @@ -414,39 +486,44 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise any - } = { - 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), - 'misskey:authentication:github': (id, login) => ({ id, login }), - 'misskey:authentication:discord': (id, name) => $discord(id, name), - }; + [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'; + if (typeof name !== "string") { + name = "unknown#0000"; } - const [username, discriminator] = name.split('#'); + 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'; + if (typeof source.value !== "string") { + source.value = "unknown"; } - const [id, username] = source.value.split('@'); + const [id, username] = source.value.split("@"); if (service) { - target[source.name.split(':')[2]] = service(id, username); + target[source.name.split(":")[2]] = service(id, username); } } -export function analyzeAttachments(attachments: IObject | IObject[] | undefined) { +export function analyzeAttachments( + attachments: IObject | IObject[] | undefined, +) { const fields: { - name: string, - value: string + name: string; + value: string; }[] = []; const services: { [x: string]: any } = {}; @@ -466,7 +543,7 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined) return { fields, services }; } -export async function updateFeatured(userId: User['id'], resolver?: Resolver) { +export async function updateFeatured(userId: User["id"], resolver?: Resolver) { const user = await Users.findOneByOrFail({ id: userId }); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -477,25 +554,34 @@ export async function updateFeatured(userId: User['id'], resolver?: Resolver) { // Resolve to (Ordered)Collection Object const collection = await resolver.resolveCollection(user.featured); - if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); + 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))); + 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: Maybe it doesn't have to be a Note. - .slice(0, 5) - .map(item => limit(() => resolveNote(item, resolver)))); + const featuredNotes = await Promise.all( + items + .filter((item) => getApType(item) === "Note") // TODO: Maybe it doesn't have to be a Note. + .slice(0, 5) + .map((item) => limit(() => resolveNote(item, resolver))), + ); - await db.transaction(async transactionalEntityManager => { - await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); + await db.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(UserNotePining, { + userId: user.id, + }); // For now, generate the id at a different time and maintain the order. let td = 0; - for (const note of featuredNotes.filter(note => note != null)) { + for (const note of featuredNotes.filter((note) => note != null)) { td -= 1000; transactionalEntityManager.insert(UserNotePining, { id: genId(new Date(Date.now() + td)), diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index b87d6ac1b..5d9385e56 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -1,31 +1,41 @@ -import config from '@/config/index.js'; -import Resolver from '../resolver.js'; -import { IObject, IQuestion, isQuestion } from '../type.js'; -import { apLogger } from '../logger.js'; -import { Notes, Polls } from '@/models/index.js'; -import { IPoll } from '@/models/entities/poll.js'; +import config from "@/config/index.js"; +import Resolver from "../resolver.js"; +import type { IObject, IQuestion } from "../type.js"; +import { isQuestion } from "../type.js"; +import { apLogger } from "../logger.js"; +import { Notes, Polls } from "@/models/index.js"; +import type { IPoll } from "@/models/entities/poll.js"; -export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { +export async function extractPollFromQuestion( + source: string | IObject, + resolver?: Resolver, +): Promise { if (resolver == null) resolver = new Resolver(); const question = await resolver.resolve(source); if (!isQuestion(question)) { - throw new Error('invalid type'); + throw new Error("invalid type"); } const multiple = !question.oneOf; - const expiresAt = question.endTime ? new Date(question.endTime) : question.closed ? new Date(question.closed) : null; + const expiresAt = question.endTime + ? new Date(question.endTime) + : question.closed + ? new Date(question.closed) + : null; if (multiple && !question.anyOf) { - throw new Error('invalid question'); + throw new Error("invalid question"); } - const choices = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.name!); + const choices = question[multiple ? "anyOf" : "oneOf"]!.map( + (x, i) => x.name!, + ); - const votes = question[multiple ? 'anyOf' : 'oneOf']! - .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); + const votes = question[multiple ? "anyOf" : "oneOf"]!.map( + (x, i) => (x.replies?.totalItems) || x._misskey_votes || 0, + ); return { choices, @@ -41,25 +51,25 @@ export async function extractPollFromQuestion(source: string | IObject, resolver * @returns true if updated */ export async function updateQuestion(value: any, resolver?: Resolver) { - const uri = typeof value === 'string' ? value : value.id; + const uri = typeof value === "string" ? value : value.id; // Skip if URI points to this server - if (uri.startsWith(config.url + '/')) throw new Error('uri points local'); + if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local"); //#region Already registered with this server? const note = await Notes.findOneBy({ uri }); - if (note == null) throw new Error('Question is not registed'); + if (note == null) throw new Error("Question is not registed"); const poll = await Polls.findOneBy({ noteId: note.id }); - if (poll == null) throw new Error('Question is not registed'); + if (poll == null) throw new Error("Question is not registed"); //#endregion // resolve new Question object if (resolver == null) resolver = new Resolver(); - const question = await resolver.resolve(value) as IQuestion; + const question = (await resolver.resolve(value)) as IQuestion; apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); - if (question.type !== 'Question') throw new Error('object is not a Question'); + if (question.type !== "Question") throw new Error("object is not a Question"); const apChoices = question.oneOf || question.anyOf; @@ -67,7 +77,8 @@ export async function updateQuestion(value: any, resolver?: Resolver) { for (const choice of poll.choices) { const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; + const newCount = apChoices!.filter((ap) => ap.name === choice)[0].replies! + .totalItems; if (oldCount !== newCount) { changed = true; @@ -75,9 +86,12 @@ export async function updateQuestion(value: any, resolver?: Resolver) { } } - await Polls.update({ noteId: note.id }, { - votes: poll.votes, - }); + await Polls.update( + { noteId: note.id }, + { + votes: poll.votes, + }, + ); return changed; } diff --git a/packages/backend/src/remote/activitypub/models/tag.ts b/packages/backend/src/remote/activitypub/models/tag.ts index 964dabad0..537cdecbd 100644 --- a/packages/backend/src/remote/activitypub/models/tag.ts +++ b/packages/backend/src/remote/activitypub/models/tag.ts @@ -1,18 +1,25 @@ -import { toArray } from '@/prelude/array.js'; -import { IObject, isHashtag, IApHashtag } from '../type.js'; +import { toArray } from "@/prelude/array.js"; +import type { IObject, IApHashtag } from "../type.js"; +import { isHashtag } from "../type.js"; -export function extractApHashtags(tags: IObject | IObject[] | null | undefined) { +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); + 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[] { +export function extractApHashtagObjects( + tags: IObject | IObject[] | null | undefined, +): IApHashtag[] { if (tags == null) return []; return toArray(tags).filter(isHashtag); } diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index d79043aaf..0d2cdb4a5 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -1,14 +1,20 @@ -import { IObject } from './type.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { performActivity } from './kernel/index.js'; -import { updatePerson } from './models/person.js'; +import type { IObject } from "./type.js"; +import type { CacheableRemoteUser } from "@/models/entities/user.js"; +import { performActivity } from "./kernel/index.js"; +import { updatePerson } from "./models/person.js"; -export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { +export default async ( + actor: CacheableRemoteUser, + activity: IObject, +): Promise => { await performActivity(actor, activity); // Update the remote user information if it is out of date if (actor.uri) { - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + if ( + actor.lastFetchedAt == null || + Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24 + ) { setImmediate(() => { updatePerson(actor.uri!); }); diff --git a/packages/backend/src/remote/activitypub/renderer/accept.ts b/packages/backend/src/remote/activitypub/renderer/accept.ts index cb01f6a91..fd145dcf9 100644 --- a/packages/backend/src/remote/activitypub/renderer/accept.ts +++ b/packages/backend/src/remote/activitypub/renderer/accept.ts @@ -1,8 +1,8 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; -export default (object: any, user: { id: User['id']; host: null }) => ({ - type: 'Accept', +export default (object: any, user: { id: User["id"]; host: null }) => ({ + type: "Accept", actor: `${config.url}/users/${user.id}`, object, }); diff --git a/packages/backend/src/remote/activitypub/renderer/add.ts b/packages/backend/src/remote/activitypub/renderer/add.ts index ec4788429..d8203ac1e 100644 --- a/packages/backend/src/remote/activitypub/renderer/add.ts +++ b/packages/backend/src/remote/activitypub/renderer/add.ts @@ -1,8 +1,8 @@ -import config from '@/config/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; export default (user: ILocalUser, target: any, object: any) => ({ - type: 'Add', + type: "Add", actor: `${config.url}/users/${user.id}`, target, object, diff --git a/packages/backend/src/remote/activitypub/renderer/announce.ts b/packages/backend/src/remote/activitypub/renderer/announce.ts index 2709fea51..cff79a3f7 100644 --- a/packages/backend/src/remote/activitypub/renderer/announce.ts +++ b/packages/backend/src/remote/activitypub/renderer/announce.ts @@ -1,5 +1,5 @@ -import config from '@/config/index.js'; -import { Note } from '@/models/entities/note.js'; +import config from "@/config/index.js"; +import type { Note } from "@/models/entities/note.js"; export default (object: any, note: Note) => { const attributedTo = `${config.url}/users/${note.userId}`; @@ -7,12 +7,12 @@ export default (object: any, note: Note) => { let to: string[] = []; let cc: string[] = []; - if (note.visibility === 'public') { - to = ['https://www.w3.org/ns/activitystreams#Public']; + if (note.visibility === "public") { + to = ["https://www.w3.org/ns/activitystreams#Public"]; cc = [`${attributedTo}/followers`]; - } else if (note.visibility === 'home') { + } else if (note.visibility === "home") { to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public']; + cc = ["https://www.w3.org/ns/activitystreams#Public"]; } else { return null; } @@ -20,7 +20,7 @@ export default (object: any, note: Note) => { return { id: `${config.url}/notes/${note.id}/activity`, actor: `${config.url}/users/${note.userId}`, - type: 'Announce', + type: "Announce", published: note.createdAt.toISOString(), to, cc, diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts index 802d7280b..c2ea267f3 100644 --- a/packages/backend/src/remote/activitypub/renderer/block.ts +++ b/packages/backend/src/remote/activitypub/renderer/block.ts @@ -1,5 +1,5 @@ -import config from '@/config/index.js'; -import { Blocking } from '@/models/entities/blocking.js'; +import config from "@/config/index.js"; +import type { Blocking } from "@/models/entities/blocking.js"; /** * Renders a block into its ActivityPub representation. @@ -8,11 +8,11 @@ import { Blocking } from '@/models/entities/blocking.js'; */ export function renderBlock(block: Blocking) { if (block.blockee?.uri == null) { - throw new Error('renderBlock: missing blockee uri'); + throw new Error("renderBlock: missing blockee uri"); } return { - type: 'Block', + type: "Block", id: `${config.url}/blocks/${block.id}`, actor: `${config.url}/users/${block.blockerId}`, object: block.blockee.uri, diff --git a/packages/backend/src/remote/activitypub/renderer/create.ts b/packages/backend/src/remote/activitypub/renderer/create.ts index 281a3cb2a..857f5722c 100644 --- a/packages/backend/src/remote/activitypub/renderer/create.ts +++ b/packages/backend/src/remote/activitypub/renderer/create.ts @@ -1,11 +1,11 @@ -import config from '@/config/index.js'; -import { Note } from '@/models/entities/note.js'; +import config from "@/config/index.js"; +import type { Note } from "@/models/entities/note.js"; export default (object: any, note: Note) => { const activity = { id: `${config.url}/notes/${note.id}/activity`, actor: `${config.url}/users/${note.userId}`, - type: 'Create', + type: "Create", published: note.createdAt.toISOString(), object, } as any; diff --git a/packages/backend/src/remote/activitypub/renderer/delete.ts b/packages/backend/src/remote/activitypub/renderer/delete.ts index 4edd3a880..70bdc3492 100644 --- a/packages/backend/src/remote/activitypub/renderer/delete.ts +++ b/packages/backend/src/remote/activitypub/renderer/delete.ts @@ -1,8 +1,8 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; -export default (object: any, user: { id: User['id']; host: null }) => ({ - type: 'Delete', +export default (object: any, user: { id: User["id"]; host: null }) => ({ + type: "Delete", actor: `${config.url}/users/${user.id}`, object, published: new Date().toISOString(), diff --git a/packages/backend/src/remote/activitypub/renderer/document.ts b/packages/backend/src/remote/activitypub/renderer/document.ts index c973de4c4..1c2ca89d9 100644 --- a/packages/backend/src/remote/activitypub/renderer/document.ts +++ b/packages/backend/src/remote/activitypub/renderer/document.ts @@ -1,8 +1,8 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles } from "@/models/index.js"; export default (file: DriveFile) => ({ - type: 'Document', + type: "Document", mediaType: file.type, url: DriveFiles.getPublicUrl(file), name: file.comment, diff --git a/packages/backend/src/remote/activitypub/renderer/emoji.ts b/packages/backend/src/remote/activitypub/renderer/emoji.ts index 0bf15eefd..3d9b8cd55 100644 --- a/packages/backend/src/remote/activitypub/renderer/emoji.ts +++ b/packages/backend/src/remote/activitypub/renderer/emoji.ts @@ -1,14 +1,17 @@ -import config from '@/config/index.js'; -import { Emoji } from '@/models/entities/emoji.js'; +import config from "@/config/index.js"; +import type { Emoji } from "@/models/entities/emoji.js"; export default (emoji: Emoji) => ({ id: `${config.url}/emojis/${emoji.name}`, - type: 'Emoji', + type: "Emoji", name: `:${emoji.name}:`, - updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, + updated: + emoji.updatedAt != null + ? emoji.updatedAt.toISOString() + : new Date().toISOString, icon: { - type: 'Image', - mediaType: emoji.type || 'image/png', + type: "Image", + mediaType: emoji.type || "image/png", url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため }, }); diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts index 58eadddba..f94d508e1 100644 --- a/packages/backend/src/remote/activitypub/renderer/flag.ts +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -1,13 +1,18 @@ -import config from '@/config/index.js'; -import { IObject, IActivity } from '@/remote/activitypub/type.js'; -import { ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { getInstanceActor } from '@/services/instance-actor.js'; +import config from "@/config/index.js"; +import { IObject, IActivity } from "@/remote/activitypub/type.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { IRemoteUser } from "@/models/entities/user.js"; +import { getInstanceActor } from "@/services/instance-actor.js"; // to anonymise reporters, the reporting actor must be a system user // object has to be a uri or array of uris -export const renderFlag = (user: ILocalUser, object: [string], content: string) => { +export const renderFlag = ( + user: ILocalUser, + object: [string], + content: string, +) => { return { - type: 'Flag', + type: "Flag", actor: `${config.url}/users/${user.id}`, content, object, diff --git a/packages/backend/src/remote/activitypub/renderer/follow-relay.ts b/packages/backend/src/remote/activitypub/renderer/follow-relay.ts index 2c9678090..ad7f05bf8 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-relay.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-relay.ts @@ -1,13 +1,13 @@ -import config from '@/config/index.js'; -import { Relay } from '@/models/entities/relay.js'; -import { ILocalUser } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { Relay } from "@/models/entities/relay.js"; +import type { ILocalUser } from "@/models/entities/user.js"; export function renderFollowRelay(relay: Relay, relayActor: ILocalUser) { const follow = { id: `${config.url}/activities/follow-relay/${relay.id}`, - type: 'Follow', + type: "Follow", actor: `${config.url}/users/${relayActor.id}`, - object: 'https://www.w3.org/ns/activitystreams#Public', + object: "https://www.w3.org/ns/activitystreams#Public", }; return follow; diff --git a/packages/backend/src/remote/activitypub/renderer/follow-user.ts b/packages/backend/src/remote/activitypub/renderer/follow-user.ts index 9a8a16d74..22ee429ff 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow-user.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow-user.ts @@ -1,12 +1,12 @@ -import config from '@/config/index.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import { Users } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; /** * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ -export default async function renderFollowUser(id: User['id']): Promise { +export default async function renderFollowUser(id: User["id"]): Promise { const user = await Users.findOneByOrFail({ id: id }); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts index 00fac18ad..3ff89c12a 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow.ts @@ -1,13 +1,21 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; -export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => { +export default ( + 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 ?? `${config.url}/follows/${follower.id}/${followee.id}`, - type: 'Follow', - actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri, - object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri, + type: "Follow", + actor: Users.isLocalUser(follower) + ? `${config.url}/users/${follower.id}` + : follower.uri, + object: Users.isLocalUser(followee) + ? `${config.url}/users/${followee.id}` + : followee.uri, } as any; return follow; diff --git a/packages/backend/src/remote/activitypub/renderer/hashtag.ts b/packages/backend/src/remote/activitypub/renderer/hashtag.ts index a7b441e00..a00cd1ff5 100644 --- a/packages/backend/src/remote/activitypub/renderer/hashtag.ts +++ b/packages/backend/src/remote/activitypub/renderer/hashtag.ts @@ -1,7 +1,7 @@ -import config from '@/config/index.js'; +import config from "@/config/index.js"; export default (tag: string) => ({ - type: 'Hashtag', + type: "Hashtag", href: `${config.url}/tags/${encodeURIComponent(tag)}`, name: `#${tag}`, }); diff --git a/packages/backend/src/remote/activitypub/renderer/image.ts b/packages/backend/src/remote/activitypub/renderer/image.ts index c7d5a31a2..96183c7ad 100644 --- a/packages/backend/src/remote/activitypub/renderer/image.ts +++ b/packages/backend/src/remote/activitypub/renderer/image.ts @@ -1,8 +1,8 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles } from "@/models/index.js"; export default (file: DriveFile) => ({ - type: 'Image', + type: "Image", url: DriveFiles.getPublicUrl(file), sensitive: file.isSensitive, name: file.comment, diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 68bdc80f2..7b98cf2d7 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -1,63 +1,73 @@ -import { v4 as uuid } from 'uuid'; -import config from '@/config/index.js'; -import { getUserKeypair } from '@/misc/keypair-store.js'; -import type { User } from '@/models/entities/user.js'; -import { LdSignature } from '../misc/ld-signature.js'; -import type { IActivity } from '../type.js'; +import { v4 as uuid } from "uuid"; +import config from "@/config/index.js"; +import { getUserKeypair } from "@/misc/keypair-store.js"; +import type { User } from "@/models/entities/user.js"; +import { LdSignature } from "../misc/ld-signature.js"; +import type { IActivity } from "../type.js"; export const renderActivity = (x: any): IActivity | null => { if (x == null) return null; - if (typeof x === 'object' && x.id == null) { + if (typeof x === "object" && x.id == null) { x.id = `${config.url}/${uuid()}`; } - return Object.assign({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - // as non-standards - manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', - movedToUri: 'as:movedTo', - sensitive: 'as:sensitive', - Hashtag: 'as:Hashtag', - quoteUri: 'fedibird:quoteUri', - 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', - // Fedibird - fedibird: 'http://fedibird.com/ns#', - // vcard - vcard: 'http://www.w3.org/2006/vcard/ns#', - }, - ], - }, x); + return Object.assign( + { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + // as non-standards + manuallyApprovesFollowers: "as:manuallyApprovesFollowers", + movedToUri: "as:movedTo", + sensitive: "as:sensitive", + Hashtag: "as:Hashtag", + quoteUri: "fedibird:quoteUri", + 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", + // Fedibird + fedibird: "http://fedibird.com/ns#", + // vcard + vcard: "http://www.w3.org/2006/vcard/ns#", + }, + ], + }, + x, + ); }; -export const attachLdSignature = async (activity: any, user: { id: User['id']; host: null; }): Promise => { +export const attachLdSignature = async ( + activity: any, + user: { id: User["id"]; host: null }, +): Promise => { if (activity == null) return null; const keypair = await getUserKeypair(user.id); const ldSignature = new LdSignature(); ldSignature.debug = false; - activity = await ldSignature.signRsaSignature2017(activity, keypair.privateKey, `${config.url}/users/${user.id}#main-key`); + activity = await ldSignature.signRsaSignature2017( + activity, + keypair.privateKey, + `${config.url}/users/${user.id}#main-key`, + ); return activity; }; diff --git a/packages/backend/src/remote/activitypub/renderer/key.ts b/packages/backend/src/remote/activitypub/renderer/key.ts index c4f3d464f..084bb5361 100644 --- a/packages/backend/src/remote/activitypub/renderer/key.ts +++ b/packages/backend/src/remote/activitypub/renderer/key.ts @@ -1,14 +1,14 @@ -import config from '@/config/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { UserKeypair } from '@/models/entities/user-keypair.js'; -import { createPublicKey } from 'node:crypto'; +import config from "@/config/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import type { UserKeypair } from "@/models/entities/user-keypair.js"; +import { createPublicKey } from "node:crypto"; export default (user: ILocalUser, key: UserKeypair, postfix?: string) => ({ - id: `${config.url}/users/${user.id}${postfix || '/publickey'}`, - type: 'Key', + id: `${config.url}/users/${user.id}${postfix || "/publickey"}`, + type: "Key", owner: `${config.url}/users/${user.id}`, publicKeyPem: createPublicKey(key.publicKey).export({ - type: 'spki', - format: 'pem', + type: "spki", + format: "pem", }), }); diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index a54095139..53c66c5c9 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -1,34 +1,36 @@ -import { IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { Note } from '@/models/entities/note.js'; -import { Emojis } from '@/models/index.js'; -import renderEmoji from './emoji.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { IsNull } from "typeorm"; +import config from "@/config/index.js"; +import type { NoteReaction } from "@/models/entities/note-reaction.js"; +import type { Note } from "@/models/entities/note.js"; +import { Emojis } from "@/models/index.js"; +import renderEmoji from "./emoji.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; export const renderLike = async (noteReaction: NoteReaction, note: Note) => { const reaction = noteReaction.reaction; const meta = await fetchMeta(); const object = { - type: 'Like', + type: "Like", id: `${config.url}/likes/${noteReaction.id}`, actor: `${config.url}/users/${noteReaction.userId}`, object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`, - ... (!meta.defaultReaction.includes(reaction) ? { - content: reaction, - _misskey_reaction: reaction, - } : {}), + ...(!meta.defaultReaction.includes(reaction) + ? { + content: reaction, + _misskey_reaction: reaction, + } + : {}), } as any; - if (reaction.startsWith(':')) { - const name = reaction.replace(/:/g, ''); + if (reaction.startsWith(":")) { + const name = reaction.replace(/:/g, ""); const emoji = await Emojis.findOneBy({ name, host: IsNull(), }); - if (emoji) object.tag = [ renderEmoji(emoji) ]; + if (emoji) object.tag = [renderEmoji(emoji)]; } return object; diff --git a/packages/backend/src/remote/activitypub/renderer/mention.ts b/packages/backend/src/remote/activitypub/renderer/mention.ts index c7e62e884..e7f0435c1 100644 --- a/packages/backend/src/remote/activitypub/renderer/mention.ts +++ b/packages/backend/src/remote/activitypub/renderer/mention.ts @@ -1,9 +1,13 @@ -import config from '@/config/index.js'; -import { User, ILocalUser } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; +import config from "@/config/index.js"; +import type { User, ILocalUser } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; export default (mention: User) => ({ - type: 'Mention', - href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/users/${(mention as ILocalUser).id}`, - name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, + type: "Mention", + href: Users.isRemoteUser(mention) + ? mention.uri + : `${config.url}/users/${(mention as ILocalUser).id}`, + name: Users.isRemoteUser(mention) + ? `@${mention.username}@${mention.host}` + : `@${(mention as ILocalUser).username}`, }); diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index 83df0b10d..2ad2fec9f 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -1,21 +1,27 @@ -import { In, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { Poll } from '@/models/entities/poll.js'; -import toHtml from '../misc/get-note-html.js'; -import renderEmoji from './emoji.js'; -import renderMention from './mention.js'; -import renderHashtag from './hashtag.js'; -import renderDocument from './document.js'; +import { In, IsNull } from "typeorm"; +import config from "@/config/index.js"; +import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles, Notes, Users, Emojis, Polls } from "@/models/index.js"; +import type { Emoji } from "@/models/entities/emoji.js"; +import type { Poll } from "@/models/entities/poll.js"; +import toHtml from "../misc/get-note-html.js"; +import renderEmoji from "./emoji.js"; +import renderMention from "./mention.js"; +import renderHashtag from "./hashtag.js"; +import renderDocument from "./document.js"; -export default async function renderNote(note: Note, dive = true, isTalk = false): Promise> { +export default async function renderNote( + note: Note, + dive = true, + isTalk = false, +): Promise> { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; const items = await DriveFiles.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; + return ids + .map((id) => items.find((item) => item.id === id)) + .filter((item) => item != null) as DriveFile[]; }; let inReplyTo; @@ -55,34 +61,39 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const attributedTo = `${config.url}/users/${note.userId}`; - const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); + 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']; + if (note.visibility === "public") { + to = ["https://www.w3.org/ns/activitystreams#Public"]; cc = [`${attributedTo}/followers`].concat(mentions); - } else if (note.visibility === 'home') { + } else if (note.visibility === "home") { to = [`${attributedTo}/followers`]; - cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions); - } else if (note.visibility === '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 Users.findBy({ - id: In(note.mentions), - }) : []; + const mentionedUsers = + note.mentions.length > 0 + ? await Users.findBy({ + id: In(note.mentions), + }) + : []; - const hashtagTags = (note.tags || []).map(tag => renderHashtag(tag)); - const mentionTags = mentionedUsers.map(u => renderMention(u)); + const hashtagTags = (note.tags || []).map((tag) => renderHashtag(tag)); + const mentionTags = mentionedUsers.map((u) => renderMention(u)); const files = await getPromisedFiles(note.fileIds); - const text = note.text ?? ''; + const text = note.text ?? ""; let poll: Poll | null = null; if (note.hasPoll) { @@ -95,44 +106,49 @@ export default async function renderNote(note: Note, dive = true, isTalk = false apText += `\n\nRE: ${quote}`; } - const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + const summary = note.cw === "" ? String.fromCharCode(0x200b) : note.cw; - const content = toHtml(Object.assign({}, note, { - text: apText, - })); + const content = toHtml( + Object.assign({}, note, { + text: apText, + }), + ); const emojis = await getEmojis(note.emojis); - const apemojis = emojis.map(emoji => renderEmoji(emoji)); + const apemojis = emojis.map((emoji) => renderEmoji(emoji)); - const tag = [ - ...hashtagTags, - ...mentionTags, - ...apemojis, - ]; + const tag = [...hashtagTags, ...mentionTags, ...apemojis]; - const asPoll = poll ? { - type: 'Question', - content: toHtml(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 asPoll = poll + ? { + type: "Question", + content: toHtml( + 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, - } : {}; + const asTalk = isTalk + ? { + _misskey_talk: true, + } + : {}; return { id: `${config.url}/notes/${note.id}`, - type: 'Note', + type: "Note", attributedTo, summary, content, @@ -149,7 +165,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false cc, inReplyTo, attachment: files.map(renderDocument), - sensitive: note.cw != null || files.some(file => file.isSensitive), + sensitive: note.cw != null || files.some((file) => file.isSensitive), tag, ...asPoll, ...asTalk, @@ -160,11 +176,13 @@ export async function getEmojis(names: string[]): Promise { if (names == null || names.length === 0) return []; const emojis = await Promise.all( - names.map(name => Emojis.findOneBy({ - name, - host: IsNull(), - })), + names.map((name) => + Emojis.findOneBy({ + name, + host: IsNull(), + }), + ), ); - return emojis.filter(emoji => emoji != null) as Emoji[]; + return emojis.filter((emoji) => emoji != null) as Emoji[]; } diff --git a/packages/backend/src/remote/activitypub/renderer/ordered-collection-page.ts b/packages/backend/src/remote/activitypub/renderer/ordered-collection-page.ts index c5e25f577..2275c9c94 100644 --- a/packages/backend/src/remote/activitypub/renderer/ordered-collection-page.ts +++ b/packages/backend/src/remote/activitypub/renderer/ordered-collection-page.ts @@ -7,11 +7,18 @@ * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ -export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { +export default function ( + id: string, + totalItems: any, + orderedItems: any, + partOf: string, + prev?: string, + next?: string, +) { const page = { id, partOf, - type: 'OrderedCollectionPage', + type: "OrderedCollectionPage", totalItems, orderedItems, } as any; diff --git a/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts b/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts index ff9a77be3..b975399b6 100644 --- a/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts +++ b/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts @@ -6,9 +6,15 @@ * @param last URL of last page (optional) * @param orderedItems attached objects (optional) */ -export default function(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: Record[]): { +export default function ( + id: string | null, + totalItems: any, + first?: string, + last?: string, + orderedItems?: Record[], +): { id: string | null; - type: 'OrderedCollection'; + type: "OrderedCollection"; totalItems: any; first?: string; last?: string; @@ -16,7 +22,7 @@ export default function(id: string | null, totalItems: any, first?: string, last } { const page: any = { id, - type: 'OrderedCollection', + type: "OrderedCollection", totalItems, }; diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index 4f73d28a6..cad3374cc 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -1,60 +1,66 @@ -import { URL } from 'node:url'; -import * as mfm from 'mfm-js'; -import config from '@/config/index.js'; -import type { ILocalUser } from '@/models/entities/user.js'; -import { DriveFiles, UserProfiles } from '@/models/index.js'; -import { getUserKeypair } from '@/misc/keypair-store.js'; -import { toHtml } from '../../../mfm/to-html.js'; -import renderImage from './image.js'; -import renderKey from './key.js'; -import { getEmojis } from './note.js'; -import renderEmoji from './emoji.js'; -import renderHashtag from './hashtag.js'; -import type { IIdentifier } from '../models/identifier.js'; +import { URL } from "node:url"; +import * as mfm from "mfm-js"; +import config from "@/config/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { DriveFiles, UserProfiles } from "@/models/index.js"; +import { getUserKeypair } from "@/misc/keypair-store.js"; +import { toHtml } from "../../../mfm/to-html.js"; +import renderImage from "./image.js"; +import renderKey from "./key.js"; +import { getEmojis } from "./note.js"; +import renderEmoji from "./emoji.js"; +import renderHashtag from "./hashtag.js"; +import type { IIdentifier } from "../models/identifier.js"; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; const isSystem = !!user.username.match(/\./); const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? DriveFiles.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), - user.bannerId ? DriveFiles.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + user.avatarId + ? DriveFiles.findOneBy({ id: user.avatarId }) + : Promise.resolve(undefined), + user.bannerId + ? DriveFiles.findOneBy({ id: user.bannerId }) + : Promise.resolve(undefined), UserProfiles.findOneByOrFail({ userId: user.id }), ]); const attachment: { - type: 'PropertyValue', - name: string, - value: string, - identifier?: IIdentifier + type: "PropertyValue"; + name: string; + value: string; + identifier?: IIdentifier; }[] = []; if (profile.fields) { for (const field of profile.fields) { attachment.push({ - type: 'PropertyValue', + type: "PropertyValue", name: field.name, - value: (field.value != null && field.value.match(/^https?:/)) - ? `${new URL(field.value).href}` - : field.value, + value: + field.value?.match(/^https?:/) + ? `${ + new URL(field.value).href + }` + : field.value, }); } } const emojis = await getEmojis(user.emojis); - const apemojis = emojis.map(emoji => renderEmoji(emoji)); + const apemojis = emojis.map((emoji) => renderEmoji(emoji)); - const hashtagTags = (user.tags || []).map(tag => renderHashtag(tag)); + const hashtagTags = (user.tags || []).map((tag) => renderHashtag(tag)); - const tag = [ - ...apemojis, - ...hashtagTags, - ]; + const tag = [...apemojis, ...hashtagTags]; const keypair = await getUserKeypair(user.id); const person = { - type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', + type: isSystem ? "Application" : user.isBot ? "Service" : "Person", id, inbox: `${id}/inbox`, outbox: `${id}/outbox`, @@ -66,13 +72,15 @@ export async function renderPerson(user: ILocalUser) { url: `${config.url}/@${user.username}`, preferredUsername: user.username, name: user.name, - summary: profile.description ? toHtml(mfm.parse(profile.description)) : null, + summary: profile.description + ? toHtml(mfm.parse(profile.description)) + : null, icon: avatar ? renderImage(avatar) : null, image: banner ? renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, discoverable: !!user.isExplorable, - publicKey: renderKey(user, keypair, '#main-key'), + publicKey: renderKey(user, keypair, "#main-key"), isCat: user.isCat, attachment: attachment.length ? attachment : undefined, } as any; @@ -86,11 +94,11 @@ export async function renderPerson(user: ILocalUser) { } if (profile.birthday) { - person['vcard:bday'] = profile.birthday; + person["vcard:bday"] = profile.birthday; } if (profile.location) { - person['vcard:Address'] = profile.location; + person["vcard:Address"] = profile.location; } return person; diff --git a/packages/backend/src/remote/activitypub/renderer/question.ts b/packages/backend/src/remote/activitypub/renderer/question.ts index d4d1b590a..cb89aa758 100644 --- a/packages/backend/src/remote/activitypub/renderer/question.ts +++ b/packages/backend/src/remote/activitypub/renderer/question.ts @@ -1,19 +1,23 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { Poll } from '@/models/entities/poll.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import type { Poll } from "@/models/entities/poll.js"; -export default async function renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { +export default async function renderQuestion( + user: { id: User["id"] }, + note: Note, + poll: Poll, +) { const question = { - type: 'Question', + type: "Question", id: `${config.url}/questions/${note.id}`, actor: `${config.url}/users/${user.id}`, - content: note.text || '', - [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ + content: note.text || "", + [poll.multiple ? "anyOf" : "oneOf"]: poll.choices.map((text, i) => ({ name: text, _misskey_votes: poll.votes[i], replies: { - type: 'Collection', + type: "Collection", totalItems: poll.votes[i], }, })), diff --git a/packages/backend/src/remote/activitypub/renderer/read.ts b/packages/backend/src/remote/activitypub/renderer/read.ts index a30e649f6..212e7e8dd 100644 --- a/packages/backend/src/remote/activitypub/renderer/read.ts +++ b/packages/backend/src/remote/activitypub/renderer/read.ts @@ -1,9 +1,12 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; -export const renderReadActivity = (user: { id: User['id'] }, message: MessagingMessage) => ({ - type: 'Read', +export const renderReadActivity = ( + user: { id: User["id"] }, + message: MessagingMessage, +) => ({ + type: "Read", actor: `${config.url}/users/${user.id}`, object: message.uri, }); diff --git a/packages/backend/src/remote/activitypub/renderer/reject.ts b/packages/backend/src/remote/activitypub/renderer/reject.ts index ab4cc1646..7ac445241 100644 --- a/packages/backend/src/remote/activitypub/renderer/reject.ts +++ b/packages/backend/src/remote/activitypub/renderer/reject.ts @@ -1,8 +1,8 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; -export default (object: any, user: { id: User['id'] }) => ({ - type: 'Reject', +export default (object: any, user: { id: User["id"] }) => ({ + type: "Reject", actor: `${config.url}/users/${user.id}`, object, }); diff --git a/packages/backend/src/remote/activitypub/renderer/remove.ts b/packages/backend/src/remote/activitypub/renderer/remove.ts index 1be3edc5d..e3b3fef85 100644 --- a/packages/backend/src/remote/activitypub/renderer/remove.ts +++ b/packages/backend/src/remote/activitypub/renderer/remove.ts @@ -1,8 +1,8 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; -export default (user: { id: User['id'] }, target: any, object: any) => ({ - type: 'Remove', +export default (user: { id: User["id"] }, target: any, object: any) => ({ + type: "Remove", actor: `${config.url}/users/${user.id}`, target, object, diff --git a/packages/backend/src/remote/activitypub/renderer/tombstone.ts b/packages/backend/src/remote/activitypub/renderer/tombstone.ts index 313ca74e9..5c4003c75 100644 --- a/packages/backend/src/remote/activitypub/renderer/tombstone.ts +++ b/packages/backend/src/remote/activitypub/renderer/tombstone.ts @@ -1,4 +1,4 @@ export default (id: string) => ({ id, - type: 'Tombstone', + type: "Tombstone", }); diff --git a/packages/backend/src/remote/activitypub/renderer/undo.ts b/packages/backend/src/remote/activitypub/renderer/undo.ts index 46631df9e..249d643b2 100644 --- a/packages/backend/src/remote/activitypub/renderer/undo.ts +++ b/packages/backend/src/remote/activitypub/renderer/undo.ts @@ -1,12 +1,16 @@ -import config from '@/config/index.js'; -import { ILocalUser, User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; +import { ILocalUser } from "@/models/entities/user.js"; -export default (object: any, user: { id: User['id'] }) => { +export default (object: any, user: { id: User["id"] }) => { if (object == null) return null; - const id = typeof object.id === 'string' && object.id.startsWith(config.url) ? `${object.id}/undo` : undefined; + const id = + typeof object.id === "string" && object.id.startsWith(config.url) + ? `${object.id}/undo` + : undefined; return { - type: 'Undo', + type: "Undo", ...(id ? { id } : {}), actor: `${config.url}/users/${user.id}`, object, diff --git a/packages/backend/src/remote/activitypub/renderer/update.ts b/packages/backend/src/remote/activitypub/renderer/update.ts index cf880f03f..765a52f06 100644 --- a/packages/backend/src/remote/activitypub/renderer/update.ts +++ b/packages/backend/src/remote/activitypub/renderer/update.ts @@ -1,12 +1,12 @@ -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; -export default (object: any, user: { id: User['id'] }) => { +export default (object: any, user: { id: User["id"] }) => { const activity = { id: `${config.url}/users/${user.id}#updates/${new Date().getTime()}`, actor: `${config.url}/users/${user.id}`, - type: 'Update', - to: [ 'https://www.w3.org/ns/activitystreams#Public' ], + type: "Update", + to: ["https://www.w3.org/ns/activitystreams#Public"], object, published: new Date().toISOString(), } as any; diff --git a/packages/backend/src/remote/activitypub/renderer/vote.ts b/packages/backend/src/remote/activitypub/renderer/vote.ts index b6eb8e095..21234a112 100644 --- a/packages/backend/src/remote/activitypub/renderer/vote.ts +++ b/packages/backend/src/remote/activitypub/renderer/vote.ts @@ -1,19 +1,25 @@ -import config from '@/config/index.js'; -import { Note } from '@/models/entities/note.js'; -import { IRemoteUser, User } from '@/models/entities/user.js'; -import { PollVote } from '@/models/entities/poll-vote.js'; -import { Poll } from '@/models/entities/poll.js'; +import config from "@/config/index.js"; +import type { Note } from "@/models/entities/note.js"; +import type { IRemoteUser, User } from "@/models/entities/user.js"; +import type { PollVote } from "@/models/entities/poll-vote.js"; +import type { Poll } from "@/models/entities/poll.js"; -export default async function renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser): Promise { +export default async function renderVote( + user: { id: User["id"] }, + vote: PollVote, + note: Note, + poll: Poll, + pollOwner: IRemoteUser, +): Promise { return { id: `${config.url}/users/${user.id}#votes/${vote.id}/activity`, actor: `${config.url}/users/${user.id}`, - type: 'Create', + type: "Create", to: [pollOwner.uri], published: new Date().toISOString(), object: { id: `${config.url}/users/${user.id}#votes/${vote.id}`, - type: 'Note', + type: "Note", attributedTo: `${config.url}/users/${user.id}`, to: [pollOwner.uri], inReplyTo: note.uri, diff --git a/packages/backend/src/remote/activitypub/request.ts b/packages/backend/src/remote/activitypub/request.ts index 5cbfd8c25..ffb3e25a3 100644 --- a/packages/backend/src/remote/activitypub/request.ts +++ b/packages/backend/src/remote/activitypub/request.ts @@ -1,10 +1,10 @@ -import config from '@/config/index.js'; -import { getUserKeypair } from '@/misc/keypair-store.js'; -import { User } from '@/models/entities/user.js'; -import { getResponse } from '../../misc/fetch.js'; -import { createSignedPost, createSignedGet } from './ap-request.js'; +import config from "@/config/index.js"; +import { getUserKeypair } from "@/misc/keypair-store.js"; +import type { User } from "@/models/entities/user.js"; +import { getResponse } from "../../misc/fetch.js"; +import { createSignedPost, createSignedGet } from "./ap-request.js"; -export default async (user: { id: User['id'] }, url: string, object: any) => { +export default async (user: { id: User["id"] }, url: string, object: any) => { const body = JSON.stringify(object); const keypair = await getUserKeypair(user.id); @@ -17,7 +17,7 @@ export default async (user: { id: User['id'] }, url: string, object: any) => { url, body, additionalHeaders: { - 'User-Agent': config.userAgent, + "User-Agent": config.userAgent, }, }); @@ -34,7 +34,7 @@ export default async (user: { id: User['id'] }, url: string, object: any) => { * @param user http-signature user * @param url URL to fetch */ -export async function signedGet(url: string, user: { id: User['id'] }) { +export async function signedGet(url: string, user: { id: User["id"] }) { const keypair = await getUserKeypair(user.id); const req = createSignedGet({ @@ -44,7 +44,7 @@ export async function signedGet(url: string, user: { id: User['id'] }) { }, url, additionalHeaders: { - 'User-Agent': config.userAgent, + "User-Agent": config.userAgent, }, }); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 26ec4e8a6..054792760 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -1,21 +1,28 @@ -import config from '@/config/index.js'; -import { getJson } from '@/misc/fetch.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { getInstanceActor } from '@/services/instance-actor.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { extractDbHost, isSelfHost } from '@/misc/convert-host.js'; -import { signedGet } from './request.js'; -import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection, getApId } from './type.js'; -import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js'; -import { parseUri } from './db-resolver.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import { renderLike } from '@/remote/activitypub/renderer/like.js'; -import { renderPerson } from '@/remote/activitypub/renderer/person.js'; -import renderQuestion from '@/remote/activitypub/renderer/question.js'; -import renderCreate from '@/remote/activitypub/renderer/create.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +import config from "@/config/index.js"; +import { getJson } from "@/misc/fetch.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { getInstanceActor } from "@/services/instance-actor.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { extractDbHost, isSelfHost } from "@/misc/convert-host.js"; +import { signedGet } from "./request.js"; +import type { IObject, ICollection, IOrderedCollection } from "./type.js"; +import { isCollectionOrOrderedCollection, getApId } from "./type.js"; +import { + FollowRequests, + Notes, + NoteReactions, + Polls, + Users, +} from "@/models/index.js"; +import { parseUri } from "./db-resolver.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import { renderLike } from "@/remote/activitypub/renderer/like.js"; +import { renderPerson } from "@/remote/activitypub/renderer/person.js"; +import renderQuestion from "@/remote/activitypub/renderer/question.js"; +import renderCreate from "@/remote/activitypub/renderer/create.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; export default class Resolver { private history: Set; @@ -31,7 +38,9 @@ export default class Resolver { return Array.from(this.history); } - public async resolveCollection(value: string | IObject): Promise { + public async resolveCollection( + value: string | IObject, + ): Promise { const collection = await this.resolve(value); if (isCollectionOrOrderedCollection(collection)) { @@ -43,20 +52,20 @@ export default class Resolver { public async resolve(value: string | IObject): Promise { if (value == null) { - throw new Error('resolvee is null (or undefined)'); + throw new Error("resolvee is null (or undefined)"); } - if (typeof value !== 'string') { - if (typeof value.id !== 'undefined') { + if (typeof value !== "string") { + if (typeof value.id !== "undefined") { const host = extractDbHost(getApId(value)); if (await shouldBlockInstance(host)) { - throw new Error('instance is blocked'); + throw new Error("instance is blocked"); } } return value; } - if (value.includes('#')) { + 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. @@ -64,10 +73,10 @@ export default class Resolver { } if (this.history.has(value)) { - throw new Error('cannot resolve already resolved one'); + throw new Error("cannot resolve already resolved one"); } if (this.recursionLimit && this.history.size > this.recursionLimit) { - throw new Error('hit recursion limit'); + throw new Error("hit recursion limit"); } this.history.add(value); @@ -78,27 +87,36 @@ export default class Resolver { const meta = await fetchMeta(); if (await shouldBlockInstance(host, meta)) { - throw new Error('Instance is blocked'); + throw new Error("Instance is blocked"); } - if (meta.privateMode && config.host !== host && !meta.allowedHosts.includes(host)) { - throw new Error('Instance is not allowed'); + if ( + meta.privateMode && + config.host !== host && + !meta.allowedHosts.includes(host) + ) { + throw new Error("Instance is not allowed"); } if (!this.user) { this.user = await getInstanceActor(); } - const object = (this.user - ? await signedGet(value, this.user) - : await getJson(value, 'application/activity+json, application/ld+json')) as IObject; + const object = ( + this.user + ? await signedGet(value, this.user) + : await 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'); + 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; @@ -106,39 +124,44 @@ export default class Resolver { private resolveLocal(url: string): Promise { const parsed = parseUri(url); - if (!parsed.local) throw new Error('resolveLocal: not local'); + if (!parsed.local) throw new Error("resolveLocal: not local"); switch (parsed.type) { - case 'notes': - return Notes.findOneByOrFail({ id: parsed.id }) - .then(note => { - if (parsed.rest === 'activity') { + case "notes": + return Notes.findOneByOrFail({ id: parsed.id }).then((note) => { + if (parsed.rest === "activity") { // this refers to the create activity and not the note itself return renderActivity(renderCreate(renderNote(note))); } else { return renderNote(note); } }); - case 'users': - return Users.findOneByOrFail({ id: parsed.id }) - .then(user => renderPerson(user as ILocalUser)); - case 'questions': + case "users": + return Users.findOneByOrFail({ id: parsed.id }).then((user) => + renderPerson(user as ILocalUser), + ); + case "questions": // Polls are indexed by the note they are attached to. return Promise.all([ Notes.findOneByOrFail({ id: parsed.id }), Polls.findOneByOrFail({ noteId: parsed.id }), - ]) - .then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll)); - case 'likes': - return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null }))); - case 'follows': + ]).then(([note, poll]) => + renderQuestion({ id: note.userId }, note, poll), + ); + case "likes": + return NoteReactions.findOneByOrFail({ id: parsed.id }).then( + (reaction) => renderActivity(renderLike(reaction, { uri: null })), + ); + case "follows": // rest should be - if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); + if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) + throw new Error("resolveLocal: invalid follow URI"); return Promise.all( - [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })) - ) - .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url))); + [parsed.id, parsed.rest].map((id) => Users.findOneByOrFail({ id })), + ).then(([follower, followee]) => + renderActivity(renderFollow(follower, followee, url)), + ); default: throw new Error(`resolveLocal: type ${type} unhandled`); } diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 17920254f..b0bdb0a8b 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -2,7 +2,7 @@ export type obj = { [x: string]: any }; export type ApObject = IObject | string | (IObject | string)[]; export interface IObject { - '@context': string | string[] | obj | obj[]; + "@context": string | string[] | obj | obj[]; type: string | string[]; id?: string; summary?: string; @@ -31,7 +31,7 @@ export interface IObject { export function getApIds(value: ApObject | undefined): string[] { if (value == null) return []; const array = Array.isArray(value) ? value : [value]; - return array.map(x => getApId(x)); + return array.map((x) => getApId(x)); } /** @@ -46,28 +46,33 @@ export function getOneApId(value: ApObject): string { * 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'); + 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'); + 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 { +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; +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; } @@ -88,24 +93,43 @@ export interface IActivity extends IObject { } export interface ICollection extends IObject { - type: 'Collection'; + type: "Collection"; totalItems: number; items: ApObject; } export interface IOrderedCollection extends IObject { - type: 'OrderedCollection'; + type: "OrderedCollection"; totalItems: number; orderedItems: ApObject; } -export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event']; +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'; + type: + | "Note" + | "Question" + | "Article" + | "Audio" + | "Document" + | "Image" + | "Page" + | "Video" + | "Event"; source?: { content: string; mediaType: string; @@ -117,7 +141,7 @@ export interface IPost extends IObject { } export interface IQuestion extends IObject { - type: 'Note' | 'Question'; + type: "Note" | "Question"; source?: { content: string; mediaType: string; @@ -131,7 +155,7 @@ export interface IQuestion extends IObject { } export const isQuestion = (object: IObject): object is IQuestion => - getApType(object) === 'Note' || getApType(object) === 'Question'; + getApType(object) === "Note" || getApType(object) === "Question"; interface IQuestionChoice { name?: string; @@ -139,21 +163,27 @@ interface IQuestionChoice { _misskey_votes?: number; } export interface ITombstone extends IObject { - type: 'Tombstone'; + type: "Tombstone"; formerType?: string; deleted?: Date; } export const isTombstone = (object: IObject): object is ITombstone => - getApType(object) === 'Tombstone'; + getApType(object) === "Tombstone"; -export const validActor = ['Person', 'Service', 'Group', 'Organization', 'Application']; +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'; + type: "Person" | "Service" | "Organization" | "Group" | "Application"; name?: string; preferredUsername?: string; manuallyApprovesFollowers?: boolean; @@ -161,7 +191,7 @@ export interface IActor extends IObject { alsoKnownAs?: string[]; discoverable?: boolean; inbox: string; - sharedInbox?: string; // backward compatibility.. ig + sharedInbox?: string; // backward compatibility.. ig publicKey?: { id: string; publicKeyPem: string; @@ -173,21 +203,24 @@ export interface IActor extends IObject { endpoints?: { sharedInbox?: string; }; - 'vcard:bday'?: string; - 'vcard:Address'?: string; + "vcard:bday"?: string; + "vcard:Address"?: string; } export const isCollection = (object: IObject): object is ICollection => - getApType(object) === 'Collection'; + getApType(object) === "Collection"; -export const isOrderedCollection = (object: IObject): object is IOrderedCollection => - getApType(object) === 'OrderedCollection'; +export const isOrderedCollection = ( + object: IObject, +): object is IOrderedCollection => getApType(object) === "OrderedCollection"; -export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => +export const isCollectionOrOrderedCollection = ( + object: IObject, +): object is ICollection | IOrderedCollection => isCollection(object) || isOrderedCollection(object); export interface IApPropertyValue extends IObject { - type: 'PropertyValue'; + type: "PropertyValue"; identifier: IApPropertyValue; name: string; value: string; @@ -195,110 +228,127 @@ export interface IApPropertyValue extends IObject { export const isPropertyValue = (object: IObject): object is IApPropertyValue => object && - getApType(object) === 'PropertyValue' && - typeof object.name === 'string' && - typeof (object as any).value === 'string'; + getApType(object) === "PropertyValue" && + typeof object.name === "string" && + typeof (object as any).value === "string"; export interface IApMention extends IObject { - type: 'Mention'; + type: "Mention"; href: string; } export const isMention = (object: IObject): object is IApMention => - getApType(object) === 'Mention' && - typeof object.href === 'string'; + getApType(object) === "Mention" && typeof object.href === "string"; export interface IApHashtag extends IObject { - type: 'Hashtag'; + type: "Hashtag"; name: string; } export const isHashtag = (object: IObject): object is IApHashtag => - getApType(object) === 'Hashtag' && - typeof object.name === 'string'; + getApType(object) === "Hashtag" && typeof object.name === "string"; export interface IApEmoji extends IObject { - type: 'Emoji'; + type: "Emoji"; updated: Date; } export const isEmoji = (object: IObject): object is IApEmoji => - getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; + getApType(object) === "Emoji" && + !Array.isArray(object.icon) && + object.icon.url != null; export interface ICreate extends IActivity { - type: 'Create'; + type: "Create"; } export interface IDelete extends IActivity { - type: 'Delete'; + type: "Delete"; } export interface IUpdate extends IActivity { - type: 'Update'; + type: "Update"; } export interface IRead extends IActivity { - type: 'Read'; + type: "Read"; } export interface IUndo extends IActivity { - type: 'Undo'; + type: "Undo"; } export interface IFollow extends IActivity { - type: 'Follow'; + type: "Follow"; } export interface IAccept extends IActivity { - type: 'Accept'; + type: "Accept"; } export interface IReject extends IActivity { - type: 'Reject'; + type: "Reject"; } export interface IAdd extends IActivity { - type: 'Add'; + type: "Add"; } export interface IRemove extends IActivity { - type: 'Remove'; + type: "Remove"; } export interface ILike extends IActivity { - type: 'Like' | 'EmojiReaction' | 'EmojiReact'; + type: "Like" | "EmojiReaction" | "EmojiReact"; _misskey_reaction?: string; } export interface IAnnounce extends IActivity { - type: 'Announce'; + type: "Announce"; } export interface IBlock extends IActivity { - type: 'Block'; + type: "Block"; } export interface IFlag extends IActivity { - type: 'Flag'; + type: "Flag"; } export interface IMove extends IActivity { - type: 'Move'; + type: "Move"; target: IObject | string; } -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'; -export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move'; +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"; +export const isMove = (object: IObject): object is IMove => + getApType(object) === "Move"; diff --git a/packages/backend/src/remote/logger.ts b/packages/backend/src/remote/logger.ts index 4921f53bd..b6bc5bf6d 100644 --- a/packages/backend/src/remote/logger.ts +++ b/packages/backend/src/remote/logger.ts @@ -1,3 +1,3 @@ -import Logger from '@/services/logger.js'; +import Logger from "@/services/logger.js"; -export const remoteLogger = new Logger('remote', 'cyan'); +export const remoteLogger = new Logger("remote", "cyan"); diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index fe6b65472..a6c1e399a 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -1,44 +1,54 @@ -import { URL } from 'node:url'; -import chalk from 'chalk'; -import { IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import type { User, IRemoteUser } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; -import webFinger from './webfinger.js'; -import { createPerson, updatePerson } from './activitypub/models/person.js'; -import { remoteLogger } from './logger.js'; +import { URL } from "node:url"; +import chalk from "chalk"; +import { IsNull } from "typeorm"; +import config from "@/config/index.js"; +import type { User, IRemoteUser } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; +import { toPuny } from "@/misc/convert-host.js"; +import webFinger from "./webfinger.js"; +import { createPerson, updatePerson } from "./activitypub/models/person.js"; +import { remoteLogger } from "./logger.js"; -const logger = remoteLogger.createSubLogger('resolve-user'); +const logger = remoteLogger.createSubLogger("resolve-user"); -export async function resolveUser(username: string, host: string | null): Promise { +export async function resolveUser( + username: string, + host: string | null, +): Promise { const usernameLower = username.toLowerCase(); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); + return await Users.findOneBy({ usernameLower, host: IsNull() }).then( + (u) => { + if (u == null) { + throw new Error("user not found"); + } else { + return u; + } + }, + ); } host = toPuny(host); if (config.host === host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOneBy({ usernameLower, host: IsNull() }).then(u => { - if (u == null) { - throw new Error('user not found'); - } else { - return u; - } - }); + return await Users.findOneBy({ usernameLower, host: IsNull() }).then( + (u) => { + if (u == null) { + throw new Error("user not found"); + } else { + return u; + } + }, + ); } - const user = await Users.findOneBy({ usernameLower, host }) as IRemoteUser | null; + const user = (await Users.findOneBy({ + usernameLower, + host, + })) as IRemoteUser | null; const acctLower = `${usernameLower}@${host}`; @@ -50,7 +60,10 @@ export async function resolveUser(username: string, host: string | null): Promis } // If user information is out of date, return it by starting over from WebFilger - if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + if ( + user.lastFetchedAt == null || + Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24 + ) { // Prevent multiple attempts to connect to unconnected instances, update before each attempt to prevent subsequent similar attempts await Users.update(user.id, { lastFetchedAt: new Date(), @@ -62,20 +75,25 @@ export async function resolveUser(username: string, host: string | null): Promis if (user.uri !== self.href) { // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. logger.info(`uri missmatch: ${acctLower}`); - logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + 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'); + throw new Error("Invalid uri"); } - await Users.update({ - usernameLower, - host: host, - }, { - uri: self.href, - }); + await Users.update( + { + usernameLower, + host: host, + }, + { + uri: self.href, + }, + ); } else { logger.info(`uri is fine: ${acctLower}`); } @@ -83,9 +101,9 @@ export async function resolveUser(username: string, host: string | null): Promis await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOneBy({ uri: self.href }).then(u => { + return await Users.findOneBy({ uri: self.href }).then((u) => { if (u == null) { - throw new Error('user not found'); + throw new Error("user not found"); } else { return u; } @@ -98,14 +116,24 @@ export async function resolveUser(username: string, host: string | null): Promis async function resolveSelf(acctLower: string) { logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); - const finger = await webFinger(acctLower).catch(e => { - logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); - throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); + const finger = await webFinger(acctLower).catch((e) => { + logger.error( + `Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ + e.statusCode || e.message + }`, + ); + throw new Error( + `Failed to WebFinger for ${acctLower}: ${e.statusCode || e.message}`, + ); }); - const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); + const self = finger.links.find( + (link) => link.rel != null && link.rel.toLowerCase() === "self", + ); if (!self) { - logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); - throw new Error('self link not found'); + 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/remote/webfinger.ts b/packages/backend/src/remote/webfinger.ts index 337df34c2..52226b042 100644 --- a/packages/backend/src/remote/webfinger.ts +++ b/packages/backend/src/remote/webfinger.ts @@ -1,6 +1,6 @@ -import { URL } from 'node:url'; -import { getJson } from '@/misc/fetch.js'; -import { query as urlQuery } from '@/prelude/url.js'; +import { URL } from "node:url"; +import { getJson } from "@/misc/fetch.js"; +import { query as urlQuery } from "@/prelude/url.js"; type ILink = { href: string; @@ -12,22 +12,29 @@ type IWebFinger = { subject: string; }; -export default async function(query: string): Promise { +export default async function (query: string): Promise { const url = genUrl(query); - return await getJson(url, 'application/jrd+json, application/json') as IWebFinger; + return (await getJson( + url, + "application/jrd+json, application/json", + )) as IWebFinger; } function genUrl(query: string) { if (query.match(/^https?:\/\//)) { const u = new URL(query); - return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: 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}` }); + return ( + `https://${hostname}/.well-known/webfinger?${urlQuery({ resource: `acct:${query}` })}` + ); } throw new Error(`Invalid query (${query})`); diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 703c11f92..29ac726ef 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -1,27 +1,27 @@ -import Router from '@koa/router'; -import json from 'koa-json-body'; -import httpSignature from '@peertube/http-signature'; +import Router from "@koa/router"; +import json from "koa-json-body"; +import httpSignature from "@peertube/http-signature"; -import { In, IsNull, Not } from 'typeorm'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderKey from '@/remote/activitypub/renderer/key.js'; -import { renderPerson } from '@/remote/activitypub/renderer/person.js'; -import renderEmoji from '@/remote/activitypub/renderer/emoji.js'; -import { inbox as processInbox } from '@/queue/index.js'; -import { isSelfHost, toPuny } from '@/misc/convert-host.js'; -import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; -import type { ILocalUser, User } from '@/models/entities/user.js'; -import { renderLike } from '@/remote/activitypub/renderer/like.js'; -import { getUserKeypair } from '@/misc/keypair-store.js'; -import { checkFetch, hasSignature } from '@/remote/activitypub/check-fetch.js'; -import { getInstanceActor } from '@/services/instance-actor.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import Featured from './activitypub/featured.js'; -import Following from './activitypub/following.js'; -import Followers from './activitypub/followers.js'; -import Outbox, { packActivity } from './activitypub/outbox.js'; +import { In, IsNull, Not } from "typeorm"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import renderKey from "@/remote/activitypub/renderer/key.js"; +import { renderPerson } from "@/remote/activitypub/renderer/person.js"; +import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; +import { inbox as processInbox } from "@/queue/index.js"; +import { isSelfHost, toPuny } from "@/misc/convert-host.js"; +import { Notes, Users, Emojis, NoteReactions } from "@/models/index.js"; +import type { ILocalUser, User } from "@/models/entities/user.js"; +import { renderLike } from "@/remote/activitypub/renderer/like.js"; +import { getUserKeypair } from "@/misc/keypair-store.js"; +import { checkFetch, hasSignature } from "@/remote/activitypub/check-fetch.js"; +import { getInstanceActor } from "@/services/instance-actor.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import Featured from "./activitypub/featured.js"; +import Following from "./activitypub/following.js"; +import Followers from "./activitypub/followers.js"; +import Outbox, { packActivity } from "./activitypub/outbox.js"; // Init router const router = new Router(); @@ -32,25 +32,25 @@ function inbox(ctx: Router.RouterContext) { let signature; try { - signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); + signature = httpSignature.parseRequest(ctx.req, { headers: [] }); } catch (e) { ctx.status = 401; return; } - // @ts-ignore processInbox(ctx.request.body, signature); ctx.status = 202; } -const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; -const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; +const ACTIVITY_JSON = "application/activity+json; charset=utf-8"; +const LD_JSON = + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; function isActivityPubReq(ctx: Router.RouterContext) { - ctx.response.vary('Accept'); - const accepted = ctx.accepts('html', ACTIVITY_JSON, LD_JSON); - return typeof accepted === 'string' && !accepted.match(/html/); + ctx.response.vary("Accept"); + const accepted = ctx.accepts("html", ACTIVITY_JSON, LD_JSON); + return typeof accepted === "string" && !accepted.match(/html/); } export function setResponseType(ctx: Router.RouterContext) { @@ -63,22 +63,22 @@ export function setResponseType(ctx: Router.RouterContext) { } // inbox -router.post('/inbox', json(), inbox); -router.post('/users/:user/inbox', json(), inbox); +router.post("/inbox", json(), inbox); +router.post("/users/:user/inbox", json(), inbox); // note -router.get('/notes/:note', async (ctx, next) => { +router.get("/notes/:note", async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } const note = await Notes.findOneBy({ id: ctx.params.note, - visibility: In(['public' as const, 'home' as const]), + visibility: In(["public" as const, "home" as const]), localOnly: false, }); @@ -88,7 +88,7 @@ router.get('/notes/:note', async (ctx, next) => { } // redirect if remote - if (note.userHost != null) { + if (note.userHost !== null) { if (note.uri == null || isSelfHost(note.userHost)) { ctx.status = 500; return; @@ -101,17 +101,17 @@ router.get('/notes/:note', async (ctx, next) => { const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }); // note activity -router.get('/notes/:note/activity', async ctx => { +router.get("/notes/:note/activity", async (ctx) => { const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -119,7 +119,7 @@ router.get('/notes/:note/activity', async ctx => { const note = await Notes.findOneBy({ id: ctx.params.note, userHost: IsNull(), - visibility: In(['public' as const, 'home' as const]), + visibility: In(["public" as const, "home" as const]), localOnly: false, }); @@ -131,37 +131,39 @@ router.get('/notes/:note/activity', async ctx => { ctx.body = renderActivity(await packActivity(note)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }); // outbox -router.get('/users/:user/outbox', Outbox); +router.get("/users/:user/outbox", Outbox); // followers -router.get('/users/:user/followers', Followers); +router.get("/users/:user/followers", Followers); // following -router.get('/users/:user/following', Following); +router.get("/users/:user/following", Following); // featured -router.get('/users/:user/collections/featured', Featured); +router.get("/users/:user/collections/featured", Featured); // publickey -router.get('/users/:user/publickey', async ctx => { +router.get("/users/:user/publickey", async (ctx) => { const instanceActor = await getInstanceActor(); if (ctx.params.user === instanceActor.id) { - ctx.body = renderActivity(renderKey(instanceActor, await getUserKeypair(instanceActor.id))); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.body = renderActivity( + renderKey(instanceActor, await getUserKeypair(instanceActor.id)), + ); + ctx.set("Cache-Control", "public, max-age=180"); setResponseType(ctx); return; } const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -184,9 +186,9 @@ router.get('/users/:user/publickey', async ctx => { ctx.body = renderActivity(renderKey(user, keypair)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); } else { @@ -204,14 +206,14 @@ async function userInfo(ctx: Router.RouterContext, user: User | null) { ctx.body = renderActivity(await renderPerson(user as ILocalUser)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); } -router.get('/users/:user', async (ctx, next) => { +router.get("/users/:user", async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); const instanceActor = await getInstanceActor(); @@ -221,7 +223,7 @@ router.get('/users/:user', async (ctx, next) => { } const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -237,17 +239,17 @@ router.get('/users/:user', async (ctx, next) => { await userInfo(ctx, user); }); -router.get('/@:user', async (ctx, next) => { +router.get("/@:user", async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - if (ctx.params.user === 'instance.actor') { + if (ctx.params.user === "instance.actor") { const instanceActor = await getInstanceActor(); await userInfo(ctx, instanceActor); return; } const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -261,16 +263,16 @@ router.get('/@:user', async (ctx, next) => { await userInfo(ctx, user); }); -router.get('/actor', async (ctx, next) => { +router.get("/actor", async (ctx, next) => { const instanceActor = await getInstanceActor(); await userInfo(ctx, instanceActor); }); //#endregion // emoji -router.get('/emojis/:emoji', async ctx => { +router.get("/emojis/:emoji", async (ctx) => { const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -288,17 +290,17 @@ router.get('/emojis/:emoji', async ctx => { ctx.body = renderActivity(await renderEmoji(emoji)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }); // like -router.get('/likes/:like', async ctx => { +router.get("/likes/:like", async (ctx) => { const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -320,17 +322,17 @@ router.get('/likes/:like', async ctx => { ctx.body = renderActivity(await renderLike(reaction, note)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }); // follow -router.get('/follows/:follower/:followee', async ctx => { +router.get("/follows/:follower/:followee", async (ctx) => { const verify = await checkFetch(ctx.req); - if (verify != 200) { + if (verify !== 200) { ctx.status = verify; return; } @@ -356,9 +358,9 @@ router.get('/follows/:follower/:followee', async ctx => { ctx.body = renderActivity(renderFollow(follower, followee)); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }); diff --git a/packages/backend/src/server/activitypub/featured.ts b/packages/backend/src/server/activitypub/featured.ts index bf2454fa5..82bb19fa1 100644 --- a/packages/backend/src/server/activitypub/featured.ts +++ b/packages/backend/src/server/activitypub/featured.ts @@ -1,13 +1,13 @@ -import { IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import { Users, Notes, UserNotePinings } from '@/models/index.js'; -import { checkFetch } from '@/remote/activitypub/check-fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { setResponseType } from '../activitypub.js'; -import type Router from '@koa/router'; +import { IsNull } from "typeorm"; +import config from "@/config/index.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import { Users, Notes, UserNotePinings } from "@/models/index.js"; +import { checkFetch } from "@/remote/activitypub/check-fetch.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { setResponseType } from "../activitypub.js"; +import type Router from "@koa/router"; export default async (ctx: Router.RouterContext) => { const verify = await checkFetch(ctx.req); @@ -30,26 +30,32 @@ export default async (ctx: Router.RouterContext) => { const pinings = await UserNotePinings.find({ where: { userId: user.id }, - order: { id: 'DESC' }, + order: { id: "DESC" }, }); - const pinnedNotes = await Promise.all(pinings.map(pining => - Notes.findOneByOrFail({ id: pining.noteId }))); + const pinnedNotes = await Promise.all( + pinings.map((pining) => Notes.findOneByOrFail({ id: pining.noteId })), + ); - const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); + const renderedNotes = await Promise.all( + pinnedNotes.map((note) => renderNote(note)), + ); const rendered = renderOrderedCollection( `${config.url}/users/${userId}/collections/featured`, - renderedNotes.length, undefined, undefined, renderedNotes, + renderedNotes.length, + undefined, + undefined, + renderedNotes, ); ctx.body = renderActivity(rendered); const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } setResponseType(ctx); }; diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 2a558764c..146ca5192 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -1,17 +1,17 @@ -import { IsNull, LessThan } from 'typeorm'; -import config from '@/config/index.js'; -import * as url from '@/prelude/url.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import type { Following } from '@/models/entities/following.js'; -import { checkFetch } from '@/remote/activitypub/check-fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { setResponseType } from '../activitypub.js'; -import type { FindOptionsWhere } from 'typeorm'; -import type Router from '@koa/router'; +import { IsNull, LessThan } from "typeorm"; +import config from "@/config/index.js"; +import * as url from "@/prelude/url.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; +import renderOrderedCollectionPage from "@/remote/activitypub/renderer/ordered-collection-page.js"; +import renderFollowUser from "@/remote/activitypub/renderer/follow-user.js"; +import { Users, Followings, UserProfiles } from "@/models/index.js"; +import type { Following } from "@/models/entities/following.js"; +import { checkFetch } from "@/remote/activitypub/check-fetch.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { setResponseType } from "../activitypub.js"; +import type { FindOptionsWhere } from "typeorm"; +import type Router from "@koa/router"; export default async (ctx: Router.RouterContext) => { const verify = await checkFetch(ctx.req); @@ -23,12 +23,12 @@ export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; - if (cursor != null && typeof cursor !== 'string') { + if (cursor != null && typeof cursor !== "string") { ctx.status = 400; return; } - const page = ctx.request.query.page === 'true'; + const page = ctx.request.query.page === "true"; const user = await Users.findOneBy({ id: userId, @@ -43,13 +43,13 @@ export default async (ctx: Router.RouterContext) => { //#region Check ff visibility const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - if (profile.ffVisibility === 'private') { + if (profile.ffVisibility === "private") { ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set("Cache-Control", "public, max-age=30"); return; - } else if (profile.ffVisibility === 'followers') { + } else if (profile.ffVisibility === "followers") { ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set("Cache-Control", "public, max-age=30"); return; } //#endregion @@ -78,32 +78,42 @@ export default async (ctx: Router.RouterContext) => { const inStock = followings.length === limit + 1; if (inStock) followings.pop(); - const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId))); + const renderedFollowers = await Promise.all( + followings.map((following) => renderFollowUser(following.followerId)), + ); const rendered = renderOrderedCollectionPage( `${partOf}?${url.query({ - page: 'true', + page: "true", cursor, })}`, - user.followersCount, renderedFollowers, partOf, + user.followersCount, + renderedFollowers, + partOf, undefined, - inStock ? `${partOf}?${url.query({ - page: 'true', - cursor: followings[followings.length - 1].id, - })}` : undefined, + inStock + ? `${partOf}?${url.query({ + page: "true", + cursor: followings[followings.length - 1].id, + })}` + : undefined, ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); + const rendered = renderOrderedCollection( + partOf, + user.followersCount, + `${partOf}?page=true`, + ); ctx.body = renderActivity(rendered); setResponseType(ctx); } const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } }; diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index a917e04ea..eab513ce6 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -1,17 +1,17 @@ -import { LessThan, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import * as url from '@/prelude/url.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; -import type { Following } from '@/models/entities/following.js'; -import { checkFetch } from '@/remote/activitypub/check-fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { setResponseType } from '../activitypub.js'; -import type { FindOptionsWhere } from 'typeorm'; -import type Router from '@koa/router'; +import { LessThan, IsNull } from "typeorm"; +import config from "@/config/index.js"; +import * as url from "@/prelude/url.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; +import renderOrderedCollectionPage from "@/remote/activitypub/renderer/ordered-collection-page.js"; +import renderFollowUser from "@/remote/activitypub/renderer/follow-user.js"; +import { Users, Followings, UserProfiles } from "@/models/index.js"; +import type { Following } from "@/models/entities/following.js"; +import { checkFetch } from "@/remote/activitypub/check-fetch.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { setResponseType } from "../activitypub.js"; +import type { FindOptionsWhere } from "typeorm"; +import type Router from "@koa/router"; export default async (ctx: Router.RouterContext) => { const verify = await checkFetch(ctx.req); @@ -23,12 +23,12 @@ export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; const cursor = ctx.request.query.cursor; - if (cursor != null && typeof cursor !== 'string') { + if (cursor != null && typeof cursor !== "string") { ctx.status = 400; return; } - const page = ctx.request.query.page === 'true'; + const page = ctx.request.query.page === "true"; const user = await Users.findOneBy({ id: userId, @@ -43,13 +43,13 @@ export default async (ctx: Router.RouterContext) => { //#region Check ff visibility const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - if (profile.ffVisibility === 'private') { + if (profile.ffVisibility === "private") { ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set("Cache-Control", "public, max-age=30"); return; - } else if (profile.ffVisibility === 'followers') { + } else if (profile.ffVisibility === "followers") { ctx.status = 403; - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set("Cache-Control", "public, max-age=30"); return; } //#endregion @@ -78,32 +78,42 @@ export default async (ctx: Router.RouterContext) => { const inStock = followings.length === limit + 1; if (inStock) followings.pop(); - const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId))); + const renderedFollowees = await Promise.all( + followings.map((following) => renderFollowUser(following.followeeId)), + ); const rendered = renderOrderedCollectionPage( `${partOf}?${url.query({ - page: 'true', + page: "true", cursor, })}`, - user.followingCount, renderedFollowees, partOf, + user.followingCount, + renderedFollowees, + partOf, undefined, - inStock ? `${partOf}?${url.query({ - page: 'true', - cursor: followings[followings.length - 1].id, - })}` : undefined, + inStock + ? `${partOf}?${url.query({ + page: "true", + cursor: followings[followings.length - 1].id, + })}` + : undefined, ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); + const rendered = renderOrderedCollection( + partOf, + user.followingCount, + `${partOf}?page=true`, + ); ctx.body = renderActivity(rendered); setResponseType(ctx); } const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } }; diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 3c010e3b6..e0a380ffb 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -1,20 +1,20 @@ -import { Brackets, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; -import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderCreate from '@/remote/activitypub/renderer/create.js'; -import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; -import { countIf } from '@/prelude/array.js'; -import * as url from '@/prelude/url.js'; -import { Users, Notes } from '@/models/index.js'; -import type { Note } from '@/models/entities/note.js'; -import { checkFetch } from '@/remote/activitypub/check-fetch.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { setResponseType } from '../activitypub.js'; -import type Router from '@koa/router'; +import { Brackets, IsNull } from "typeorm"; +import config from "@/config/index.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderOrderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; +import renderOrderedCollectionPage from "@/remote/activitypub/renderer/ordered-collection-page.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import renderCreate from "@/remote/activitypub/renderer/create.js"; +import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; +import { countIf } from "@/prelude/array.js"; +import * as url from "@/prelude/url.js"; +import { Users, Notes } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; +import { checkFetch } from "@/remote/activitypub/check-fetch.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { makePaginationQuery } from "../api/common/make-pagination-query.js"; +import { setResponseType } from "../activitypub.js"; +import type Router from "@koa/router"; export default async (ctx: Router.RouterContext) => { const verify = await checkFetch(ctx.req); @@ -26,20 +26,20 @@ export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; const sinceId = ctx.request.query.since_id; - if (sinceId != null && typeof sinceId !== 'string') { + if (sinceId != null && typeof sinceId !== "string") { ctx.status = 400; return; } const untilId = ctx.request.query.until_id; - if (untilId != null && typeof untilId !== 'string') { + if (untilId != null && typeof untilId !== "string") { ctx.status = 400; return; } - const page = ctx.request.query.page === 'true'; + const page = ctx.request.query.page === "true"; - if (countIf(x => x != null, [sinceId, untilId]) > 1) { + if (countIf((x) => x != null, [sinceId, untilId]) > 1) { ctx.status = 400; return; } @@ -58,41 +58,58 @@ export default async (ctx: Router.RouterContext) => { const partOf = `${config.url}/users/${userId}/outbox`; if (page) { - const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId) - .andWhere('note.userId = :userId', { userId: user.id }) - .andWhere(new Brackets(qb => { qb - .where('note.visibility = \'public\'') - .orWhere('note.visibility = \'home\''); - })) - .andWhere('note.localOnly = FALSE'); + const query = makePaginationQuery( + Notes.createQueryBuilder("note"), + sinceId, + untilId, + ) + .andWhere("note.userId = :userId", { userId: user.id }) + .andWhere( + new Brackets((qb) => { + qb.where("note.visibility = 'public'").orWhere( + "note.visibility = 'home'", + ); + }), + ) + .andWhere("note.localOnly = FALSE"); const notes = await query.take(limit).getMany(); if (sinceId) notes.reverse(); - const activities = await Promise.all(notes.map(note => packActivity(note))); + const activities = await Promise.all( + notes.map((note) => packActivity(note)), + ); const rendered = renderOrderedCollectionPage( `${partOf}?${url.query({ - page: 'true', + page: "true", since_id: sinceId, until_id: untilId, })}`, - user.notesCount, activities, partOf, - notes.length ? `${partOf}?${url.query({ - page: 'true', - since_id: notes[0].id, - })}` : undefined, - notes.length ? `${partOf}?${url.query({ - page: 'true', - until_id: notes[notes.length - 1].id, - })}` : undefined, + user.notesCount, + activities, + partOf, + notes.length + ? `${partOf}?${url.query({ + page: "true", + since_id: notes[0].id, + })}` + : undefined, + notes.length + ? `${partOf}?${url.query({ + page: "true", + until_id: notes[notes.length - 1].id, + })}` + : undefined, ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.notesCount, + const rendered = renderOrderedCollection( + partOf, + user.notesCount, `${partOf}?page=true`, `${partOf}?page=true&since_id=000000000000000000000000`, ); @@ -102,9 +119,9 @@ export default async (ctx: Router.RouterContext) => { } const meta = await fetchMeta(); if (meta.secureMode || meta.privateMode) { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } else { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set("Cache-Control", "public, max-age=180"); } }; @@ -113,9 +130,17 @@ export default async (ctx: Router.RouterContext) => { * @param note Note */ export async function packActivity(note: Note): Promise { - if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { + if ( + note.renoteId && + note.text == null && + !note.hasPoll && + (note.fileIds == null || note.fileIds.length === 0) + ) { const renote = await Notes.findOneByOrFail({ id: note.renoteId }); - return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); + return renderAnnounce( + renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, + note, + ); } return renderCreate(await renderNote(note, false), note); diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts index 96b9316e4..830fb809a 100644 --- a/packages/backend/src/server/api/2fa.ts +++ b/packages/backend/src/server/api/2fa.ts @@ -1,12 +1,12 @@ -import * as crypto from 'node:crypto'; -import * as jsrsasign from 'jsrsasign'; -import config from '@/config/index.js'; +import * as crypto from "node:crypto"; +import * as jsrsasign from "jsrsasign"; +import config from "@/config/index.js"; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); const PEM_PRELUDE = Buffer.from( - '3059301306072a8648ce3d020106082a8648ce3d030107034200', - 'hex', + "3059301306072a8648ce3d020106082a8648ce3d030107034200", + "hex", ); // Android Safetynet attestations are signed with this cert: @@ -34,7 +34,7 @@ TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE-----\n`; function base64URLDecode(source: string) { - return Buffer.from(source.replace(/\-/g, '+').replace(/_/g, '/'), 'base64'); + return Buffer.from(source.replace(/\-/g, "+").replace(/_/g, "/"), "base64"); } function getCertSubject(certificate: string) { @@ -42,11 +42,11 @@ function getCertSubject(certificate: string) { subjectCert.readCertPEM(certificate); const subjectString = subjectCert.getSubjectString(); - const subjectFields = subjectString.slice(1).split('/'); + const subjectFields = subjectString.slice(1).split("/"); const fields = {} as Record; for (const field of subjectFields) { - const eqIndex = field.indexOf('='); + const eqIndex = field.indexOf("="); fields[field.substring(0, eqIndex)] = field.substring(eqIndex + 1); } @@ -77,12 +77,12 @@ function verifyCertificateChain(certificates: string[]) { return valid; } -function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { +function PEMString(pemBuffer: Buffer, type = "CERTIFICATE") { if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) { pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91); - type = 'PUBLIC KEY'; + type = "PUBLIC KEY"; } - const cert = pemBuffer.toString('base64'); + const cert = pemBuffer.toString("base64"); const keyParts = []; const max = Math.ceil(cert.length / 64); @@ -93,17 +93,12 @@ function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') { } return ( - `-----BEGIN ${type}-----\n` + - keyParts.join('\n') + - `\n-----END ${type}-----\n` + `-----BEGIN ${type}-----\n${keyParts.join("\n")}\n-----END ${type}-----\n` ); } export function hash(data: Buffer) { - return crypto - .createHash('sha256') - .update(data) - .digest(); + return crypto.createHash("sha256").update(data).digest(); } export function verifyLogin({ @@ -114,22 +109,22 @@ export function verifyLogin({ signature, challenge, }: { - publicKey: Buffer, - authenticatorData: Buffer, - clientDataJSON: Buffer, - clientData: any, - signature: Buffer, - challenge: string + publicKey: Buffer; + authenticatorData: Buffer; + clientDataJSON: Buffer; + clientData: any; + signature: Buffer; + challenge: string; }) { - if (clientData.type !== 'webauthn.get') { - throw new Error('type is not webauthn.get'); + if (clientData.type !== "webauthn.get") { + throw new Error("type is not webauthn.get"); } - if (hash(clientData.challenge).toString('hex') !== challenge) { - throw new Error('challenge mismatch'); + if (hash(clientData.challenge).toString("hex") !== challenge) { + throw new Error("challenge mismatch"); } - if (clientData.origin !== config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); + if (clientData.origin !== `${config.scheme}://${config.host}`) { + throw new Error("origin mismatch"); } const verificationData = Buffer.concat( @@ -138,7 +133,7 @@ export function verifyLogin({ ); return crypto - .createVerify('SHA256') + .createVerify("SHA256") .update(verificationData) .verify(PEMString(publicKey), signature); } @@ -149,11 +144,11 @@ export const procedures = { const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); + throw new Error("invalid or no -2 key given"); } const negThree = publicKey.get(-3); if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); + throw new Error("invalid or no -3 key given"); } const publicKeyU2F = Buffer.concat( @@ -167,7 +162,7 @@ export const procedures = { }; }, }, - 'android-key': { + "android-key": { verify({ attStmt, authenticatorData, @@ -176,15 +171,15 @@ export const procedures = { rpIdHash, credentialId, }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, + attStmt: any; + authenticatorData: Buffer; + clientDataHash: Buffer; publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, + rpIdHash: Buffer; + credentialId: Buffer; }) { if (attStmt.alg !== -7) { - throw new Error('alg mismatch'); + throw new Error("alg mismatch"); } const verificationData = Buffer.concat([ @@ -197,11 +192,11 @@ export const procedures = { const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); + throw new Error("invalid or no -2 key given"); } const negThree = publicKey.get(-3); if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); + throw new Error("invalid or no -3 key given"); } const publicKeyData = Buffer.concat( @@ -210,11 +205,11 @@ export const procedures = { ); if (!attCert.equals(publicKeyData)) { - throw new Error('public key mismatch'); + throw new Error("public key mismatch"); } const isValid = crypto - .createVerify('SHA256') + .createVerify("SHA256") .update(verificationData) .verify(PEMString(attCert), attStmt.sig); @@ -227,7 +222,7 @@ export const procedures = { }, }, // what a stupid attestation - 'android-safetynet': { + "android-safetynet": { verify({ attStmt, authenticatorData, @@ -236,59 +231,59 @@ export const procedures = { rpIdHash, credentialId, }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, + attStmt: any; + authenticatorData: Buffer; + clientDataHash: Buffer; publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, + rpIdHash: Buffer; + credentialId: Buffer; }) { const verificationData = hash( Buffer.concat([authenticatorData, clientDataHash]), ); - const jwsParts = attStmt.response.toString('utf-8').split('.'); + const jwsParts = attStmt.response.toString("utf-8").split("."); - const header = JSON.parse(base64URLDecode(jwsParts[0]).toString('utf-8')); + const header = JSON.parse(base64URLDecode(jwsParts[0]).toString("utf-8")); const response = JSON.parse( - base64URLDecode(jwsParts[1]).toString('utf-8'), + base64URLDecode(jwsParts[1]).toString("utf-8"), ); const signature = jwsParts[2]; - if (!verificationData.equals(Buffer.from(response.nonce, 'base64'))) { - throw new Error('invalid nonce'); + if (!verificationData.equals(Buffer.from(response.nonce, "base64"))) { + throw new Error("invalid nonce"); } const certificateChain = header.x5c .map((key: any) => PEMString(key)) .concat([GSR2]); - if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') { - throw new Error('invalid common name'); + if (getCertSubject(certificateChain[0]).CN !== "attest.android.com") { + throw new Error("invalid common name"); } if (!verifyCertificateChain(certificateChain)) { - throw new Error('Invalid certificate chain!'); + throw new Error("Invalid certificate chain!"); } const signatureBase = Buffer.from( - jwsParts[0] + '.' + jwsParts[1], - 'utf-8', + `${jwsParts[0]}.${jwsParts[1]}`, + "utf-8", ); const valid = crypto - .createVerify('sha256') + .createVerify("sha256") .update(signatureBase) .verify(certificateChain[0], base64URLDecode(signature)); const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); + throw new Error("invalid or no -2 key given"); } const negThree = publicKey.get(-3); if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); + throw new Error("invalid or no -3 key given"); } const publicKeyData = Buffer.concat( @@ -310,12 +305,12 @@ export const procedures = { rpIdHash, credentialId, }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, + attStmt: any; + authenticatorData: Buffer; + clientDataHash: Buffer; publicKey: Map; - rpIdHash: Buffer, - credentialId: Buffer, + rpIdHash: Buffer; + credentialId: Buffer; }) { const verificationData = Buffer.concat([ authenticatorData, @@ -326,18 +321,18 @@ export const procedures = { const attCert = attStmt.x5c[0]; const validSignature = crypto - .createVerify('SHA256') + .createVerify("SHA256") .update(verificationData) .verify(PEMString(attCert), attStmt.sig); const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); + throw new Error("invalid or no -2 key given"); } const negThree = publicKey.get(-3); if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); + throw new Error("invalid or no -3 key given"); } const publicKeyData = Buffer.concat( @@ -351,16 +346,16 @@ export const procedures = { }; } else if (attStmt.ecdaaKeyId) { // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation - throw new Error('ECDAA-Verify is not supported'); + throw new Error("ECDAA-Verify is not supported"); } else { - if (attStmt.alg !== -7) throw new Error('alg mismatch'); + if (attStmt.alg !== -7) throw new Error("alg mismatch"); - throw new Error('self attestation is not supported'); + throw new Error("self attestation is not supported"); } }, }, - 'fido-u2f': { + "fido-u2f": { verify({ attStmt, authenticatorData, @@ -369,16 +364,16 @@ export const procedures = { rpIdHash, credentialId, }: { - attStmt: any, - authenticatorData: Buffer, - clientDataHash: Buffer, - publicKey: Map, - rpIdHash: Buffer, - credentialId: Buffer + attStmt: any; + authenticatorData: Buffer; + clientDataHash: Buffer; + publicKey: Map; + rpIdHash: Buffer; + credentialId: Buffer; }) { const x5c: Buffer[] = attStmt.x5c; if (x5c.length !== 1) { - throw new Error('x5c length does not match expectation'); + throw new Error("x5c length does not match expectation"); } const attCert = x5c[0]; @@ -388,11 +383,11 @@ export const procedures = { const negTwo: Buffer = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { - throw new Error('invalid or no -2 key given'); + throw new Error("invalid or no -2 key given"); } const negThree: Buffer = publicKey.get(-3); if (!negThree || negThree.length !== 32) { - throw new Error('invalid or no -3 key given'); + throw new Error("invalid or no -3 key given"); } const publicKeyU2F = Buffer.concat( @@ -409,7 +404,7 @@ export const procedures = { ]); const validSignature = crypto - .createVerify('SHA256') + .createVerify("SHA256") .update(verificationData) .verify(PEMString(attCert), attStmt.sig); diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index 3cb94f10f..99a12fd11 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -1,97 +1,123 @@ -import Koa from 'koa'; +import type Koa from "koa"; -import { User } from '@/models/entities/user.js'; -import { UserIps } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { IEndpoint } from './endpoints.js'; -import authenticate, { AuthenticationError } from './authenticate.js'; -import call from './call.js'; -import { ApiError } from './error.js'; +import type { User } from "@/models/entities/user.js"; +import { UserIps } from "@/models/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import type { IEndpoint } from "./endpoints.js"; +import authenticate, { AuthenticationError } from "./authenticate.js"; +import call from "./call.js"; +import { ApiError } from "./error.js"; -const userIpHistories = new Map>(); +const userIpHistories = new Map>(); setInterval(() => { userIpHistories.clear(); }, 1000 * 60 * 60); -export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => { - const body = ctx.is('multipart/form-data') - ? (ctx.request as any).body - : ctx.method === 'GET' +export default (endpoint: IEndpoint, ctx: Koa.Context) => + new Promise((res) => { + const body = ctx.is("multipart/form-data") + ? (ctx.request as any).body + : ctx.method === "GET" ? ctx.query : ctx.request.body; - const reply = (x?: any, y?: ApiError) => { - if (x == null) { - ctx.status = 204; - } else if (typeof x === 'number' && y) { - ctx.status = x; - ctx.body = { - error: { - message: y!.message, - code: y!.code, - id: y!.id, - kind: y!.kind, - ...(y!.info ? { info: y!.info } : {}), - }, - }; - } else { - // 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない - ctx.body = typeof x === 'string' ? JSON.stringify(x) : x; - } - res(); - }; - - // Authentication - // for GET requests, do not even pass on the body parameter as it is considered unsafe - authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(([user, app]) => { - // API invoking - call(endpoint.name, user, app, body, ctx).then((res: any) => { - if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { - ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); + 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; } - reply(res); - }).catch((e: ApiError) => { - reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); - }); + res(); + }; - // Log IP - if (user) { - fetchMeta().then(meta => { - if (!meta.enableIpLogging) return; - const ip = ctx.ip; - const ips = userIpHistories.get(user.id); - if (ips == null || !ips.has(ip)) { - if (ips == null) { - userIpHistories.set(user.id, new Set([ip])); - } else { - ips.add(ip); - } + // Authentication + // for GET requests, do not even pass on the body parameter as it is considered unsafe + authenticate( + ctx.headers.authorization, + ctx.method === "GET" ? null : body["i"], + ) + .then(([user, app]) => { + // API invoking + call(endpoint.name, user, app, body, ctx) + .then((res: any) => { + if ( + ctx.method === "GET" && + endpoint.meta.cacheSec && + !body["i"] && + !user + ) { + ctx.set( + "Cache-Control", + `public, max-age=${endpoint.meta.cacheSec}`, + ); + } + reply(res); + }) + .catch((e: ApiError) => { + reply( + e.httpStatusCode + ? e.httpStatusCode + : e.kind === "client" + ? 400 + : 500, + e, + ); + }); - try { - UserIps.createQueryBuilder().insert().values({ - createdAt: new Date(), - userId: user.id, - ip: ip, - }).orIgnore(true).execute(); - } catch { - } + // Log IP + if (user) { + fetchMeta().then((meta) => { + if (!meta.enableIpLogging) return; + const ip = ctx.ip; + const ips = userIpHistories.get(user.id); + if (ips == null || !ips.has(ip)) { + if (ips == null) { + userIpHistories.set(user.id, new Set([ip])); + } else { + ips.add(ip); + } + + try { + UserIps.createQueryBuilder() + .insert() + .values({ + createdAt: new Date(), + userId: user.id, + ip: ip, + }) + .orIgnore(true) + .execute(); + } catch {} + } + }); + } + }) + .catch((e) => { + if (e instanceof AuthenticationError) { + ctx.response.status = 403; + ctx.response.set("WWW-Authenticate", "Bearer"); + ctx.response.body = { + message: `Authentication failed: ${e.message}`, + code: "AUTHENTICATION_FAILED", + id: "b0a7f5f8-dc2f-4171-b91f-de88ad238e14", + kind: "client", + }; + res(); + } else { + reply(500, new ApiError()); } }); - } - }).catch(e => { - if (e instanceof AuthenticationError) { - ctx.response.status = 403; - ctx.response.set('WWW-Authenticate', 'Bearer'); - ctx.response.body = { - message: 'Authentication failed: ' + e.message, - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - kind: 'client', - }; - res(); - } else { - reply(500, new ApiError()); - } }); -}); diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 39be06c29..42274ad2a 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -1,35 +1,43 @@ -import isNativeToken from './common/is-native-token.js'; -import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; -import { Users, AccessTokens, Apps } from '@/models/index.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { Cache } from '@/misc/cache.js'; -import { App } from '@/models/entities/app.js'; -import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; +import isNativeToken from "./common/is-native-token.js"; +import type { CacheableLocalUser, ILocalUser } from "@/models/entities/user.js"; +import { Users, AccessTokens, Apps } from "@/models/index.js"; +import type { AccessToken } from "@/models/entities/access-token.js"; +import { Cache } from "@/misc/cache.js"; +import type { App } from "@/models/entities/app.js"; +import { + localUserByIdCache, + localUserByNativeTokenCache, +} from "@/services/user-cache.js"; const appCache = new Cache(Infinity); export class AuthenticationError extends Error { constructor(message: string) { super(message); - this.name = 'AuthenticationError'; + this.name = "AuthenticationError"; } } -export default async (authorization: string | null | undefined, bodyToken: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { +export default async ( + authorization: string | null | undefined, + bodyToken: string | null, +): Promise< + [CacheableLocalUser | null | undefined, AccessToken | null | undefined] +> => { let token: string | null = null; // check if there is an authorization header set if (authorization != null) { if (bodyToken != null) { - throw new AuthenticationError('using multiple authorization schemes'); + throw new AuthenticationError("using multiple authorization schemes"); } // check if OAuth 2.0 Bearer tokens are being used // Authorization schemes are case insensitive - if (authorization.substring(0, 7).toLowerCase() === 'bearer ') { + if (authorization.substring(0, 7).toLowerCase() === "bearer ") { token = authorization.substring(7); } else { - throw new AuthenticationError('unsupported authentication scheme'); + throw new AuthenticationError("unsupported authentication scheme"); } } else if (bodyToken != null) { token = bodyToken; @@ -38,44 +46,56 @@ export default async (authorization: string | null | undefined, bodyToken: strin } if (isNativeToken(token)) { - const user = await localUserByNativeTokenCache.fetch(token, - () => Users.findOneBy({ token }) as Promise); + const user = await localUserByNativeTokenCache.fetch( + token, + () => Users.findOneBy({ token }) as Promise, + ); if (user == null) { - throw new AuthenticationError('unknown token'); + throw new AuthenticationError("unknown token"); } return [user, null]; } else { const accessToken = await AccessTokens.findOne({ - where: [{ - hash: token.toLowerCase(), // app - }, { - token: token, // miauth - }], + where: [ + { + hash: token.toLowerCase(), // app + }, + { + token: token, // miauth + }, + ], }); if (accessToken == null) { - throw new AuthenticationError('unknown token'); + throw new AuthenticationError("unknown token"); } AccessTokens.update(accessToken.id, { lastUsedAt: new Date(), }); - const user = await localUserByIdCache.fetch(accessToken.userId, - () => Users.findOneBy({ - id: accessToken.userId, - }) as Promise); + const user = await localUserByIdCache.fetch( + accessToken.userId, + () => + Users.findOneBy({ + id: accessToken.userId, + }) as Promise, + ); if (accessToken.appId) { - const app = await appCache.fetch(accessToken.appId, - () => Apps.findOneByOrFail({ id: accessToken.appId! })); + const app = await appCache.fetch(accessToken.appId, () => + Apps.findOneByOrFail({ id: accessToken.appId! }), + ); - return [user, { - id: accessToken.id, - permission: app.permission, - } as AccessToken]; + return [ + user, + { + id: accessToken.id, + permission: app.permission, + } as AccessToken, + ]; } else { return [user, accessToken]; } diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index c20443ca6..45471ed56 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,34 +1,43 @@ -import { performance } from 'perf_hooks'; -import Koa from 'koa'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { getIpHash } from '@/misc/get-ip-hash.js'; -import { limiter } from './limiter.js'; -import endpoints, { IEndpointMeta } from './endpoints.js'; -import compatibility from './compatibility.js'; -import { ApiError } from './error.js'; -import { apiLogger } from './logger.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import { performance } from "perf_hooks"; +import type Koa from "koa"; +import type { CacheableLocalUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import type { AccessToken } from "@/models/entities/access-token.js"; +import { getIpHash } from "@/misc/get-ip-hash.js"; +import { limiter } from "./limiter.js"; +import type { IEndpointMeta } from "./endpoints.js"; +import endpoints from "./endpoints.js"; +import compatibility from "./compatibility.js"; +import { ApiError } from "./error.js"; +import { apiLogger } from "./logger.js"; +import type { AccessToken } from "@/models/entities/access-token.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; const accessDenied = { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "56f35758-7dd5-468b-8439-5d6fb8ec9b8e", }; -export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { +export default async ( + endpoint: string, + user: CacheableLocalUser | null | undefined, + token: AccessToken | null | undefined, + data: any, + ctx?: Koa.Context, +) => { const isSecure = user != null && token == null; const isModerator = user != null && (user.isModerator || user.isAdmin); - const ep = endpoints.find(e => e.name === endpoint) || - compatibility.find(e => e.name === endpoint); + const ep = + endpoints.find((e) => e.name === endpoint) || + compatibility.find((e) => e.name === endpoint); if (ep == null) { throw new ApiError({ - message: 'No such endpoint.', - code: 'NO_SUCH_ENDPOINT', - id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709', + message: "No such endpoint.", + code: "NO_SUCH_ENDPOINT", + id: "f8080b67-5f9c-4eb7-8c18-7f1eeae8f709", httpStatusCode: 404, }); } @@ -53,11 +62,14 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi } // Rate limit - await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable }, limitActor).catch(e => { + await limiter( + limit as IEndpointMeta["limit"] & { key: NonNullable }, + limitActor, + ).catch((e) => { throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + message: "Rate limit exceeded. Please try again later.", + code: "RATE_LIMIT_EXCEEDED", + id: "d5826d14-3982-4d2e-8011-b9e9f02499ef", httpStatusCode: 429, }); }); @@ -65,65 +77,80 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi if (ep.meta.requireCredential && user == null) { throw new ApiError({ - message: 'Credential required.', - code: 'CREDENTIAL_REQUIRED', - id: '1384574d-a912-4b81-8601-c7b1c4085df1', + message: "Credential required.", + code: "CREDENTIAL_REQUIRED", + id: "1384574d-a912-4b81-8601-c7b1c4085df1", httpStatusCode: 401, }); } if (ep.meta.requireCredential && user!.isSuspended) { throw new ApiError({ - message: 'Your account has been suspended.', - code: 'YOUR_ACCOUNT_SUSPENDED', - id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', + message: "Your account has been suspended.", + code: "YOUR_ACCOUNT_SUSPENDED", + id: "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370", httpStatusCode: 403, }); } if (ep.meta.requireAdmin && !user!.isAdmin) { - throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); + throw new ApiError(accessDenied, { reason: "You are not the admin." }); } if (ep.meta.requireModerator && !isModerator) { - throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); + throw new ApiError(accessDenied, { reason: "You are not a moderator." }); } - if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) { + if ( + token && + ep.meta.kind && + !token.permission.some((p) => p === ep.meta.kind) + ) { throw new ApiError({ - message: 'Your app does not have the necessary permissions to use this endpoint.', - code: 'PERMISSION_DENIED', - id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', + message: + "Your app does not have the necessary permissions to use this endpoint.", + code: "PERMISSION_DENIED", + id: "1370e5b7-d4eb-4566-bb1d-7748ee6a1838", }); } // private mode const meta = await fetchMeta(); - if (meta.privateMode && ep.meta.requireCredentialPrivateMode && user == null) { + if ( + meta.privateMode && + ep.meta.requireCredentialPrivateMode && + user == null + ) { throw new ApiError({ - message: 'Credential required.', - code: 'CREDENTIAL_REQUIRED', - id: '1384574d-a912-4b81-8601-c7b1c4085df1', - httpStatusCode: 401 + message: "Credential required.", + code: "CREDENTIAL_REQUIRED", + id: "1384574d-a912-4b81-8601-c7b1c4085df1", + httpStatusCode: 401, }); } // Cast non JSON input - if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) { + if ((ep.meta.requireFile || ctx?.method === "GET") && ep.params.properties) { for (const k of Object.keys(ep.params.properties)) { const param = ep.params.properties![k]; - if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') { + if ( + ["boolean", "number", "integer"].includes(param.type ?? "") && + typeof data[k] === "string" + ) { try { data[k] = JSON.parse(data[k]); } catch (e) { - throw new ApiError({ - message: 'Invalid param.', - code: 'INVALID_PARAM', - id: '0b5f1631-7c1a-41a6-b399-cce335f34d85', - }, { - param: k, - reason: `cannot cast to ${param.type}`, - }); + throw new ApiError( + { + message: "Invalid param.", + code: "INVALID_PARAM", + id: "0b5f1631-7c1a-41a6-b399-cce335f34d85", + }, + { + param: k, + reason: `cannot cast to ${param.type}`, + }, + ); } } } @@ -131,32 +158,35 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi // API invoking const before = performance.now(); - return await ep.exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers).catch((e: Error) => { - if (e instanceof ApiError) { - throw e; - } else { - apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, { - ep: ep.name, - ps: data, - e: { - message: e.message, - code: e.name, - stack: e.stack, - }, - }); - throw new ApiError(null, { - e: { - message: e.message, - code: e.name, - stack: e.stack, - }, - }); - } - }).finally(() => { - const after = performance.now(); - const time = after - before; - if (time > 1000) { - apiLogger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); - } - }); + return await ep + .exec(data, user, token, ctx?.file, ctx?.ip, ctx?.headers) + .catch((e: Error) => { + if (e instanceof ApiError) { + throw e; + } else { + apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, { + ep: ep.name, + ps: data, + e: { + message: e.message, + code: e.name, + stack: e.stack, + }, + }); + throw new ApiError(null, { + e: { + message: e.message, + code: e.name, + stack: e.stack, + }, + }); + } + }) + .finally(() => { + const after = performance.now(); + const time = after - before; + if (time > 1000) { + apiLogger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); + } + }); }; diff --git a/packages/backend/src/server/api/common/generate-block-query.ts b/packages/backend/src/server/api/common/generate-block-query.ts index 60db1e731..a37b607eb 100644 --- a/packages/backend/src/server/api/common/generate-block-query.ts +++ b/packages/backend/src/server/api/common/generate-block-query.ts @@ -1,42 +1,54 @@ -import { User } from '@/models/entities/user.js'; -import { Blockings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import { Blockings } from "@/models/index.js"; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; // ここでいうBlockedは被Blockedの意 -export function generateBlockedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const blockingQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockerId') - .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); +export function generateBlockedUserQuery( + q: SelectQueryBuilder, + me: { id: User["id"] }, +) { + const blockingQuery = Blockings.createQueryBuilder("blocking") + .select("blocking.blockerId") + .where("blocking.blockeeId = :blockeeId", { blockeeId: me.id }); // 投稿の作者にブロックされていない かつ // 投稿の返信先の作者にブロックされていない かつ // 投稿の引用元の作者にブロックされていない - q - .andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`) - .andWhere(new Brackets(qb => { qb - .where(`note.replyUserId IS NULL`) - .orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`); - })) - .andWhere(new Brackets(qb => { qb - .where(`note.renoteUserId IS NULL`) - .orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`); - })); + q.andWhere(`note.userId NOT IN (${blockingQuery.getQuery()})`) + .andWhere( + new Brackets((qb) => { + qb.where("note.replyUserId IS NULL").orWhere( + `note.replyUserId NOT IN (${blockingQuery.getQuery()})`, + ); + }), + ) + .andWhere( + new Brackets((qb) => { + qb.where("note.renoteUserId IS NULL").orWhere( + `note.renoteUserId NOT IN (${blockingQuery.getQuery()})`, + ); + }), + ); q.setParameters(blockingQuery.getParameters()); } -export function generateBlockQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }) { - const blockingQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockeeId') - .where('blocking.blockerId = :blockerId', { blockerId: me.id }); +export function generateBlockQueryForUsers( + q: SelectQueryBuilder, + me: { id: User["id"] }, +) { + const blockingQuery = Blockings.createQueryBuilder("blocking") + .select("blocking.blockeeId") + .where("blocking.blockerId = :blockerId", { blockerId: me.id }); - const blockedQuery = Blockings.createQueryBuilder('blocking') - .select('blocking.blockerId') - .where('blocking.blockeeId = :blockeeId', { blockeeId: me.id }); + const blockedQuery = Blockings.createQueryBuilder("blocking") + .select("blocking.blockerId") + .where("blocking.blockeeId = :blockeeId", { blockeeId: me.id }); - q.andWhere(`user.id NOT IN (${ blockingQuery.getQuery() })`); + q.andWhere(`user.id NOT IN (${blockingQuery.getQuery()})`); q.setParameters(blockingQuery.getParameters()); - q.andWhere(`user.id NOT IN (${ blockedQuery.getQuery() })`); + q.andWhere(`user.id NOT IN (${blockedQuery.getQuery()})`); q.setParameters(blockedQuery.getParameters()); } diff --git a/packages/backend/src/server/api/common/generate-channel-query.ts b/packages/backend/src/server/api/common/generate-channel-query.ts index 333bb73b8..318062266 100644 --- a/packages/backend/src/server/api/common/generate-channel-query.ts +++ b/packages/backend/src/server/api/common/generate-channel-query.ts @@ -1,23 +1,34 @@ -import { User } from '@/models/entities/user.js'; -import { ChannelFollowings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import { ChannelFollowings } from "@/models/index.js"; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; -export function generateChannelQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null) { +export function generateChannelQuery( + q: SelectQueryBuilder, + me?: { id: User["id"] } | null, +) { if (me == null) { - q.andWhere('note.channelId IS NULL'); + q.andWhere("note.channelId IS NULL"); } else { - q.leftJoinAndSelect('note.channel', 'channel'); + q.leftJoinAndSelect("note.channel", "channel"); - const channelFollowingQuery = ChannelFollowings.createQueryBuilder('channelFollowing') - .select('channelFollowing.followeeId') - .where('channelFollowing.followerId = :followerId', { followerId: me.id }); + const channelFollowingQuery = ChannelFollowings.createQueryBuilder( + "channelFollowing", + ) + .select("channelFollowing.followeeId") + .where("channelFollowing.followerId = :followerId", { + followerId: me.id, + }); - q.andWhere(new Brackets(qb => { qb - // チャンネルのノートではない - .where('note.channelId IS NULL') - // または自分がフォローしているチャンネルのノート - .orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`); - })); + q.andWhere( + new Brackets((qb) => { + qb + // チャンネルのノートではない + .where("note.channelId IS NULL") + // または自分がフォローしているチャンネルのノート + .orWhere(`note.channelId IN (${channelFollowingQuery.getQuery()})`); + }), + ); q.setParameters(channelFollowingQuery.getParameters()); } diff --git a/packages/backend/src/server/api/common/generate-muted-note-query.ts b/packages/backend/src/server/api/common/generate-muted-note-query.ts index f544e334d..86da2ea88 100644 --- a/packages/backend/src/server/api/common/generate-muted-note-query.ts +++ b/packages/backend/src/server/api/common/generate-muted-note-query.ts @@ -1,13 +1,16 @@ -import { User } from '@/models/entities/user.js'; -import { MutedNotes } from '@/models/index.js'; -import { SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import { MutedNotes } from "@/models/index.js"; +import type { SelectQueryBuilder } from "typeorm"; -export function generateMutedNoteQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const mutedQuery = MutedNotes.createQueryBuilder('muted') - .select('muted.noteId') - .where('muted.userId = :userId', { userId: me.id }); +export function generateMutedNoteQuery( + q: SelectQueryBuilder, + me: { id: User["id"] }, +) { + const mutedQuery = MutedNotes.createQueryBuilder("muted") + .select("muted.noteId") + .where("muted.userId = :userId", { userId: me.id }); - q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); + q.andWhere(`note.id NOT IN (${mutedQuery.getQuery()})`); q.setParameters(mutedQuery.getParameters()); } diff --git a/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts b/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts index 7263ea2e6..61f44f4a7 100644 --- a/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts +++ b/packages/backend/src/server/api/common/generate-muted-note-thread-query.ts @@ -1,17 +1,24 @@ -import { User } from '@/models/entities/user.js'; -import { NoteThreadMutings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import { NoteThreadMutings } from "@/models/index.js"; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; -export function generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { - const mutedQuery = NoteThreadMutings.createQueryBuilder('threadMuted') - .select('threadMuted.threadId') - .where('threadMuted.userId = :userId', { userId: me.id }); +export function generateMutedNoteThreadQuery( + q: SelectQueryBuilder, + me: { id: User["id"] }, +) { + const mutedQuery = NoteThreadMutings.createQueryBuilder("threadMuted") + .select("threadMuted.threadId") + .where("threadMuted.userId = :userId", { userId: me.id }); - q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); - q.andWhere(new Brackets(qb => { qb - .where(`note.threadId IS NULL`) - .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`); - })); + q.andWhere(`note.id NOT IN (${mutedQuery.getQuery()})`); + q.andWhere( + new Brackets((qb) => { + qb.where("note.threadId IS NULL").orWhere( + `note.threadId NOT IN (${mutedQuery.getQuery()})`, + ); + }), + ); q.setParameters(mutedQuery.getParameters()); } diff --git a/packages/backend/src/server/api/common/generate-muted-user-query.ts b/packages/backend/src/server/api/common/generate-muted-user-query.ts index 470ece1a6..3538fbf0a 100644 --- a/packages/backend/src/server/api/common/generate-muted-user-query.ts +++ b/packages/backend/src/server/api/common/generate-muted-user-query.ts @@ -1,57 +1,81 @@ -import { SelectQueryBuilder, Brackets } from 'typeorm'; -import { User } from '@/models/entities/user.js'; -import { Mutings, UserProfiles } from '@/models/index.js'; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; +import type { User } from "@/models/entities/user.js"; +import { Mutings, UserProfiles } from "@/models/index.js"; -export function generateMutedUserQuery(q: SelectQueryBuilder, me: { id: User['id'] }, exclude?: User) { - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: me.id }); +export function generateMutedUserQuery( + q: SelectQueryBuilder, + me: { id: User["id"] }, + exclude?: User, +) { + const mutingQuery = Mutings.createQueryBuilder("muting") + .select("muting.muteeId") + .where("muting.muterId = :muterId", { muterId: me.id }); if (exclude) { - mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id }); + mutingQuery.andWhere("muting.muteeId != :excludeId", { + excludeId: exclude.id, + }); } - const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile') - .select('user_profile.mutedInstances') - .where('user_profile.userId = :muterId', { muterId: me.id }); + const mutingInstanceQuery = UserProfiles.createQueryBuilder("user_profile") + .select("user_profile.mutedInstances") + .where("user_profile.userId = :muterId", { muterId: me.id }); // 投稿の作者をミュートしていない かつ // 投稿の返信先の作者をミュートしていない かつ // 投稿の引用元の作者をミュートしていない - q - .andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`) - .andWhere(new Brackets(qb => { qb - .where('note.replyUserId IS NULL') - .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`); - })) - .andWhere(new Brackets(qb => { qb - .where('note.renoteUserId IS NULL') - .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); - })) + q.andWhere(`note.userId NOT IN (${mutingQuery.getQuery()})`) + .andWhere( + new Brackets((qb) => { + qb.where("note.replyUserId IS NULL").orWhere( + `note.replyUserId NOT IN (${mutingQuery.getQuery()})`, + ); + }), + ) + .andWhere( + new Brackets((qb) => { + qb.where("note.renoteUserId IS NULL").orWhere( + `note.renoteUserId NOT IN (${mutingQuery.getQuery()})`, + ); + }), + ) // mute instances - .andWhere(new Brackets(qb => { qb - .andWhere('note.userHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`); - })) - .andWhere(new Brackets(qb => { qb - .where('note.replyUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`); - })) - .andWhere(new Brackets(qb => { qb - .where('note.renoteUserHost IS NULL') - .orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`); - })); + .andWhere( + new Brackets((qb) => { + qb.andWhere("note.userHost IS NULL").orWhere( + `NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.userHost)`, + ); + }), + ) + .andWhere( + new Brackets((qb) => { + qb.where("note.replyUserHost IS NULL").orWhere( + `NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.replyUserHost)`, + ); + }), + ) + .andWhere( + new Brackets((qb) => { + qb.where("note.renoteUserHost IS NULL").orWhere( + `NOT ((${mutingInstanceQuery.getQuery()})::jsonb ? note.renoteUserHost)`, + ); + }), + ); q.setParameters(mutingQuery.getParameters()); q.setParameters(mutingInstanceQuery.getParameters()); } -export function generateMutedUserQueryForUsers(q: SelectQueryBuilder, me: { id: User['id'] }) { - const mutingQuery = Mutings.createQueryBuilder('muting') - .select('muting.muteeId') - .where('muting.muterId = :muterId', { muterId: me.id }); +export function generateMutedUserQueryForUsers( + q: SelectQueryBuilder, + me: { id: User["id"] }, +) { + const mutingQuery = Mutings.createQueryBuilder("muting") + .select("muting.muteeId") + .where("muting.muterId = :muterId", { muterId: me.id }); - q.andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`); + q.andWhere(`user.id NOT IN (${mutingQuery.getQuery()})`); q.setParameters(mutingQuery.getParameters()); } diff --git a/packages/backend/src/server/api/common/generate-native-user-token.ts b/packages/backend/src/server/api/common/generate-native-user-token.ts index 5d8a4c537..5a8b41b70 100644 --- a/packages/backend/src/server/api/common/generate-native-user-token.ts +++ b/packages/backend/src/server/api/common/generate-native-user-token.ts @@ -1,3 +1,3 @@ -import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { secureRndstr } from "@/misc/secure-rndstr.js"; export default () => secureRndstr(16, true); diff --git a/packages/backend/src/server/api/common/generate-replies-query.ts b/packages/backend/src/server/api/common/generate-replies-query.ts index 301782eab..140c1d74a 100644 --- a/packages/backend/src/server/api/common/generate-replies-query.ts +++ b/packages/backend/src/server/api/common/generate-replies-query.ts @@ -1,27 +1,47 @@ -import { User } from '@/models/entities/user.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; -export function generateRepliesQuery(q: SelectQueryBuilder, me?: Pick | null) { +export function generateRepliesQuery( + q: SelectQueryBuilder, + me?: Pick | null, +) { if (me == null) { - q.andWhere(new Brackets(qb => { qb - .where(`note.replyId IS NULL`) // 返信ではない - .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.replyUserId = note.userId'); - })); - })); + q.andWhere( + new Brackets((qb) => { + qb.where("note.replyId IS NULL") // 返信ではない + .orWhere( + new Brackets((qb) => { + qb.where( + // 返信だけど投稿者自身への返信 + "note.replyId IS NOT NULL", + ).andWhere("note.replyUserId = note.userId"); + }), + ); + }), + ); } else if (!me.showTimelineReplies) { - q.andWhere(new Brackets(qb => { qb - .where(`note.replyId IS NULL`) // 返信ではない - .orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信 - .orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.userId = :meId', { meId: me.id }); - })) - .orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信 - .where(`note.replyId IS NOT NULL`) - .andWhere('note.replyUserId = note.userId'); - })); - })); + q.andWhere( + new Brackets((qb) => { + qb.where("note.replyId IS NULL") // 返信ではない + .orWhere("note.replyUserId = :meId", { meId: me.id }) // 返信だけど自分のノートへの返信 + .orWhere( + new Brackets((qb) => { + qb.where( + // 返信だけど自分の行った返信 + "note.replyId IS NOT NULL", + ).andWhere("note.userId = :meId", { meId: me.id }); + }), + ) + .orWhere( + new Brackets((qb) => { + qb.where( + // 返信だけど投稿者自身への返信 + "note.replyId IS NOT NULL", + ).andWhere("note.replyUserId = note.userId"); + }), + ); + }), + ); } } diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts index b50b6812f..c434df082 100644 --- a/packages/backend/src/server/api/common/generate-visibility-query.ts +++ b/packages/backend/src/server/api/common/generate-visibility-query.ts @@ -1,41 +1,60 @@ -import { User } from '@/models/entities/user.js'; -import { Followings } from '@/models/index.js'; -import { Brackets, SelectQueryBuilder } from 'typeorm'; +import type { User } from "@/models/entities/user.js"; +import { Followings } from "@/models/index.js"; +import type { SelectQueryBuilder } from "typeorm"; +import { Brackets } from "typeorm"; -export function generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null) { +export function generateVisibilityQuery( + q: SelectQueryBuilder, + me?: { id: User["id"] } | null, +) { // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { - q.andWhere(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); - })); + q.andWhere( + new Brackets((qb) => { + qb.where(`note.visibility = 'public'`).orWhere( + `note.visibility = 'home'`, + ); + }), + ); } else { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :meId'); + const followingQuery = Followings.createQueryBuilder("following") + .select("following.followeeId") + .where("following.followerId = :meId"); - q.andWhere(new Brackets(qb => { qb - // 公開投稿である - .where(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); - })) - // または 自分自身 - .orWhere('note.userId = :meId') - // または 自分宛て - .orWhere(':meId = ANY(note.visibleUserIds)') - .orWhere(':meId = ANY(note.mentions)') - .orWhere(new Brackets(qb => { qb - // または フォロワー宛ての投稿であり、 - .where(`note.visibility = 'followers'`) - .andWhere(new Brackets(qb => { qb - // 自分がフォロワーである - .where(`note.userId IN (${ followingQuery.getQuery() })`) - // または 自分の投稿へのリプライ - .orWhere('note.replyUserId = :meId'); - })); - })); - })); + q.andWhere( + new Brackets((qb) => { + qb + // 公開投稿である + .where( + new Brackets((qb) => { + qb.where(`note.visibility = 'public'`).orWhere( + `note.visibility = 'home'`, + ); + }), + ) + // または 自分自身 + .orWhere("note.userId = :meId") + // または 自分宛て + .orWhere(":meId = ANY(note.visibleUserIds)") + .orWhere(":meId = ANY(note.mentions)") + .orWhere( + new Brackets((qb) => { + qb + // または フォロワー宛ての投稿であり、 + .where(`note.visibility = 'followers'`) + .andWhere( + new Brackets((qb) => { + qb + // 自分がフォロワーである + .where(`note.userId IN (${followingQuery.getQuery()})`) + // または 自分の投稿へのリプライ + .orWhere("note.replyUserId = :meId"); + }), + ); + }), + ); + }), + ); q.setParameters({ meId: me.id }); } diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index c5a1e765e..fd7580775 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -1,24 +1,29 @@ -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { Notes, Users } from '@/models/index.js'; -import { generateVisibilityQuery } from './generate-visibility-query.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 { Notes, Users } from "@/models/index.js"; +import { generateVisibilityQuery } from "./generate-visibility-query.js"; /** * Get note for API processing, taking into account visibility. */ -export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) { - const query = Notes.createQueryBuilder('note') - .where("note.id = :id", { - id: noteId, - }); +export async function getNote( + noteId: Note["id"], + me: { id: User["id"] } | null, +) { + const query = Notes.createQueryBuilder("note").where("note.id = :id", { + id: noteId, + }); generateVisibilityQuery(query, me); const note = await query.getOne(); if (note == null) { - throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + throw new IdentifiableError( + "9725d0ce-ba28-4dde-95a7-2cbb2c15de24", + "No such note.", + ); } return note; @@ -27,11 +32,14 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) /** * Get user for API processing */ -export async function getUser(userId: User['id']) { +export async function getUser(userId: User["id"]) { const user = await Users.findOneBy({ id: userId }); if (user == null) { - throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); + throw new IdentifiableError( + "15348ddd-432d-49c2-8a5a-8069753becff", + "No such user.", + ); } return user; @@ -40,11 +48,11 @@ export async function getUser(userId: User['id']) { /** * Get remote user for API processing */ -export async function getRemoteUser(userId: User['id']) { +export async function getRemoteUser(userId: User["id"]) { const user = await getUser(userId); if (!Users.isRemoteUser(user)) { - throw new Error('user is not a remote user'); + throw new Error("user is not a remote user"); } return user; @@ -53,11 +61,11 @@ export async function getRemoteUser(userId: User['id']) { /** * Get local user for API processing */ -export async function getLocalUser(userId: User['id']) { +export async function getLocalUser(userId: User["id"]) { const user = await getUser(userId); if (!Users.isLocalUser(user)) { - throw new Error('user is not a local user'); + throw new Error("user is not a local user"); } return user; diff --git a/packages/backend/src/server/api/common/inject-featured.ts b/packages/backend/src/server/api/common/inject-featured.ts index f7cdd365e..30ba3eca9 100644 --- a/packages/backend/src/server/api/common/inject-featured.ts +++ b/packages/backend/src/server/api/common/inject-featured.ts @@ -1,9 +1,9 @@ -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 rndstr from "rndstr"; +import type { Note } from "@/models/entities/note.js"; +import type { 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"; // TODO: リアクション、Renote、返信などをしたノートは除外する @@ -18,38 +18,35 @@ export async function injectFeatured(timeline: Note[], user?: User | null) { const max = 30; const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - const query = Notes.createQueryBuilder('note') - .addSelect('note.score') - .where('note.userHost IS NULL') - .andWhere(`note.score > 0`) - .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) + 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'); + .innerJoinAndSelect("note.user", "user"); if (user) { - query.andWhere('note.userId != :userId', { userId: user.id }); + query.andWhere("note.userId != :userId", { userId: user.id }); generateMutedUserQuery(query, user); generateBlockedUserQuery(query, user); - const reactionQuery = NoteReactions.createQueryBuilder('reaction') - .select('reaction.noteId') - .where('reaction.userId = :userId', { userId: user.id }); + const reactionQuery = NoteReactions.createQueryBuilder("reaction") + .select("reaction.noteId") + .where("reaction.userId = :userId", { userId: user.id }); - query.andWhere(`note.id NOT IN (${ reactionQuery.getQuery() })`); + query.andWhere(`note.id NOT IN (${reactionQuery.getQuery()})`); } - const notes = await query - .orderBy('note.score', 'DESC') - .take(max) - .getMany(); + const notes = await query.orderBy("note.score", "DESC").take(max).getMany(); if (notes.length === 0) return; // Pick random one const featured = notes[Math.floor(Math.random() * notes.length)]; - (featured as any)._featuredId_ = rndstr('a-z0-9', 8); + (featured as any)._featuredId_ = rndstr("a-z0-9", 8); // Inject featured timeline.splice(3, 0, featured); diff --git a/packages/backend/src/server/api/common/inject-promo.ts b/packages/backend/src/server/api/common/inject-promo.ts index b0da8118b..dcc4e5f3f 100644 --- a/packages/backend/src/server/api/common/inject-promo.ts +++ b/packages/backend/src/server/api/common/inject-promo.ts @@ -1,21 +1,23 @@ -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 rndstr from "rndstr"; +import type { Note } from "@/models/entities/note.js"; +import type { User } from "@/models/entities/user.js"; +import { PromoReads, PromoNotes, Notes, Users } from "@/models/index.js"; export async function injectPromo(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; // TODO: readやexpireフィルタはクエリ側でやる - const reads = user ? await PromoReads.findBy({ - userId: user.id, - }) : []; + const reads = user + ? await PromoReads.findBy({ + userId: user.id, + }) + : []; let promos = await PromoNotes.find(); - promos = promos.filter(n => n.expiresAt.getTime() > Date.now()); - promos = promos.filter(n => !reads.map(r => r.noteId).includes(n.noteId)); + promos = promos.filter((n) => n.expiresAt.getTime() > Date.now()); + promos = promos.filter((n) => !reads.map((r) => r.noteId).includes(n.noteId)); if (promos.length === 0) return; @@ -27,7 +29,7 @@ export async function injectPromo(timeline: Note[], user?: User | null) { // Join note.user = await Users.findOneByOrFail({ id: note.userId }); - (note as any)._prId_ = rndstr('a-z0-9', 8); + (note as any)._prId_ = rndstr("a-z0-9", 8); // Inject promo timeline.splice(3, 0, note); diff --git a/packages/backend/src/server/api/common/make-pagination-query.ts b/packages/backend/src/server/api/common/make-pagination-query.ts index 51c11e5df..a2c327569 100644 --- a/packages/backend/src/server/api/common/make-pagination-query.ts +++ b/packages/backend/src/server/api/common/make-pagination-query.ts @@ -1,28 +1,42 @@ -import { SelectQueryBuilder } from 'typeorm'; +import type { SelectQueryBuilder } from "typeorm"; -export function makePaginationQuery(q: SelectQueryBuilder, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number) { +export function makePaginationQuery( + q: SelectQueryBuilder, + sinceId?: string, + untilId?: string, + sinceDate?: number, + untilDate?: number, +) { if (sinceId && untilId) { q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.orderBy(`${q.alias}.id`, "DESC"); } else if (sinceId) { q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); - q.orderBy(`${q.alias}.id`, 'ASC'); + q.orderBy(`${q.alias}.id`, "ASC"); } else if (untilId) { q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.orderBy(`${q.alias}.id`, "DESC"); } else if (sinceDate && untilDate) { - q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); - q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); - q.orderBy(`${q.alias}.createdAt`, 'DESC'); + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { + sinceDate: new Date(sinceDate), + }); + q.andWhere(`${q.alias}.createdAt < :untilDate`, { + untilDate: new Date(untilDate), + }); + q.orderBy(`${q.alias}.createdAt`, "DESC"); } else if (sinceDate) { - q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); - q.orderBy(`${q.alias}.createdAt`, 'ASC'); + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { + sinceDate: new Date(sinceDate), + }); + q.orderBy(`${q.alias}.createdAt`, "ASC"); } else if (untilDate) { - q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); - q.orderBy(`${q.alias}.createdAt`, 'DESC'); + q.andWhere(`${q.alias}.createdAt < :untilDate`, { + untilDate: new Date(untilDate), + }); + q.orderBy(`${q.alias}.createdAt`, "DESC"); } else { - q.orderBy(`${q.alias}.id`, 'DESC'); + q.orderBy(`${q.alias}.id`, "DESC"); } return q; } diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index c4c18ffa0..fc22c843a 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -1,26 +1,29 @@ -import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; -import { publishMessagingStream } from '@/services/stream.js'; -import { publishMessagingIndexStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js'; -import { In } from 'typeorm'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { toArray } from '@/prelude/array.js'; -import { renderReadActivity } from '@/remote/activitypub/renderer/read.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { deliver } from '@/queue/index.js'; -import orderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; +import { + publishMainStream, + publishGroupMessagingStream, +} from "@/services/stream.js"; +import { publishMessagingStream } from "@/services/stream.js"; +import { publishMessagingIndexStream } from "@/services/stream.js"; +import { pushNotification } from "@/services/push-notification.js"; +import type { User, IRemoteUser } from "@/models/entities/user.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { MessagingMessages, UserGroupJoinings, Users } from "@/models/index.js"; +import { In } from "typeorm"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import { toArray } from "@/prelude/array.js"; +import { renderReadActivity } from "@/remote/activitypub/renderer/read.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { deliver } from "@/queue/index.js"; +import orderedCollection from "@/remote/activitypub/renderer/ordered-collection.js"; /** * Mark messages as read */ export async function readUserMessagingMessage( - userId: User['id'], - otherpartyId: User['id'], - messageIds: MessagingMessage['id'][] + userId: User["id"], + otherpartyId: User["id"], + messageIds: MessagingMessage["id"][], ) { if (messageIds.length === 0) return; @@ -30,28 +33,34 @@ export async function readUserMessagingMessage( for (const message of messages) { if (message.recipientId !== userId) { - throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).'); + throw new IdentifiableError( + "e140a4bf-49ce-4fb6-b67c-b78dadf6b52f", + "Access denied (user).", + ); } } // Update documents - await MessagingMessages.update({ - id: In(messageIds), - userId: otherpartyId, - recipientId: userId, - isRead: false, - }, { - isRead: true, - }); + await MessagingMessages.update( + { + id: In(messageIds), + userId: otherpartyId, + recipientId: userId, + isRead: false, + }, + { + isRead: true, + }, + ); // Publish event - publishMessagingStream(otherpartyId, userId, 'read', messageIds); - publishMessagingIndexStream(userId, 'read', messageIds); + publishMessagingStream(otherpartyId, userId, "read", messageIds); + publishMessagingIndexStream(userId, "read", messageIds); - if (!await Users.getHasUnreadMessagingMessage(userId)) { + if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - publishMainStream(userId, 'readAllMessagingMessages'); - pushNotification(userId, 'readAllMessagingMessages', undefined); + publishMainStream(userId, "readAllMessagingMessages"); + pushNotification(userId, "readAllMessagingMessages", undefined); } else { // そのユーザーとのメッセージで未読がなければイベント発行 const count = await MessagingMessages.count({ @@ -60,11 +69,13 @@ export async function readUserMessagingMessage( recipientId: userId, isRead: false, }, - take: 1 + take: 1, }); if (!count) { - pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId }); + pushNotification(userId, "readAllMessagingMessagesOfARoom", { + userId: otherpartyId, + }); } } } @@ -73,9 +84,9 @@ export async function readUserMessagingMessage( * Mark messages as read */ export async function readGroupMessagingMessage( - userId: User['id'], - groupId: UserGroup['id'], - messageIds: MessagingMessage['id'][] + userId: User["id"], + groupId: UserGroup["id"], + messageIds: MessagingMessage["id"][], ) { if (messageIds.length === 0) return; @@ -86,62 +97,79 @@ export async function readGroupMessagingMessage( }); if (joining == null) { - throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).'); + throw new IdentifiableError( + "930a270c-714a-46b2-b776-ad27276dc569", + "Access denied (group).", + ); } const messages = await MessagingMessages.findBy({ id: In(messageIds), }); - const reads: MessagingMessage['id'][] = []; + const reads: MessagingMessage["id"][] = []; for (const message of messages) { if (message.userId === userId) continue; if (message.reads.includes(userId)) continue; // Update document - await MessagingMessages.createQueryBuilder().update() + await MessagingMessages.createQueryBuilder() + .update() .set({ reads: (() => `array_append("reads", '${joining.userId}')`) as any, }) - .where('id = :id', { id: message.id }) + .where("id = :id", { id: message.id }) .execute(); reads.push(message.id); } // Publish event - publishGroupMessagingStream(groupId, 'read', { + publishGroupMessagingStream(groupId, "read", { ids: reads, userId: userId, }); - publishMessagingIndexStream(userId, 'read', reads); + publishMessagingIndexStream(userId, "read", reads); - if (!await Users.getHasUnreadMessagingMessage(userId)) { + if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - publishMainStream(userId, 'readAllMessagingMessages'); - pushNotification(userId, 'readAllMessagingMessages', undefined); + publishMainStream(userId, "readAllMessagingMessages"); + pushNotification(userId, "readAllMessagingMessages", undefined); } else { // そのグループにおいて未読がなければイベント発行 - const unreadExist = await MessagingMessages.createQueryBuilder('message') - .where(`message.groupId = :groupId`, { groupId: groupId }) - .andWhere('message.userId != :userId', { userId: userId }) - .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) - .andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない - .getOne().then(x => x != null); + const unreadExist = await MessagingMessages.createQueryBuilder("message") + .where("message.groupId = :groupId", { groupId: groupId }) + .andWhere("message.userId != :userId", { userId: userId }) + .andWhere("NOT (:userId = ANY(message.reads))", { userId: userId }) + .andWhere("message.createdAt > :joinedAt", { + joinedAt: joining.createdAt, + }) // 自分が加入する前の会話については、未読扱いしない + .getOne() + .then((x) => x != null); if (!unreadExist) { - pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId }); + pushNotification(userId, "readAllMessagingMessagesOfARoom", { groupId }); } } } -export async function deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) { - messages = toArray(messages).filter(x => x.uri); - const contents = messages.map(x => renderReadActivity(user, x)); +export async function deliverReadActivity( + user: { id: User["id"]; host: null }, + recipient: IRemoteUser, + messages: MessagingMessage | MessagingMessage[], +) { + messages = toArray(messages).filter((x) => x.uri); + const contents = messages.map((x) => renderReadActivity(user, x)); if (contents.length > 1) { - const collection = orderedCollection(null, contents.length, undefined, undefined, contents); + const collection = orderedCollection( + null, + contents.length, + undefined, + undefined, + contents, + ); deliver(user, renderActivity(collection), recipient.inbox); } else { for (const content of contents) { diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts index b0d38a9e3..1fb1d642f 100644 --- a/packages/backend/src/server/api/common/read-notification.ts +++ b/packages/backend/src/server/api/common/read-notification.ts @@ -1,50 +1,59 @@ -import { In } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { User } from '@/models/entities/user.js'; -import { Notification } from '@/models/entities/notification.js'; -import { Notifications, Users } from '@/models/index.js'; +import { In } from "typeorm"; +import { publishMainStream } from "@/services/stream.js"; +import { pushNotification } from "@/services/push-notification.js"; +import type { User } from "@/models/entities/user.js"; +import type { Notification } from "@/models/entities/notification.js"; +import { Notifications, Users } from "@/models/index.js"; export async function readNotification( - userId: User['id'], - notificationIds: Notification['id'][], + userId: User["id"], + notificationIds: Notification["id"][], ) { if (notificationIds.length === 0) return; // Update documents - const result = await Notifications.update({ - notifieeId: userId, - id: In(notificationIds), - isRead: false, - }, { - isRead: true, - }); + const result = await Notifications.update( + { + notifieeId: userId, + id: In(notificationIds), + isRead: false, + }, + { + isRead: true, + }, + ); if (result.affected === 0) return; - if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId); + if (!(await Users.getHasUnreadNotification(userId))) + return postReadAllNotifications(userId); else return postReadNotifications(userId, notificationIds); } export async function readNotificationByQuery( - userId: User['id'], + userId: User["id"], query: Record, ) { const notificationIds = await Notifications.findBy({ ...query, notifieeId: userId, isRead: false, - }).then(notifications => notifications.map(notification => notification.id)); + }).then((notifications) => + notifications.map((notification) => notification.id), + ); return readNotification(userId, notificationIds); } -function postReadAllNotifications(userId: User['id']) { - publishMainStream(userId, 'readAllNotifications'); - return pushNotification(userId, 'readAllNotifications', undefined); +function postReadAllNotifications(userId: User["id"]) { + publishMainStream(userId, "readAllNotifications"); + return pushNotification(userId, "readAllNotifications", undefined); } -function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { - publishMainStream(userId, 'readNotifications', notificationIds); - return pushNotification(userId, 'readNotifications', { notificationIds }); +function postReadNotifications( + userId: User["id"], + notificationIds: Notification["id"][], +) { + publishMainStream(userId, "readNotifications", notificationIds); + return pushNotification(userId, "readNotifications", { notificationIds }); } diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index 038fd8d96..a8a435843 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -1,19 +1,19 @@ -import Koa from 'koa'; +import type Koa from "koa"; -import config from '@/config/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { Signins } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { publishMainStream } from '@/services/stream.js'; +import config from "@/config/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { Signins } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { publishMainStream } from "@/services/stream.js"; -export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { +export default function (ctx: Koa.Context, user: ILocalUser, redirect = false) { if (redirect) { //#region Cookie - ctx.cookies.set('igi', user.token!, { - path: '/', + ctx.cookies.set("igi", user.token!, { + path: "/", // SEE: https://github.com/koajs/koa/issues/974 // When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header - secure: config.url.startsWith('https'), + secure: config.url.startsWith("https"), httpOnly: false, }); //#endregion @@ -36,9 +36,9 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { ip: ctx.ip, headers: ctx.headers, success: true, - }).then(x => Signins.findOneByOrFail(x.identifiers[0])); + }).then((x) => Signins.findOneByOrFail(x.identifiers[0])); // Publish signin event - publishMainStream(user.id, 'signin', await Signins.pack(record)); + publishMainStream(user.id, "signin", await Signins.pack(record)); })(); } diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts index a1993cacd..7ae9e10fb 100644 --- a/packages/backend/src/server/api/common/signup.ts +++ b/packages/backend/src/server/api/common/signup.ts @@ -1,22 +1,22 @@ -import bcrypt from 'bcryptjs'; -import { generateKeyPair } from 'node:crypto'; -import generateUserToken from './generate-native-user-token.js'; -import { User } from '@/models/entities/user.js'; -import { Users, UsedUsernames } from '@/models/index.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { IsNull } from 'typeorm'; -import { genId } from '@/misc/gen-id.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import { UserKeypair } from '@/models/entities/user-keypair.js'; -import { usersChart } from '@/services/chart/index.js'; -import { UsedUsername } from '@/models/entities/used-username.js'; -import { db } from '@/db/postgre.js'; -import config from '@/config/index.js'; +import bcrypt from "bcryptjs"; +import { generateKeyPair } from "node:crypto"; +import generateUserToken from "./generate-native-user-token.js"; +import { User } from "@/models/entities/user.js"; +import { Users, UsedUsernames } from "@/models/index.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { IsNull } from "typeorm"; +import { genId } from "@/misc/gen-id.js"; +import { toPunyNullable } from "@/misc/convert-host.js"; +import { UserKeypair } from "@/models/entities/user-keypair.js"; +import { usersChart } from "@/services/chart/index.js"; +import { UsedUsername } from "@/models/entities/used-username.js"; +import { db } from "@/db/postgre.js"; +import config from "@/config/index.js"; export async function signup(opts: { - username: User['username']; + username: User["username"]; password?: string | null; - passwordHash?: UserProfile['password'] | null; + passwordHash?: UserProfile["password"] | null; host?: string | null; }) { const { username, password, passwordHash, host } = opts; @@ -27,18 +27,18 @@ export async function signup(opts: { }); if (config.maxUserSignups != null && userCount > config.maxUserSignups) { - throw new Error('MAX_USERS_REACHED'); + throw new Error("MAX_USERS_REACHED"); } // Validate username if (!Users.validateLocalUsername(username)) { - throw new Error('INVALID_USERNAME'); + throw new Error("INVALID_USERNAME"); } if (password != null && passwordHash == null) { // Validate password if (!Users.validatePassword(password)) { - throw new Error('INVALID_PASSWORD'); + throw new Error("INVALID_PASSWORD"); } // Generate hash of password @@ -50,71 +50,89 @@ export async function signup(opts: { const secret = generateUserToken(); // Check username duplication - if (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { - throw new Error('DUPLICATED_USERNAME'); + if ( + await Users.findOneBy({ + usernameLower: username.toLowerCase(), + host: IsNull(), + }) + ) { + throw new Error("DUPLICATED_USERNAME"); } // Check deleted username duplication if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) { - throw new Error('USED_USERNAME'); + throw new Error("USED_USERNAME"); } const keyPair = await new Promise((res, rej) => - generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - cipher: undefined, - passphrase: undefined, - }, - } as any, (err, publicKey, privateKey) => - err ? rej(err) : res([publicKey, privateKey]) - )); + generateKeyPair( + "rsa", + { + modulusLength: 4096, + publicKeyEncoding: { + type: "spki", + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + cipher: undefined, + passphrase: undefined, + }, + } as any, + (err, publicKey, privateKey) => + err ? rej(err) : res([publicKey, privateKey]), + ), + ); let account!: User; // Start transaction - await db.transaction(async transactionalEntityManager => { + await db.transaction(async (transactionalEntityManager) => { const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), host: IsNull(), }); - if (exist) throw new Error(' the username is already used'); + if (exist) throw new Error(" the username is already used"); - account = await transactionalEntityManager.save(new User({ - id: genId(), - createdAt: new Date(), - username: username, - usernameLower: username.toLowerCase(), - host: toPunyNullable(host), - token: secret, - isAdmin: (await Users.countBy({ - host: IsNull(), - })) === 0, - })); + account = await transactionalEntityManager.save( + new User({ + id: genId(), + createdAt: new Date(), + username: username, + usernameLower: username.toLowerCase(), + host: toPunyNullable(host), + token: secret, + isAdmin: + (await Users.countBy({ + host: IsNull(), + })) === 0, + }), + ); - await transactionalEntityManager.save(new UserKeypair({ - publicKey: keyPair[0], - privateKey: keyPair[1], - userId: account.id, - })); + await transactionalEntityManager.save( + new UserKeypair({ + publicKey: keyPair[0], + privateKey: keyPair[1], + userId: account.id, + }), + ); - await transactionalEntityManager.save(new UserProfile({ - userId: account.id, - autoAcceptFollowed: true, - password: hash, - })); + await transactionalEntityManager.save( + new UserProfile({ + userId: account.id, + autoAcceptFollowed: true, + password: hash, + }), + ); - await transactionalEntityManager.save(new UsedUsername({ - createdAt: new Date(), - username: username.toLowerCase(), - })); + await transactionalEntityManager.save( + new UsedUsername({ + createdAt: new Date(), + username: username.toLowerCase(), + }), + ); }); usersChart.update(account, true); diff --git a/packages/backend/src/server/api/compatibility.ts b/packages/backend/src/server/api/compatibility.ts index 2f22cf718..7e44fa8b2 100644 --- a/packages/backend/src/server/api/compatibility.ts +++ b/packages/backend/src/server/api/compatibility.ts @@ -1,11 +1,11 @@ -import { IEndpoint } from './endpoints'; +import type { IEndpoint } from "./endpoints"; -import * as cp___instanceInfo from './endpoints/compatibility/instance-info.js'; -import * as cp___customEmojis from './endpoints/compatibility/custom-emojis.js'; +import * as cp___instanceInfo from "./endpoints/compatibility/instance-info.js"; +import * as cp___customEmojis from "./endpoints/compatibility/custom-emojis.js"; const cps = [ - ['v1/instance', cp___instanceInfo], - ['v1/custom_emojis', cp___customEmojis], + ["v1/instance", cp___instanceInfo], + ["v1/custom_emojis", cp___customEmojis], ]; const compatibility: IEndpoint[] = cps.map(([name, cp]) => { diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index c1b56b8a8..ee0844185 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -1,29 +1,61 @@ -import * as fs from 'node:fs'; -import Ajv from 'ajv'; -import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js'; -import { Schema, SchemaType } from '@/misc/schema.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { IEndpointMeta } from './endpoints.js'; -import { ApiError } from './error.js'; +import * as fs from "node:fs"; +import Ajv from "ajv"; +import type { CacheableLocalUser } from "@/models/entities/user.js"; +import { ILocalUser } from "@/models/entities/user.js"; +import type { Schema, SchemaType } from "@/misc/schema.js"; +import type { AccessToken } from "@/models/entities/access-token.js"; +import type { IEndpointMeta } from "./endpoints.js"; +import { ApiError } from "./error.js"; export type Response = Record | void; // TODO: paramsの型をT['params']のスキーマ定義から推論する -type executor = - (params: SchemaType, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any, ip?: string | null, headers?: Record | null) => - Promise>>; +type executor = ( + params: SchemaType, + user: T["requireCredential"] extends true + ? CacheableLocalUser + : CacheableLocalUser | null, + token: AccessToken | null, + file?: any, + cleanup?: () => any, + ip?: string | null, + headers?: Record | null, +) => Promise< + T["res"] extends undefined ? Response : SchemaType> +>; const ajv = new Ajv({ useDefaults: true, }); -ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); +ajv.addFormat("misskey:id", /^[a-zA-Z0-9]+$/); -export default function (meta: T, paramDef: Ps, cb: executor) - : (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => Promise { +export default function ( + meta: T, + paramDef: Ps, + cb: executor, +): ( + params: any, + user: T["requireCredential"] extends true + ? CacheableLocalUser + : CacheableLocalUser | null, + token: AccessToken | null, + file?: any, + ip?: string | null, + headers?: Record | null, +) => Promise { const validate = ajv.compile(paramDef); - return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, ip?: string | null, headers?: Record | null) => { + return ( + params: any, + user: T["requireCredential"] extends true + ? CacheableLocalUser + : CacheableLocalUser | null, + token: AccessToken | null, + file?: any, + ip?: string | null, + headers?: Record | null, + ) => { let cleanup: undefined | (() => void) = undefined; if (meta.requireFile) { @@ -31,11 +63,14 @@ export default function (meta: T, pa fs.unlink(file.path, () => {}); }; - if (file == null) return Promise.reject(new ApiError({ - message: 'File required.', - code: 'FILE_REQUIRED', - id: '4267801e-70d1-416a-b011-4ee502885d8b', - })); + if (file == null) + return Promise.reject( + new ApiError({ + message: "File required.", + code: "FILE_REQUIRED", + id: "4267801e-70d1-416a-b011-4ee502885d8b", + }), + ); } const valid = validate(params); @@ -43,17 +78,28 @@ export default function (meta: T, pa if (file) cleanup!(); const errors = validate.errors!; - const err = new ApiError({ - message: 'Invalid param.', - code: 'INVALID_PARAM', - id: '3d81ceae-475f-4600-b2a8-2bc116157532', - }, { - param: errors[0].schemaPath, - reason: errors[0].message, - }); + const err = new ApiError( + { + message: "Invalid param.", + code: "INVALID_PARAM", + id: "3d81ceae-475f-4600-b2a8-2bc116157532", + }, + { + param: errors[0].schemaPath, + reason: errors[0].message, + }, + ); return Promise.reject(err); } - return cb(params as SchemaType, user, token, file, cleanup, ip, headers); + return cb( + params as SchemaType, + user, + token, + file, + cleanup, + ip, + headers, + ); }; } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index cb2564037..b6d9c3e1f 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -1,669 +1,675 @@ -import { Schema } from '@/misc/schema.js'; +import type { Schema } from "@/misc/schema.js"; -import * as ep___admin_meta from './endpoints/admin/meta.js'; -import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; -import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; -import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js'; -import * as ep___admin_accounts_hosted from './endpoints/admin/accounts/hosted.js'; -import * as ep___admin_ad_create from './endpoints/admin/ad/create.js'; -import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js'; -import * as ep___admin_ad_list from './endpoints/admin/ad/list.js'; -import * as ep___admin_ad_update from './endpoints/admin/ad/update.js'; -import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js'; -import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; -import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; -import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; -import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; -import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; -import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; -import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; -import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js'; -import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js'; -import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js'; -import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js'; -import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js'; -import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js'; -import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js'; -import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js'; -import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; -import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; -import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; -import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; -import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; -import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; -import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; -import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js'; -import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js'; -import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; -import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; -import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___admin_invite from './endpoints/admin/invite.js'; -import * as ep___admin_moderators_add from './endpoints/admin/moderators/add.js'; -import * as ep___admin_moderators_remove from './endpoints/admin/moderators/remove.js'; -import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; -import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; -import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; -import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js'; -import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js'; -import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; -import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; -import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; -import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; -import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; -import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; -import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; -import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; -import * as ep___admin_showUser from './endpoints/admin/show-user.js'; -import * as ep___admin_showUsers from './endpoints/admin/show-users.js'; -import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js'; -import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js'; -import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js'; -import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; -import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; -import * as ep___admin_vacuum from './endpoints/admin/vacuum.js'; -import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; -import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js'; -import * as ep___announcements from './endpoints/announcements.js'; -import * as ep___antennas_create from './endpoints/antennas/create.js'; -import * as ep___antennas_delete from './endpoints/antennas/delete.js'; -import * as ep___antennas_list from './endpoints/antennas/list.js'; -import * as ep___antennas_markRead from './endpoints/antennas/markread.js'; -import * as ep___antennas_notes from './endpoints/antennas/notes.js'; -import * as ep___antennas_show from './endpoints/antennas/show.js'; -import * as ep___antennas_update from './endpoints/antennas/update.js'; -import * as ep___ap_get from './endpoints/ap/get.js'; -import * as ep___ap_show from './endpoints/ap/show.js'; -import * as ep___app_create from './endpoints/app/create.js'; -import * as ep___app_show from './endpoints/app/show.js'; -import * as ep___auth_accept from './endpoints/auth/accept.js'; -import * as ep___auth_session_generate from './endpoints/auth/session/generate.js'; -import * as ep___auth_session_show from './endpoints/auth/session/show.js'; -import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js'; -import * as ep___blocking_create from './endpoints/blocking/create.js'; -import * as ep___blocking_delete from './endpoints/blocking/delete.js'; -import * as ep___blocking_list from './endpoints/blocking/list.js'; -import * as ep___channels_create from './endpoints/channels/create.js'; -import * as ep___channels_featured from './endpoints/channels/featured.js'; -import * as ep___channels_follow from './endpoints/channels/follow.js'; -import * as ep___channels_followed from './endpoints/channels/followed.js'; -import * as ep___channels_owned from './endpoints/channels/owned.js'; -import * as ep___channels_show from './endpoints/channels/show.js'; -import * as ep___channels_timeline from './endpoints/channels/timeline.js'; -import * as ep___channels_unfollow from './endpoints/channels/unfollow.js'; -import * as ep___channels_update from './endpoints/channels/update.js'; -import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; -import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; -import * as ep___charts_drive from './endpoints/charts/drive.js'; -import * as ep___charts_federation from './endpoints/charts/federation.js'; -import * as ep___charts_hashtag from './endpoints/charts/hashtag.js'; -import * as ep___charts_instance from './endpoints/charts/instance.js'; -import * as ep___charts_notes from './endpoints/charts/notes.js'; -import * as ep___charts_user_drive from './endpoints/charts/user/drive.js'; -import * as ep___charts_user_following from './endpoints/charts/user/following.js'; -import * as ep___charts_user_notes from './endpoints/charts/user/notes.js'; -import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; -import * as ep___charts_users from './endpoints/charts/users.js'; -import * as ep___clips_addNote from './endpoints/clips/add-note.js'; -import * as ep___clips_removeNote from './endpoints/clips/remove-note.js'; -import * as ep___clips_create from './endpoints/clips/create.js'; -import * as ep___clips_delete from './endpoints/clips/delete.js'; -import * as ep___clips_list from './endpoints/clips/list.js'; -import * as ep___clips_notes from './endpoints/clips/notes.js'; -import * as ep___clips_show from './endpoints/clips/show.js'; -import * as ep___clips_update from './endpoints/clips/update.js'; -import * as ep___drive from './endpoints/drive.js'; -import * as ep___drive_files from './endpoints/drive/files.js'; -import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js'; -import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js'; -import * as ep___drive_files_captionImage from './endpoints/drive/files/caption-image.js'; -import * as ep___drive_files_create from './endpoints/drive/files/create.js'; -import * as ep___drive_files_delete from './endpoints/drive/files/delete.js'; -import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js'; -import * as ep___drive_files_find from './endpoints/drive/files/find.js'; -import * as ep___drive_files_show from './endpoints/drive/files/show.js'; -import * as ep___drive_files_update from './endpoints/drive/files/update.js'; -import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js'; -import * as ep___drive_folders from './endpoints/drive/folders.js'; -import * as ep___drive_folders_create from './endpoints/drive/folders/create.js'; -import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js'; -import * as ep___drive_folders_find from './endpoints/drive/folders/find.js'; -import * as ep___drive_folders_show from './endpoints/drive/folders/show.js'; -import * as ep___drive_folders_update from './endpoints/drive/folders/update.js'; -import * as ep___drive_stream from './endpoints/drive/stream.js'; -import * as ep___emailAddress_available from './endpoints/email-address/available.js'; -import * as ep___endpoint from './endpoints/endpoint.js'; -import * as ep___endpoints from './endpoints/endpoints.js'; -import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js'; -import * as ep___federation_followers from './endpoints/federation/followers.js'; -import * as ep___federation_following from './endpoints/federation/following.js'; -import * as ep___federation_instances from './endpoints/federation/instances.js'; -import * as ep___federation_showInstance from './endpoints/federation/show-instance.js'; -import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js'; -import * as ep___federation_users from './endpoints/federation/users.js'; -import * as ep___federation_stats from './endpoints/federation/stats.js'; -import * as ep___following_create from './endpoints/following/create.js'; -import * as ep___following_delete from './endpoints/following/delete.js'; -import * as ep___following_invalidate from './endpoints/following/invalidate.js'; -import * as ep___following_requests_accept from './endpoints/following/requests/accept.js'; -import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js'; -import * as ep___following_requests_list from './endpoints/following/requests/list.js'; -import * as ep___following_requests_reject from './endpoints/following/requests/reject.js'; -import * as ep___gallery_featured from './endpoints/gallery/featured.js'; -import * as ep___gallery_popular from './endpoints/gallery/popular.js'; -import * as ep___gallery_posts from './endpoints/gallery/posts.js'; -import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js'; -import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js'; -import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js'; -import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js'; -import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js'; -import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js'; -import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js'; -import * as ep___hashtags_list from './endpoints/hashtags/list.js'; -import * as ep___hashtags_search from './endpoints/hashtags/search.js'; -import * as ep___hashtags_show from './endpoints/hashtags/show.js'; -import * as ep___hashtags_trend from './endpoints/hashtags/trend.js'; -import * as ep___hashtags_users from './endpoints/hashtags/users.js'; -import * as ep___i from './endpoints/i.js'; -import * as ep___i_2fa_done from './endpoints/i/2fa/done.js'; -import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js'; -import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js'; -import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js'; -import * as ep___i_2fa_register from './endpoints/i/2fa/register.js'; -import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js'; -import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js'; -import * as ep___i_apps from './endpoints/i/apps.js'; -import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js'; -import * as ep___i_changePassword from './endpoints/i/change-password.js'; -import * as ep___i_deleteAccount from './endpoints/i/delete-account.js'; -import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; -import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; -import * as ep___i_exportMute from './endpoints/i/export-mute.js'; -import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; -import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; -import * as ep___i_favorites from './endpoints/i/favorites.js'; -import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; -import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; -import * as ep___i_getWordMutedNotesCount from './endpoints/i/get-word-muted-notes-count.js'; -import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; -import * as ep___i_importFollowing from './endpoints/i/import-following.js'; -import * as ep___i_importMuting from './endpoints/i/import-muting.js'; -import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; -import * as ep___i_notifications from './endpoints/i/notifications.js'; -import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; -import * as ep___i_pages from './endpoints/i/pages.js'; -import * as ep___i_pin from './endpoints/i/pin.js'; -import * as ep___i_readAllMessagingMessages from './endpoints/i/read-all-messaging-messages.js'; -import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; -import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; -import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; -import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js'; -import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js'; -import * as ep___i_registry_get from './endpoints/i/registry/get.js'; -import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; -import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; -import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; -import * as ep___i_registry_scopes from './endpoints/i/registry/scopes.js'; -import * as ep___i_registry_set from './endpoints/i/registry/set.js'; -import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; -import * as ep___i_signinHistory from './endpoints/i/signin-history.js'; -import * as ep___i_unpin from './endpoints/i/unpin.js'; -import * as ep___i_updateEmail from './endpoints/i/update-email.js'; -import * as ep___i_update from './endpoints/i/update.js'; -import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js'; -import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; -import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; -import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; -import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; -import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; -import * as ep___messaging_history from './endpoints/messaging/history.js'; -import * as ep___messaging_messages from './endpoints/messaging/messages.js'; -import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js'; -import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js'; -import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js'; -import * as ep___meta from './endpoints/meta.js'; -import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; -import * as ep___mute_create from './endpoints/mute/create.js'; -import * as ep___mute_delete from './endpoints/mute/delete.js'; -import * as ep___mute_list from './endpoints/mute/list.js'; -import * as ep___my_apps from './endpoints/my/apps.js'; -import * as ep___notes from './endpoints/notes.js'; -import * as ep___notes_children from './endpoints/notes/children.js'; -import * as ep___notes_clips from './endpoints/notes/clips.js'; -import * as ep___notes_conversation from './endpoints/notes/conversation.js'; -import * as ep___notes_create from './endpoints/notes/create.js'; -import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; -import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; -import * as ep___notes_featured from './endpoints/notes/featured.js'; -import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; -import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; -import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js'; -import * as ep___notes_recommendedTimeline from './endpoints/notes/recommended-timeline.js'; -import * as ep___notes_mentions from './endpoints/notes/mentions.js'; -import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js'; -import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js'; -import * as ep___notes_reactions from './endpoints/notes/reactions.js'; -import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; -import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; -import * as ep___notes_renotes from './endpoints/notes/renotes.js'; -import * as ep___notes_replies from './endpoints/notes/replies.js'; -import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js'; -import * as ep___notes_search from './endpoints/notes/search.js'; -import * as ep___notes_show from './endpoints/notes/show.js'; -import * as ep___notes_state from './endpoints/notes/state.js'; -import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js'; -import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js'; -import * as ep___notes_timeline from './endpoints/notes/timeline.js'; -import * as ep___notes_translate from './endpoints/notes/translate.js'; -import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; -import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; -import * as ep___notes_watching_create from './endpoints/notes/watching/create.js'; -import * as ep___notes_watching_delete from './endpoints/notes/watching/delete.js'; -import * as ep___notifications_create from './endpoints/notifications/create.js'; -import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; -import * as ep___notifications_read from './endpoints/notifications/read.js'; -import * as ep___pagePush from './endpoints/page-push.js'; -import * as ep___pages_create from './endpoints/pages/create.js'; -import * as ep___pages_delete from './endpoints/pages/delete.js'; -import * as ep___pages_featured from './endpoints/pages/featured.js'; -import * as ep___pages_like from './endpoints/pages/like.js'; -import * as ep___pages_show from './endpoints/pages/show.js'; -import * as ep___pages_unlike from './endpoints/pages/unlike.js'; -import * as ep___pages_update from './endpoints/pages/update.js'; -import * as ep___ping from './endpoints/ping.js'; -import * as ep___recommendedInstances from './endpoints/recommended-instances.js'; -import * as ep___pinnedUsers from './endpoints/pinned-users.js'; -import * as ep___customMOTD from './endpoints/custom-motd.js'; -import * as ep___customSplashIcons from './endpoints/custom-splash-icons.js'; -import * as ep___latestVersion from './endpoints/latest-version.js'; -import * as ep___patrons from './endpoints/patrons.js'; -import * as ep___release from './endpoints/release.js'; -import * as ep___promo_read from './endpoints/promo/read.js'; -import * as ep___requestResetPassword from './endpoints/request-reset-password.js'; -import * as ep___resetDb from './endpoints/reset-db.js'; -import * as ep___resetPassword from './endpoints/reset-password.js'; -import * as ep___serverInfo from './endpoints/server-info.js'; -import * as ep___stats from './endpoints/stats.js'; -import * as ep___sw_register from './endpoints/sw/register.js'; -import * as ep___sw_unregister from './endpoints/sw/unregister.js'; -import * as ep___test from './endpoints/test.js'; -import * as ep___username_available from './endpoints/username/available.js'; -import * as ep___users from './endpoints/users.js'; -import * as ep___users_clips from './endpoints/users/clips.js'; -import * as ep___users_followers from './endpoints/users/followers.js'; -import * as ep___users_following from './endpoints/users/following.js'; -import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; -import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; -import * as ep___users_groups_create from './endpoints/users/groups/create.js'; -import * as ep___users_groups_delete from './endpoints/users/groups/delete.js'; -import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js'; -import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js'; -import * as ep___users_groups_invite from './endpoints/users/groups/invite.js'; -import * as ep___users_groups_joined from './endpoints/users/groups/joined.js'; -import * as ep___users_groups_leave from './endpoints/users/groups/leave.js'; -import * as ep___users_groups_owned from './endpoints/users/groups/owned.js'; -import * as ep___users_groups_pull from './endpoints/users/groups/pull.js'; -import * as ep___users_groups_show from './endpoints/users/groups/show.js'; -import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js'; -import * as ep___users_groups_update from './endpoints/users/groups/update.js'; -import * as ep___users_lists_create from './endpoints/users/lists/create.js'; -import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; -import * as ep___users_lists_delete_all from './endpoints/users/lists/delete-all.js'; -import * as ep___users_lists_list from './endpoints/users/lists/list.js'; -import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; -import * as ep___users_lists_push from './endpoints/users/lists/push.js'; -import * as ep___users_lists_show from './endpoints/users/lists/show.js'; -import * as ep___users_lists_update from './endpoints/users/lists/update.js'; -import * as ep___users_notes from './endpoints/users/notes.js'; -import * as ep___users_pages from './endpoints/users/pages.js'; -import * as ep___users_reactions from './endpoints/users/reactions.js'; -import * as ep___users_recommendation from './endpoints/users/recommendation.js'; -import * as ep___users_relation from './endpoints/users/relation.js'; -import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; -import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; -import * as ep___users_search from './endpoints/users/search.js'; -import * as ep___users_show from './endpoints/users/show.js'; -import * as ep___users_stats from './endpoints/users/stats.js'; -import * as ep___fetchRss from './endpoints/fetch-rss.js'; -import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; +import * as ep___admin_meta from "./endpoints/admin/meta.js"; +import * as ep___admin_abuseUserReports from "./endpoints/admin/abuse-user-reports.js"; +import * as ep___admin_accounts_create from "./endpoints/admin/accounts/create.js"; +import * as ep___admin_accounts_delete from "./endpoints/admin/accounts/delete.js"; +import * as ep___admin_accounts_hosted from "./endpoints/admin/accounts/hosted.js"; +import * as ep___admin_ad_create from "./endpoints/admin/ad/create.js"; +import * as ep___admin_ad_delete from "./endpoints/admin/ad/delete.js"; +import * as ep___admin_ad_list from "./endpoints/admin/ad/list.js"; +import * as ep___admin_ad_update from "./endpoints/admin/ad/update.js"; +import * as ep___admin_announcements_create from "./endpoints/admin/announcements/create.js"; +import * as ep___admin_announcements_delete from "./endpoints/admin/announcements/delete.js"; +import * as ep___admin_announcements_list from "./endpoints/admin/announcements/list.js"; +import * as ep___admin_announcements_update from "./endpoints/admin/announcements/update.js"; +import * as ep___admin_deleteAllFilesOfAUser from "./endpoints/admin/delete-all-files-of-a-user.js"; +import * as ep___admin_drive_cleanRemoteFiles from "./endpoints/admin/drive/clean-remote-files.js"; +import * as ep___admin_drive_cleanup from "./endpoints/admin/drive/cleanup.js"; +import * as ep___admin_drive_files from "./endpoints/admin/drive/files.js"; +import * as ep___admin_drive_showFile from "./endpoints/admin/drive/show-file.js"; +import * as ep___admin_emoji_addAliasesBulk from "./endpoints/admin/emoji/add-aliases-bulk.js"; +import * as ep___admin_emoji_add from "./endpoints/admin/emoji/add.js"; +import * as ep___admin_emoji_copy from "./endpoints/admin/emoji/copy.js"; +import * as ep___admin_emoji_deleteBulk from "./endpoints/admin/emoji/delete-bulk.js"; +import * as ep___admin_emoji_delete from "./endpoints/admin/emoji/delete.js"; +import * as ep___admin_emoji_importZip from "./endpoints/admin/emoji/import-zip.js"; +import * as ep___admin_emoji_listRemote from "./endpoints/admin/emoji/list-remote.js"; +import * as ep___admin_emoji_list from "./endpoints/admin/emoji/list.js"; +import * as ep___admin_emoji_removeAliasesBulk from "./endpoints/admin/emoji/remove-aliases-bulk.js"; +import * as ep___admin_emoji_setAliasesBulk from "./endpoints/admin/emoji/set-aliases-bulk.js"; +import * as ep___admin_emoji_setCategoryBulk from "./endpoints/admin/emoji/set-category-bulk.js"; +import * as ep___admin_emoji_update from "./endpoints/admin/emoji/update.js"; +import * as ep___admin_federation_deleteAllFiles from "./endpoints/admin/federation/delete-all-files.js"; +import * as ep___admin_federation_refreshRemoteInstanceMetadata from "./endpoints/admin/federation/refresh-remote-instance-metadata.js"; +import * as ep___admin_federation_removeAllFollowing from "./endpoints/admin/federation/remove-all-following.js"; +import * as ep___admin_federation_updateInstance from "./endpoints/admin/federation/update-instance.js"; +import * as ep___admin_getIndexStats from "./endpoints/admin/get-index-stats.js"; +import * as ep___admin_getTableStats from "./endpoints/admin/get-table-stats.js"; +import * as ep___admin_getUserIps from "./endpoints/admin/get-user-ips.js"; +import * as ep___admin_invite from "./endpoints/admin/invite.js"; +import * as ep___admin_moderators_add from "./endpoints/admin/moderators/add.js"; +import * as ep___admin_moderators_remove from "./endpoints/admin/moderators/remove.js"; +import * as ep___admin_promo_create from "./endpoints/admin/promo/create.js"; +import * as ep___admin_queue_clear from "./endpoints/admin/queue/clear.js"; +import * as ep___admin_queue_deliverDelayed from "./endpoints/admin/queue/deliver-delayed.js"; +import * as ep___admin_queue_inboxDelayed from "./endpoints/admin/queue/inbox-delayed.js"; +import * as ep___admin_queue_stats from "./endpoints/admin/queue/stats.js"; +import * as ep___admin_relays_add from "./endpoints/admin/relays/add.js"; +import * as ep___admin_relays_list from "./endpoints/admin/relays/list.js"; +import * as ep___admin_relays_remove from "./endpoints/admin/relays/remove.js"; +import * as ep___admin_resetPassword from "./endpoints/admin/reset-password.js"; +import * as ep___admin_resolveAbuseUserReport from "./endpoints/admin/resolve-abuse-user-report.js"; +import * as ep___admin_sendEmail from "./endpoints/admin/send-email.js"; +import * as ep___admin_serverInfo from "./endpoints/admin/server-info.js"; +import * as ep___admin_showModerationLogs from "./endpoints/admin/show-moderation-logs.js"; +import * as ep___admin_showUser from "./endpoints/admin/show-user.js"; +import * as ep___admin_showUsers from "./endpoints/admin/show-users.js"; +import * as ep___admin_silenceUser from "./endpoints/admin/silence-user.js"; +import * as ep___admin_suspendUser from "./endpoints/admin/suspend-user.js"; +import * as ep___admin_unsilenceUser from "./endpoints/admin/unsilence-user.js"; +import * as ep___admin_unsuspendUser from "./endpoints/admin/unsuspend-user.js"; +import * as ep___admin_updateMeta from "./endpoints/admin/update-meta.js"; +import * as ep___admin_vacuum from "./endpoints/admin/vacuum.js"; +import * as ep___admin_deleteAccount from "./endpoints/admin/delete-account.js"; +import * as ep___admin_updateUserNote from "./endpoints/admin/update-user-note.js"; +import * as ep___announcements from "./endpoints/announcements.js"; +import * as ep___antennas_create from "./endpoints/antennas/create.js"; +import * as ep___antennas_delete from "./endpoints/antennas/delete.js"; +import * as ep___antennas_list from "./endpoints/antennas/list.js"; +import * as ep___antennas_markRead from "./endpoints/antennas/markread.js"; +import * as ep___antennas_notes from "./endpoints/antennas/notes.js"; +import * as ep___antennas_show from "./endpoints/antennas/show.js"; +import * as ep___antennas_update from "./endpoints/antennas/update.js"; +import * as ep___ap_get from "./endpoints/ap/get.js"; +import * as ep___ap_show from "./endpoints/ap/show.js"; +import * as ep___app_create from "./endpoints/app/create.js"; +import * as ep___app_show from "./endpoints/app/show.js"; +import * as ep___auth_accept from "./endpoints/auth/accept.js"; +import * as ep___auth_session_generate from "./endpoints/auth/session/generate.js"; +import * as ep___auth_session_show from "./endpoints/auth/session/show.js"; +import * as ep___auth_session_userkey from "./endpoints/auth/session/userkey.js"; +import * as ep___blocking_create from "./endpoints/blocking/create.js"; +import * as ep___blocking_delete from "./endpoints/blocking/delete.js"; +import * as ep___blocking_list from "./endpoints/blocking/list.js"; +import * as ep___channels_create from "./endpoints/channels/create.js"; +import * as ep___channels_featured from "./endpoints/channels/featured.js"; +import * as ep___channels_follow from "./endpoints/channels/follow.js"; +import * as ep___channels_followed from "./endpoints/channels/followed.js"; +import * as ep___channels_owned from "./endpoints/channels/owned.js"; +import * as ep___channels_show from "./endpoints/channels/show.js"; +import * as ep___channels_timeline from "./endpoints/channels/timeline.js"; +import * as ep___channels_unfollow from "./endpoints/channels/unfollow.js"; +import * as ep___channels_update from "./endpoints/channels/update.js"; +import * as ep___charts_activeUsers from "./endpoints/charts/active-users.js"; +import * as ep___charts_apRequest from "./endpoints/charts/ap-request.js"; +import * as ep___charts_drive from "./endpoints/charts/drive.js"; +import * as ep___charts_federation from "./endpoints/charts/federation.js"; +import * as ep___charts_hashtag from "./endpoints/charts/hashtag.js"; +import * as ep___charts_instance from "./endpoints/charts/instance.js"; +import * as ep___charts_notes from "./endpoints/charts/notes.js"; +import * as ep___charts_user_drive from "./endpoints/charts/user/drive.js"; +import * as ep___charts_user_following from "./endpoints/charts/user/following.js"; +import * as ep___charts_user_notes from "./endpoints/charts/user/notes.js"; +import * as ep___charts_user_reactions from "./endpoints/charts/user/reactions.js"; +import * as ep___charts_users from "./endpoints/charts/users.js"; +import * as ep___clips_addNote from "./endpoints/clips/add-note.js"; +import * as ep___clips_removeNote from "./endpoints/clips/remove-note.js"; +import * as ep___clips_create from "./endpoints/clips/create.js"; +import * as ep___clips_delete from "./endpoints/clips/delete.js"; +import * as ep___clips_list from "./endpoints/clips/list.js"; +import * as ep___clips_notes from "./endpoints/clips/notes.js"; +import * as ep___clips_show from "./endpoints/clips/show.js"; +import * as ep___clips_update from "./endpoints/clips/update.js"; +import * as ep___drive from "./endpoints/drive.js"; +import * as ep___drive_files from "./endpoints/drive/files.js"; +import * as ep___drive_files_attachedNotes from "./endpoints/drive/files/attached-notes.js"; +import * as ep___drive_files_checkExistence from "./endpoints/drive/files/check-existence.js"; +import * as ep___drive_files_captionImage from "./endpoints/drive/files/caption-image.js"; +import * as ep___drive_files_create from "./endpoints/drive/files/create.js"; +import * as ep___drive_files_delete from "./endpoints/drive/files/delete.js"; +import * as ep___drive_files_findByHash from "./endpoints/drive/files/find-by-hash.js"; +import * as ep___drive_files_find from "./endpoints/drive/files/find.js"; +import * as ep___drive_files_show from "./endpoints/drive/files/show.js"; +import * as ep___drive_files_update from "./endpoints/drive/files/update.js"; +import * as ep___drive_files_uploadFromUrl from "./endpoints/drive/files/upload-from-url.js"; +import * as ep___drive_folders from "./endpoints/drive/folders.js"; +import * as ep___drive_folders_create from "./endpoints/drive/folders/create.js"; +import * as ep___drive_folders_delete from "./endpoints/drive/folders/delete.js"; +import * as ep___drive_folders_find from "./endpoints/drive/folders/find.js"; +import * as ep___drive_folders_show from "./endpoints/drive/folders/show.js"; +import * as ep___drive_folders_update from "./endpoints/drive/folders/update.js"; +import * as ep___drive_stream from "./endpoints/drive/stream.js"; +import * as ep___emailAddress_available from "./endpoints/email-address/available.js"; +import * as ep___endpoint from "./endpoints/endpoint.js"; +import * as ep___endpoints from "./endpoints/endpoints.js"; +import * as ep___exportCustomEmojis from "./endpoints/export-custom-emojis.js"; +import * as ep___federation_followers from "./endpoints/federation/followers.js"; +import * as ep___federation_following from "./endpoints/federation/following.js"; +import * as ep___federation_instances from "./endpoints/federation/instances.js"; +import * as ep___federation_showInstance from "./endpoints/federation/show-instance.js"; +import * as ep___federation_updateRemoteUser from "./endpoints/federation/update-remote-user.js"; +import * as ep___federation_users from "./endpoints/federation/users.js"; +import * as ep___federation_stats from "./endpoints/federation/stats.js"; +import * as ep___following_create from "./endpoints/following/create.js"; +import * as ep___following_delete from "./endpoints/following/delete.js"; +import * as ep___following_invalidate from "./endpoints/following/invalidate.js"; +import * as ep___following_requests_accept from "./endpoints/following/requests/accept.js"; +import * as ep___following_requests_cancel from "./endpoints/following/requests/cancel.js"; +import * as ep___following_requests_list from "./endpoints/following/requests/list.js"; +import * as ep___following_requests_reject from "./endpoints/following/requests/reject.js"; +import * as ep___gallery_featured from "./endpoints/gallery/featured.js"; +import * as ep___gallery_popular from "./endpoints/gallery/popular.js"; +import * as ep___gallery_posts from "./endpoints/gallery/posts.js"; +import * as ep___gallery_posts_create from "./endpoints/gallery/posts/create.js"; +import * as ep___gallery_posts_delete from "./endpoints/gallery/posts/delete.js"; +import * as ep___gallery_posts_like from "./endpoints/gallery/posts/like.js"; +import * as ep___gallery_posts_show from "./endpoints/gallery/posts/show.js"; +import * as ep___gallery_posts_unlike from "./endpoints/gallery/posts/unlike.js"; +import * as ep___gallery_posts_update from "./endpoints/gallery/posts/update.js"; +import * as ep___getOnlineUsersCount from "./endpoints/get-online-users-count.js"; +import * as ep___hashtags_list from "./endpoints/hashtags/list.js"; +import * as ep___hashtags_search from "./endpoints/hashtags/search.js"; +import * as ep___hashtags_show from "./endpoints/hashtags/show.js"; +import * as ep___hashtags_trend from "./endpoints/hashtags/trend.js"; +import * as ep___hashtags_users from "./endpoints/hashtags/users.js"; +import * as ep___i from "./endpoints/i.js"; +import * as ep___i_2fa_done from "./endpoints/i/2fa/done.js"; +import * as ep___i_2fa_keyDone from "./endpoints/i/2fa/key-done.js"; +import * as ep___i_2fa_passwordLess from "./endpoints/i/2fa/password-less.js"; +import * as ep___i_2fa_registerKey from "./endpoints/i/2fa/register-key.js"; +import * as ep___i_2fa_register from "./endpoints/i/2fa/register.js"; +import * as ep___i_2fa_removeKey from "./endpoints/i/2fa/remove-key.js"; +import * as ep___i_2fa_unregister from "./endpoints/i/2fa/unregister.js"; +import * as ep___i_apps from "./endpoints/i/apps.js"; +import * as ep___i_authorizedApps from "./endpoints/i/authorized-apps.js"; +import * as ep___i_changePassword from "./endpoints/i/change-password.js"; +import * as ep___i_deleteAccount from "./endpoints/i/delete-account.js"; +import * as ep___i_exportBlocking from "./endpoints/i/export-blocking.js"; +import * as ep___i_exportFollowing from "./endpoints/i/export-following.js"; +import * as ep___i_exportMute from "./endpoints/i/export-mute.js"; +import * as ep___i_exportNotes from "./endpoints/i/export-notes.js"; +import * as ep___i_exportUserLists from "./endpoints/i/export-user-lists.js"; +import * as ep___i_favorites from "./endpoints/i/favorites.js"; +import * as ep___i_gallery_likes from "./endpoints/i/gallery/likes.js"; +import * as ep___i_gallery_posts from "./endpoints/i/gallery/posts.js"; +import * as ep___i_getWordMutedNotesCount from "./endpoints/i/get-word-muted-notes-count.js"; +import * as ep___i_importBlocking from "./endpoints/i/import-blocking.js"; +import * as ep___i_importFollowing from "./endpoints/i/import-following.js"; +import * as ep___i_importMuting from "./endpoints/i/import-muting.js"; +import * as ep___i_importUserLists from "./endpoints/i/import-user-lists.js"; +import * as ep___i_notifications from "./endpoints/i/notifications.js"; +import * as ep___i_pageLikes from "./endpoints/i/page-likes.js"; +import * as ep___i_pages from "./endpoints/i/pages.js"; +import * as ep___i_pin from "./endpoints/i/pin.js"; +import * as ep___i_readAllMessagingMessages from "./endpoints/i/read-all-messaging-messages.js"; +import * as ep___i_readAllUnreadNotes from "./endpoints/i/read-all-unread-notes.js"; +import * as ep___i_readAnnouncement from "./endpoints/i/read-announcement.js"; +import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js"; +import * as ep___i_registry_getAll from "./endpoints/i/registry/get-all.js"; +import * as ep___i_registry_getDetail from "./endpoints/i/registry/get-detail.js"; +import * as ep___i_registry_get from "./endpoints/i/registry/get.js"; +import * as ep___i_registry_keysWithType from "./endpoints/i/registry/keys-with-type.js"; +import * as ep___i_registry_keys from "./endpoints/i/registry/keys.js"; +import * as ep___i_registry_remove from "./endpoints/i/registry/remove.js"; +import * as ep___i_registry_scopes from "./endpoints/i/registry/scopes.js"; +import * as ep___i_registry_set from "./endpoints/i/registry/set.js"; +import * as ep___i_revokeToken from "./endpoints/i/revoke-token.js"; +import * as ep___i_signinHistory from "./endpoints/i/signin-history.js"; +import * as ep___i_unpin from "./endpoints/i/unpin.js"; +import * as ep___i_updateEmail from "./endpoints/i/update-email.js"; +import * as ep___i_update from "./endpoints/i/update.js"; +import * as ep___i_userGroupInvites from "./endpoints/i/user-group-invites.js"; +import * as ep___i_webhooks_create from "./endpoints/i/webhooks/create.js"; +import * as ep___i_webhooks_show from "./endpoints/i/webhooks/show.js"; +import * as ep___i_webhooks_list from "./endpoints/i/webhooks/list.js"; +import * as ep___i_webhooks_update from "./endpoints/i/webhooks/update.js"; +import * as ep___i_webhooks_delete from "./endpoints/i/webhooks/delete.js"; +import * as ep___messaging_history from "./endpoints/messaging/history.js"; +import * as ep___messaging_messages from "./endpoints/messaging/messages.js"; +import * as ep___messaging_messages_create from "./endpoints/messaging/messages/create.js"; +import * as ep___messaging_messages_delete from "./endpoints/messaging/messages/delete.js"; +import * as ep___messaging_messages_read from "./endpoints/messaging/messages/read.js"; +import * as ep___meta from "./endpoints/meta.js"; +import * as ep___miauth_genToken from "./endpoints/miauth/gen-token.js"; +import * as ep___mute_create from "./endpoints/mute/create.js"; +import * as ep___mute_delete from "./endpoints/mute/delete.js"; +import * as ep___mute_list from "./endpoints/mute/list.js"; +import * as ep___my_apps from "./endpoints/my/apps.js"; +import * as ep___notes from "./endpoints/notes.js"; +import * as ep___notes_children from "./endpoints/notes/children.js"; +import * as ep___notes_clips from "./endpoints/notes/clips.js"; +import * as ep___notes_conversation from "./endpoints/notes/conversation.js"; +import * as ep___notes_create from "./endpoints/notes/create.js"; +import * as ep___notes_delete from "./endpoints/notes/delete.js"; +import * as ep___notes_favorites_create from "./endpoints/notes/favorites/create.js"; +import * as ep___notes_favorites_delete from "./endpoints/notes/favorites/delete.js"; +import * as ep___notes_featured from "./endpoints/notes/featured.js"; +import * as ep___notes_globalTimeline from "./endpoints/notes/global-timeline.js"; +import * as ep___notes_hybridTimeline from "./endpoints/notes/hybrid-timeline.js"; +import * as ep___notes_localTimeline from "./endpoints/notes/local-timeline.js"; +import * as ep___notes_recommendedTimeline from "./endpoints/notes/recommended-timeline.js"; +import * as ep___notes_mentions from "./endpoints/notes/mentions.js"; +import * as ep___notes_polls_recommendation from "./endpoints/notes/polls/recommendation.js"; +import * as ep___notes_polls_vote from "./endpoints/notes/polls/vote.js"; +import * as ep___notes_reactions from "./endpoints/notes/reactions.js"; +import * as ep___notes_reactions_create from "./endpoints/notes/reactions/create.js"; +import * as ep___notes_reactions_delete from "./endpoints/notes/reactions/delete.js"; +import * as ep___notes_renotes from "./endpoints/notes/renotes.js"; +import * as ep___notes_replies from "./endpoints/notes/replies.js"; +import * as ep___notes_searchByTag from "./endpoints/notes/search-by-tag.js"; +import * as ep___notes_search from "./endpoints/notes/search.js"; +import * as ep___notes_show from "./endpoints/notes/show.js"; +import * as ep___notes_state from "./endpoints/notes/state.js"; +import * as ep___notes_threadMuting_create from "./endpoints/notes/thread-muting/create.js"; +import * as ep___notes_threadMuting_delete from "./endpoints/notes/thread-muting/delete.js"; +import * as ep___notes_timeline from "./endpoints/notes/timeline.js"; +import * as ep___notes_translate from "./endpoints/notes/translate.js"; +import * as ep___notes_unrenote from "./endpoints/notes/unrenote.js"; +import * as ep___notes_userListTimeline from "./endpoints/notes/user-list-timeline.js"; +import * as ep___notes_watching_create from "./endpoints/notes/watching/create.js"; +import * as ep___notes_watching_delete from "./endpoints/notes/watching/delete.js"; +import * as ep___notifications_create from "./endpoints/notifications/create.js"; +import * as ep___notifications_markAllAsRead from "./endpoints/notifications/mark-all-as-read.js"; +import * as ep___notifications_read from "./endpoints/notifications/read.js"; +import * as ep___pagePush from "./endpoints/page-push.js"; +import * as ep___pages_create from "./endpoints/pages/create.js"; +import * as ep___pages_delete from "./endpoints/pages/delete.js"; +import * as ep___pages_featured from "./endpoints/pages/featured.js"; +import * as ep___pages_like from "./endpoints/pages/like.js"; +import * as ep___pages_show from "./endpoints/pages/show.js"; +import * as ep___pages_unlike from "./endpoints/pages/unlike.js"; +import * as ep___pages_update from "./endpoints/pages/update.js"; +import * as ep___ping from "./endpoints/ping.js"; +import * as ep___recommendedInstances from "./endpoints/recommended-instances.js"; +import * as ep___pinnedUsers from "./endpoints/pinned-users.js"; +import * as ep___customMOTD from "./endpoints/custom-motd.js"; +import * as ep___customSplashIcons from "./endpoints/custom-splash-icons.js"; +import * as ep___latestVersion from "./endpoints/latest-version.js"; +import * as ep___patrons from "./endpoints/patrons.js"; +import * as ep___release from "./endpoints/release.js"; +import * as ep___promo_read from "./endpoints/promo/read.js"; +import * as ep___requestResetPassword from "./endpoints/request-reset-password.js"; +import * as ep___resetDb from "./endpoints/reset-db.js"; +import * as ep___resetPassword from "./endpoints/reset-password.js"; +import * as ep___serverInfo from "./endpoints/server-info.js"; +import * as ep___stats from "./endpoints/stats.js"; +import * as ep___sw_register from "./endpoints/sw/register.js"; +import * as ep___sw_unregister from "./endpoints/sw/unregister.js"; +import * as ep___test from "./endpoints/test.js"; +import * as ep___username_available from "./endpoints/username/available.js"; +import * as ep___users from "./endpoints/users.js"; +import * as ep___users_clips from "./endpoints/users/clips.js"; +import * as ep___users_followers from "./endpoints/users/followers.js"; +import * as ep___users_following from "./endpoints/users/following.js"; +import * as ep___users_gallery_posts from "./endpoints/users/gallery/posts.js"; +import * as ep___users_getFrequentlyRepliedUsers from "./endpoints/users/get-frequently-replied-users.js"; +import * as ep___users_groups_create from "./endpoints/users/groups/create.js"; +import * as ep___users_groups_delete from "./endpoints/users/groups/delete.js"; +import * as ep___users_groups_invitations_accept from "./endpoints/users/groups/invitations/accept.js"; +import * as ep___users_groups_invitations_reject from "./endpoints/users/groups/invitations/reject.js"; +import * as ep___users_groups_invite from "./endpoints/users/groups/invite.js"; +import * as ep___users_groups_joined from "./endpoints/users/groups/joined.js"; +import * as ep___users_groups_leave from "./endpoints/users/groups/leave.js"; +import * as ep___users_groups_owned from "./endpoints/users/groups/owned.js"; +import * as ep___users_groups_pull from "./endpoints/users/groups/pull.js"; +import * as ep___users_groups_show from "./endpoints/users/groups/show.js"; +import * as ep___users_groups_transfer from "./endpoints/users/groups/transfer.js"; +import * as ep___users_groups_update from "./endpoints/users/groups/update.js"; +import * as ep___users_lists_create from "./endpoints/users/lists/create.js"; +import * as ep___users_lists_delete from "./endpoints/users/lists/delete.js"; +import * as ep___users_lists_delete_all from "./endpoints/users/lists/delete-all.js"; +import * as ep___users_lists_list from "./endpoints/users/lists/list.js"; +import * as ep___users_lists_pull from "./endpoints/users/lists/pull.js"; +import * as ep___users_lists_push from "./endpoints/users/lists/push.js"; +import * as ep___users_lists_show from "./endpoints/users/lists/show.js"; +import * as ep___users_lists_update from "./endpoints/users/lists/update.js"; +import * as ep___users_notes from "./endpoints/users/notes.js"; +import * as ep___users_pages from "./endpoints/users/pages.js"; +import * as ep___users_reactions from "./endpoints/users/reactions.js"; +import * as ep___users_recommendation from "./endpoints/users/recommendation.js"; +import * as ep___users_relation from "./endpoints/users/relation.js"; +import * as ep___users_reportAbuse from "./endpoints/users/report-abuse.js"; +import * as ep___users_searchByUsernameAndHost from "./endpoints/users/search-by-username-and-host.js"; +import * as ep___users_search from "./endpoints/users/search.js"; +import * as ep___users_show from "./endpoints/users/show.js"; +import * as ep___users_stats from "./endpoints/users/stats.js"; +import * as ep___fetchRss from "./endpoints/fetch-rss.js"; +import * as ep___admin_driveCapOverride from "./endpoints/admin/drive-capacity-override.js"; //Calckey Move -import * as ep___i_move from './endpoints/i/move.js'; -import * as ep___i_known_as from './endpoints/i/known-as.js'; +import * as ep___i_move from "./endpoints/i/move.js"; +import * as ep___i_known_as from "./endpoints/i/known-as.js"; const eps = [ - ['admin/meta', ep___admin_meta], - ['admin/abuse-user-reports', ep___admin_abuseUserReports], - ['admin/accounts/create', ep___admin_accounts_create], - ['admin/accounts/delete', ep___admin_accounts_delete], - ['admin/accounts/hosted', ep___admin_accounts_hosted], - ['admin/ad/create', ep___admin_ad_create], - ['admin/ad/delete', ep___admin_ad_delete], - ['admin/ad/list', ep___admin_ad_list], - ['admin/ad/update', ep___admin_ad_update], - ['admin/announcements/create', ep___admin_announcements_create], - ['admin/announcements/delete', ep___admin_announcements_delete], - ['admin/announcements/list', ep___admin_announcements_list], - ['admin/announcements/update', ep___admin_announcements_update], - ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser], - ['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles], - ['admin/drive/cleanup', ep___admin_drive_cleanup], - ['admin/drive/files', ep___admin_drive_files], - ['admin/drive/show-file', ep___admin_drive_showFile], - ['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk], - ['admin/emoji/add', ep___admin_emoji_add], - ['admin/emoji/copy', ep___admin_emoji_copy], - ['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk], - ['admin/emoji/delete', ep___admin_emoji_delete], - ['admin/emoji/import-zip', ep___admin_emoji_importZip], - ['admin/emoji/list-remote', ep___admin_emoji_listRemote], - ['admin/emoji/list', ep___admin_emoji_list], - ['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk], - ['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk], - ['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk], - ['admin/emoji/update', ep___admin_emoji_update], - ['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles], - ['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata], - ['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing], - ['admin/federation/update-instance', ep___admin_federation_updateInstance], - ['admin/get-index-stats', ep___admin_getIndexStats], - ['admin/get-table-stats', ep___admin_getTableStats], - ['admin/get-user-ips', ep___admin_getUserIps], - ['admin/invite', ep___admin_invite], - ['admin/moderators/add', ep___admin_moderators_add], - ['admin/moderators/remove', ep___admin_moderators_remove], - ['admin/promo/create', ep___admin_promo_create], - ['admin/queue/clear', ep___admin_queue_clear], - ['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed], - ['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed], - ['admin/queue/stats', ep___admin_queue_stats], - ['admin/relays/add', ep___admin_relays_add], - ['admin/relays/list', ep___admin_relays_list], - ['admin/relays/remove', ep___admin_relays_remove], - ['admin/reset-password', ep___admin_resetPassword], - ['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport], - ['admin/send-email', ep___admin_sendEmail], - ['admin/server-info', ep___admin_serverInfo], - ['admin/show-moderation-logs', ep___admin_showModerationLogs], - ['admin/show-user', ep___admin_showUser], - ['admin/show-users', ep___admin_showUsers], - ['admin/silence-user', ep___admin_silenceUser], - ['admin/suspend-user', ep___admin_suspendUser], - ['admin/unsilence-user', ep___admin_unsilenceUser], - ['admin/unsuspend-user', ep___admin_unsuspendUser], - ['admin/update-meta', ep___admin_updateMeta], - ['admin/vacuum', ep___admin_vacuum], - ['admin/delete-account', ep___admin_deleteAccount], - ['admin/update-user-note', ep___admin_updateUserNote], - ['announcements', ep___announcements], - ['antennas/create', ep___antennas_create], - ['antennas/delete', ep___antennas_delete], - ['antennas/list', ep___antennas_list], - ['antennas/mark-read', ep___antennas_markRead], - ['antennas/notes', ep___antennas_notes], - ['antennas/show', ep___antennas_show], - ['antennas/update', ep___antennas_update], - ['ap/get', ep___ap_get], - ['ap/show', ep___ap_show], - ['app/create', ep___app_create], - ['app/show', ep___app_show], - ['auth/accept', ep___auth_accept], - ['auth/session/generate', ep___auth_session_generate], - ['auth/session/show', ep___auth_session_show], - ['auth/session/userkey', ep___auth_session_userkey], - ['blocking/create', ep___blocking_create], - ['blocking/delete', ep___blocking_delete], - ['blocking/list', ep___blocking_list], - ['channels/create', ep___channels_create], - ['channels/featured', ep___channels_featured], - ['channels/follow', ep___channels_follow], - ['channels/followed', ep___channels_followed], - ['channels/owned', ep___channels_owned], - ['channels/show', ep___channels_show], - ['channels/timeline', ep___channels_timeline], - ['channels/unfollow', ep___channels_unfollow], - ['channels/update', ep___channels_update], - ['charts/active-users', ep___charts_activeUsers], - ['charts/ap-request', ep___charts_apRequest], - ['charts/drive', ep___charts_drive], - ['charts/federation', ep___charts_federation], - ['charts/hashtag', ep___charts_hashtag], - ['charts/instance', ep___charts_instance], - ['charts/notes', ep___charts_notes], - ['charts/user/drive', ep___charts_user_drive], - ['charts/user/following', ep___charts_user_following], - ['charts/user/notes', ep___charts_user_notes], - ['charts/user/reactions', ep___charts_user_reactions], - ['charts/users', ep___charts_users], - ['clips/add-note', ep___clips_addNote], - ['clips/remove-note', ep___clips_removeNote], - ['clips/create', ep___clips_create], - ['clips/delete', ep___clips_delete], - ['clips/list', ep___clips_list], - ['clips/notes', ep___clips_notes], - ['clips/show', ep___clips_show], - ['clips/update', ep___clips_update], - ['drive', ep___drive], - ['drive/files', ep___drive_files], - ['drive/files/attached-notes', ep___drive_files_attachedNotes], - ['drive/files/caption-image', ep___drive_files_captionImage], - ['drive/files/check-existence', ep___drive_files_checkExistence], - ['drive/files/create', ep___drive_files_create], - ['drive/files/delete', ep___drive_files_delete], - ['drive/files/find-by-hash', ep___drive_files_findByHash], - ['drive/files/find', ep___drive_files_find], - ['drive/files/show', ep___drive_files_show], - ['drive/files/update', ep___drive_files_update], - ['drive/files/upload-from-url', ep___drive_files_uploadFromUrl], - ['drive/folders', ep___drive_folders], - ['drive/folders/create', ep___drive_folders_create], - ['drive/folders/delete', ep___drive_folders_delete], - ['drive/folders/find', ep___drive_folders_find], - ['drive/folders/show', ep___drive_folders_show], - ['drive/folders/update', ep___drive_folders_update], - ['drive/stream', ep___drive_stream], - ['email-address/available', ep___emailAddress_available], - ['endpoint', ep___endpoint], - ['endpoints', ep___endpoints], - ['export-custom-emojis', ep___exportCustomEmojis], - ['federation/followers', ep___federation_followers], - ['federation/following', ep___federation_following], - ['federation/instances', ep___federation_instances], - ['federation/show-instance', ep___federation_showInstance], - ['federation/update-remote-user', ep___federation_updateRemoteUser], - ['federation/users', ep___federation_users], - ['federation/stats', ep___federation_stats], - ['following/create', ep___following_create], - ['following/delete', ep___following_delete], - ['following/invalidate', ep___following_invalidate], - ['following/requests/accept', ep___following_requests_accept], - ['following/requests/cancel', ep___following_requests_cancel], - ['following/requests/list', ep___following_requests_list], - ['following/requests/reject', ep___following_requests_reject], - ['gallery/featured', ep___gallery_featured], - ['gallery/popular', ep___gallery_popular], - ['gallery/posts', ep___gallery_posts], - ['gallery/posts/create', ep___gallery_posts_create], - ['gallery/posts/delete', ep___gallery_posts_delete], - ['gallery/posts/like', ep___gallery_posts_like], - ['gallery/posts/show', ep___gallery_posts_show], - ['gallery/posts/unlike', ep___gallery_posts_unlike], - ['gallery/posts/update', ep___gallery_posts_update], - ['get-online-users-count', ep___getOnlineUsersCount], - ['hashtags/list', ep___hashtags_list], - ['hashtags/search', ep___hashtags_search], - ['hashtags/show', ep___hashtags_show], - ['hashtags/trend', ep___hashtags_trend], - ['hashtags/users', ep___hashtags_users], - ['i', ep___i], - ['i/known-as', ep___i_known_as], - ['i/move', ep___i_move], - ['i/2fa/done', ep___i_2fa_done], - ['i/2fa/key-done', ep___i_2fa_keyDone], - ['i/2fa/password-less', ep___i_2fa_passwordLess], - ['i/2fa/register-key', ep___i_2fa_registerKey], - ['i/2fa/register', ep___i_2fa_register], - ['i/2fa/remove-key', ep___i_2fa_removeKey], - ['i/2fa/unregister', ep___i_2fa_unregister], - ['i/apps', ep___i_apps], - ['i/authorized-apps', ep___i_authorizedApps], - ['i/change-password', ep___i_changePassword], - ['i/delete-account', ep___i_deleteAccount], - ['i/export-blocking', ep___i_exportBlocking], - ['i/export-following', ep___i_exportFollowing], - ['i/export-mute', ep___i_exportMute], - ['i/export-notes', ep___i_exportNotes], - ['i/export-user-lists', ep___i_exportUserLists], - ['i/favorites', ep___i_favorites], - ['i/gallery/likes', ep___i_gallery_likes], - ['i/gallery/posts', ep___i_gallery_posts], - ['i/get-word-muted-notes-count', ep___i_getWordMutedNotesCount], - ['i/import-blocking', ep___i_importBlocking], - ['i/import-following', ep___i_importFollowing], - ['i/import-muting', ep___i_importMuting], - ['i/import-user-lists', ep___i_importUserLists], - ['i/notifications', ep___i_notifications], - ['i/page-likes', ep___i_pageLikes], - ['i/pages', ep___i_pages], - ['i/pin', ep___i_pin], - ['i/read-all-messaging-messages', ep___i_readAllMessagingMessages], - ['i/read-all-unread-notes', ep___i_readAllUnreadNotes], - ['i/read-announcement', ep___i_readAnnouncement], - ['i/regenerate-token', ep___i_regenerateToken], - ['i/registry/get-all', ep___i_registry_getAll], - ['i/registry/get-detail', ep___i_registry_getDetail], - ['i/registry/get', ep___i_registry_get], - ['i/registry/keys-with-type', ep___i_registry_keysWithType], - ['i/registry/keys', ep___i_registry_keys], - ['i/registry/remove', ep___i_registry_remove], - ['i/registry/scopes', ep___i_registry_scopes], - ['i/registry/set', ep___i_registry_set], - ['i/revoke-token', ep___i_revokeToken], - ['i/signin-history', ep___i_signinHistory], - ['i/unpin', ep___i_unpin], - ['i/update-email', ep___i_updateEmail], - ['i/update', ep___i_update], - ['i/user-group-invites', ep___i_userGroupInvites], - ['i/webhooks/create', ep___i_webhooks_create], - ['i/webhooks/list', ep___i_webhooks_list], - ['i/webhooks/show', ep___i_webhooks_show], - ['i/webhooks/update', ep___i_webhooks_update], - ['i/webhooks/delete', ep___i_webhooks_delete], - ['messaging/history', ep___messaging_history], - ['messaging/messages', ep___messaging_messages], - ['messaging/messages/create', ep___messaging_messages_create], - ['messaging/messages/delete', ep___messaging_messages_delete], - ['messaging/messages/read', ep___messaging_messages_read], - ['meta', ep___meta], - ['miauth/gen-token', ep___miauth_genToken], - ['mute/create', ep___mute_create], - ['mute/delete', ep___mute_delete], - ['mute/list', ep___mute_list], - ['my/apps', ep___my_apps], - ['notes', ep___notes], - ['notes/children', ep___notes_children], - ['notes/clips', ep___notes_clips], - ['notes/conversation', ep___notes_conversation], - ['notes/create', ep___notes_create], - ['notes/delete', ep___notes_delete], - ['notes/favorites/create', ep___notes_favorites_create], - ['notes/favorites/delete', ep___notes_favorites_delete], - ['notes/featured', ep___notes_featured], - ['notes/global-timeline', ep___notes_globalTimeline], - ['notes/hybrid-timeline', ep___notes_hybridTimeline], - ['notes/local-timeline', ep___notes_localTimeline], - ['notes/recommended-timeline', ep___notes_recommendedTimeline], - ['notes/mentions', ep___notes_mentions], - ['notes/polls/recommendation', ep___notes_polls_recommendation], - ['notes/polls/vote', ep___notes_polls_vote], - ['notes/reactions', ep___notes_reactions], - ['notes/reactions/create', ep___notes_reactions_create], - ['notes/reactions/delete', ep___notes_reactions_delete], - ['notes/renotes', ep___notes_renotes], - ['notes/replies', ep___notes_replies], - ['notes/search-by-tag', ep___notes_searchByTag], - ['notes/search', ep___notes_search], - ['notes/show', ep___notes_show], - ['notes/state', ep___notes_state], - ['notes/thread-muting/create', ep___notes_threadMuting_create], - ['notes/thread-muting/delete', ep___notes_threadMuting_delete], - ['notes/timeline', ep___notes_timeline], - ['notes/translate', ep___notes_translate], - ['notes/unrenote', ep___notes_unrenote], - ['notes/user-list-timeline', ep___notes_userListTimeline], - ['notes/watching/create', ep___notes_watching_create], - ['notes/watching/delete', ep___notes_watching_delete], - ['notifications/create', ep___notifications_create], - ['notifications/mark-all-as-read', ep___notifications_markAllAsRead], - ['notifications/read', ep___notifications_read], - ['page-push', ep___pagePush], - ['pages/create', ep___pages_create], - ['pages/delete', ep___pages_delete], - ['pages/featured', ep___pages_featured], - ['pages/like', ep___pages_like], - ['pages/show', ep___pages_show], - ['pages/unlike', ep___pages_unlike], - ['pages/update', ep___pages_update], - ['ping', ep___ping], - ['pinned-users', ep___pinnedUsers], - ['recommended-instances', ep___recommendedInstances], - ['custom-motd', ep___customMOTD], - ['custom-splash-icons', ep___customSplashIcons], - ['latest-version', ep___latestVersion], - ['patrons', ep___patrons], - ['release', ep___release], - ['promo/read', ep___promo_read], - ['request-reset-password', ep___requestResetPassword], - ['reset-db', ep___resetDb], - ['reset-password', ep___resetPassword], - ['server-info', ep___serverInfo], - ['stats', ep___stats], - ['sw/register', ep___sw_register], - ['sw/unregister', ep___sw_unregister], - ['test', ep___test], - ['username/available', ep___username_available], - ['users', ep___users], - ['users/clips', ep___users_clips], - ['users/followers', ep___users_followers], - ['users/following', ep___users_following], - ['users/gallery/posts', ep___users_gallery_posts], - ['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers], - ['users/groups/create', ep___users_groups_create], - ['users/groups/delete', ep___users_groups_delete], - ['users/groups/invitations/accept', ep___users_groups_invitations_accept], - ['users/groups/invitations/reject', ep___users_groups_invitations_reject], - ['users/groups/invite', ep___users_groups_invite], - ['users/groups/joined', ep___users_groups_joined], - ['users/groups/leave', ep___users_groups_leave], - ['users/groups/owned', ep___users_groups_owned], - ['users/groups/pull', ep___users_groups_pull], - ['users/groups/show', ep___users_groups_show], - ['users/groups/transfer', ep___users_groups_transfer], - ['users/groups/update', ep___users_groups_update], - ['users/lists/create', ep___users_lists_create], - ['users/lists/delete', ep___users_lists_delete], - ['users/lists/delete-all', ep___users_lists_delete_all], - ['users/lists/list', ep___users_lists_list], - ['users/lists/pull', ep___users_lists_pull], - ['users/lists/push', ep___users_lists_push], - ['users/lists/show', ep___users_lists_show], - ['users/lists/update', ep___users_lists_update], - ['users/notes', ep___users_notes], - ['users/pages', ep___users_pages], - ['users/reactions', ep___users_reactions], - ['users/recommendation', ep___users_recommendation], - ['users/relation', ep___users_relation], - ['users/report-abuse', ep___users_reportAbuse], - ['users/search-by-username-and-host', ep___users_searchByUsernameAndHost], - ['users/search', ep___users_search], - ['users/show', ep___users_show], - ['users/stats', ep___users_stats], - ['admin/drive-capacity-override', ep___admin_driveCapOverride], - ['fetch-rss', ep___fetchRss], + ["admin/meta", ep___admin_meta], + ["admin/abuse-user-reports", ep___admin_abuseUserReports], + ["admin/accounts/create", ep___admin_accounts_create], + ["admin/accounts/delete", ep___admin_accounts_delete], + ["admin/accounts/hosted", ep___admin_accounts_hosted], + ["admin/ad/create", ep___admin_ad_create], + ["admin/ad/delete", ep___admin_ad_delete], + ["admin/ad/list", ep___admin_ad_list], + ["admin/ad/update", ep___admin_ad_update], + ["admin/announcements/create", ep___admin_announcements_create], + ["admin/announcements/delete", ep___admin_announcements_delete], + ["admin/announcements/list", ep___admin_announcements_list], + ["admin/announcements/update", ep___admin_announcements_update], + ["admin/delete-all-files-of-a-user", ep___admin_deleteAllFilesOfAUser], + ["admin/drive/clean-remote-files", ep___admin_drive_cleanRemoteFiles], + ["admin/drive/cleanup", ep___admin_drive_cleanup], + ["admin/drive/files", ep___admin_drive_files], + ["admin/drive/show-file", ep___admin_drive_showFile], + ["admin/emoji/add-aliases-bulk", ep___admin_emoji_addAliasesBulk], + ["admin/emoji/add", ep___admin_emoji_add], + ["admin/emoji/copy", ep___admin_emoji_copy], + ["admin/emoji/delete-bulk", ep___admin_emoji_deleteBulk], + ["admin/emoji/delete", ep___admin_emoji_delete], + ["admin/emoji/import-zip", ep___admin_emoji_importZip], + ["admin/emoji/list-remote", ep___admin_emoji_listRemote], + ["admin/emoji/list", ep___admin_emoji_list], + ["admin/emoji/remove-aliases-bulk", ep___admin_emoji_removeAliasesBulk], + ["admin/emoji/set-aliases-bulk", ep___admin_emoji_setAliasesBulk], + ["admin/emoji/set-category-bulk", ep___admin_emoji_setCategoryBulk], + ["admin/emoji/update", ep___admin_emoji_update], + ["admin/federation/delete-all-files", ep___admin_federation_deleteAllFiles], + [ + "admin/federation/refresh-remote-instance-metadata", + ep___admin_federation_refreshRemoteInstanceMetadata, + ], + [ + "admin/federation/remove-all-following", + ep___admin_federation_removeAllFollowing, + ], + ["admin/federation/update-instance", ep___admin_federation_updateInstance], + ["admin/get-index-stats", ep___admin_getIndexStats], + ["admin/get-table-stats", ep___admin_getTableStats], + ["admin/get-user-ips", ep___admin_getUserIps], + ["admin/invite", ep___admin_invite], + ["admin/moderators/add", ep___admin_moderators_add], + ["admin/moderators/remove", ep___admin_moderators_remove], + ["admin/promo/create", ep___admin_promo_create], + ["admin/queue/clear", ep___admin_queue_clear], + ["admin/queue/deliver-delayed", ep___admin_queue_deliverDelayed], + ["admin/queue/inbox-delayed", ep___admin_queue_inboxDelayed], + ["admin/queue/stats", ep___admin_queue_stats], + ["admin/relays/add", ep___admin_relays_add], + ["admin/relays/list", ep___admin_relays_list], + ["admin/relays/remove", ep___admin_relays_remove], + ["admin/reset-password", ep___admin_resetPassword], + ["admin/resolve-abuse-user-report", ep___admin_resolveAbuseUserReport], + ["admin/send-email", ep___admin_sendEmail], + ["admin/server-info", ep___admin_serverInfo], + ["admin/show-moderation-logs", ep___admin_showModerationLogs], + ["admin/show-user", ep___admin_showUser], + ["admin/show-users", ep___admin_showUsers], + ["admin/silence-user", ep___admin_silenceUser], + ["admin/suspend-user", ep___admin_suspendUser], + ["admin/unsilence-user", ep___admin_unsilenceUser], + ["admin/unsuspend-user", ep___admin_unsuspendUser], + ["admin/update-meta", ep___admin_updateMeta], + ["admin/vacuum", ep___admin_vacuum], + ["admin/delete-account", ep___admin_deleteAccount], + ["admin/update-user-note", ep___admin_updateUserNote], + ["announcements", ep___announcements], + ["antennas/create", ep___antennas_create], + ["antennas/delete", ep___antennas_delete], + ["antennas/list", ep___antennas_list], + ["antennas/mark-read", ep___antennas_markRead], + ["antennas/notes", ep___antennas_notes], + ["antennas/show", ep___antennas_show], + ["antennas/update", ep___antennas_update], + ["ap/get", ep___ap_get], + ["ap/show", ep___ap_show], + ["app/create", ep___app_create], + ["app/show", ep___app_show], + ["auth/accept", ep___auth_accept], + ["auth/session/generate", ep___auth_session_generate], + ["auth/session/show", ep___auth_session_show], + ["auth/session/userkey", ep___auth_session_userkey], + ["blocking/create", ep___blocking_create], + ["blocking/delete", ep___blocking_delete], + ["blocking/list", ep___blocking_list], + ["channels/create", ep___channels_create], + ["channels/featured", ep___channels_featured], + ["channels/follow", ep___channels_follow], + ["channels/followed", ep___channels_followed], + ["channels/owned", ep___channels_owned], + ["channels/show", ep___channels_show], + ["channels/timeline", ep___channels_timeline], + ["channels/unfollow", ep___channels_unfollow], + ["channels/update", ep___channels_update], + ["charts/active-users", ep___charts_activeUsers], + ["charts/ap-request", ep___charts_apRequest], + ["charts/drive", ep___charts_drive], + ["charts/federation", ep___charts_federation], + ["charts/hashtag", ep___charts_hashtag], + ["charts/instance", ep___charts_instance], + ["charts/notes", ep___charts_notes], + ["charts/user/drive", ep___charts_user_drive], + ["charts/user/following", ep___charts_user_following], + ["charts/user/notes", ep___charts_user_notes], + ["charts/user/reactions", ep___charts_user_reactions], + ["charts/users", ep___charts_users], + ["clips/add-note", ep___clips_addNote], + ["clips/remove-note", ep___clips_removeNote], + ["clips/create", ep___clips_create], + ["clips/delete", ep___clips_delete], + ["clips/list", ep___clips_list], + ["clips/notes", ep___clips_notes], + ["clips/show", ep___clips_show], + ["clips/update", ep___clips_update], + ["drive", ep___drive], + ["drive/files", ep___drive_files], + ["drive/files/attached-notes", ep___drive_files_attachedNotes], + ["drive/files/caption-image", ep___drive_files_captionImage], + ["drive/files/check-existence", ep___drive_files_checkExistence], + ["drive/files/create", ep___drive_files_create], + ["drive/files/delete", ep___drive_files_delete], + ["drive/files/find-by-hash", ep___drive_files_findByHash], + ["drive/files/find", ep___drive_files_find], + ["drive/files/show", ep___drive_files_show], + ["drive/files/update", ep___drive_files_update], + ["drive/files/upload-from-url", ep___drive_files_uploadFromUrl], + ["drive/folders", ep___drive_folders], + ["drive/folders/create", ep___drive_folders_create], + ["drive/folders/delete", ep___drive_folders_delete], + ["drive/folders/find", ep___drive_folders_find], + ["drive/folders/show", ep___drive_folders_show], + ["drive/folders/update", ep___drive_folders_update], + ["drive/stream", ep___drive_stream], + ["email-address/available", ep___emailAddress_available], + ["endpoint", ep___endpoint], + ["endpoints", ep___endpoints], + ["export-custom-emojis", ep___exportCustomEmojis], + ["federation/followers", ep___federation_followers], + ["federation/following", ep___federation_following], + ["federation/instances", ep___federation_instances], + ["federation/show-instance", ep___federation_showInstance], + ["federation/update-remote-user", ep___federation_updateRemoteUser], + ["federation/users", ep___federation_users], + ["federation/stats", ep___federation_stats], + ["following/create", ep___following_create], + ["following/delete", ep___following_delete], + ["following/invalidate", ep___following_invalidate], + ["following/requests/accept", ep___following_requests_accept], + ["following/requests/cancel", ep___following_requests_cancel], + ["following/requests/list", ep___following_requests_list], + ["following/requests/reject", ep___following_requests_reject], + ["gallery/featured", ep___gallery_featured], + ["gallery/popular", ep___gallery_popular], + ["gallery/posts", ep___gallery_posts], + ["gallery/posts/create", ep___gallery_posts_create], + ["gallery/posts/delete", ep___gallery_posts_delete], + ["gallery/posts/like", ep___gallery_posts_like], + ["gallery/posts/show", ep___gallery_posts_show], + ["gallery/posts/unlike", ep___gallery_posts_unlike], + ["gallery/posts/update", ep___gallery_posts_update], + ["get-online-users-count", ep___getOnlineUsersCount], + ["hashtags/list", ep___hashtags_list], + ["hashtags/search", ep___hashtags_search], + ["hashtags/show", ep___hashtags_show], + ["hashtags/trend", ep___hashtags_trend], + ["hashtags/users", ep___hashtags_users], + ["i", ep___i], + ["i/known-as", ep___i_known_as], + ["i/move", ep___i_move], + ["i/2fa/done", ep___i_2fa_done], + ["i/2fa/key-done", ep___i_2fa_keyDone], + ["i/2fa/password-less", ep___i_2fa_passwordLess], + ["i/2fa/register-key", ep___i_2fa_registerKey], + ["i/2fa/register", ep___i_2fa_register], + ["i/2fa/remove-key", ep___i_2fa_removeKey], + ["i/2fa/unregister", ep___i_2fa_unregister], + ["i/apps", ep___i_apps], + ["i/authorized-apps", ep___i_authorizedApps], + ["i/change-password", ep___i_changePassword], + ["i/delete-account", ep___i_deleteAccount], + ["i/export-blocking", ep___i_exportBlocking], + ["i/export-following", ep___i_exportFollowing], + ["i/export-mute", ep___i_exportMute], + ["i/export-notes", ep___i_exportNotes], + ["i/export-user-lists", ep___i_exportUserLists], + ["i/favorites", ep___i_favorites], + ["i/gallery/likes", ep___i_gallery_likes], + ["i/gallery/posts", ep___i_gallery_posts], + ["i/get-word-muted-notes-count", ep___i_getWordMutedNotesCount], + ["i/import-blocking", ep___i_importBlocking], + ["i/import-following", ep___i_importFollowing], + ["i/import-muting", ep___i_importMuting], + ["i/import-user-lists", ep___i_importUserLists], + ["i/notifications", ep___i_notifications], + ["i/page-likes", ep___i_pageLikes], + ["i/pages", ep___i_pages], + ["i/pin", ep___i_pin], + ["i/read-all-messaging-messages", ep___i_readAllMessagingMessages], + ["i/read-all-unread-notes", ep___i_readAllUnreadNotes], + ["i/read-announcement", ep___i_readAnnouncement], + ["i/regenerate-token", ep___i_regenerateToken], + ["i/registry/get-all", ep___i_registry_getAll], + ["i/registry/get-detail", ep___i_registry_getDetail], + ["i/registry/get", ep___i_registry_get], + ["i/registry/keys-with-type", ep___i_registry_keysWithType], + ["i/registry/keys", ep___i_registry_keys], + ["i/registry/remove", ep___i_registry_remove], + ["i/registry/scopes", ep___i_registry_scopes], + ["i/registry/set", ep___i_registry_set], + ["i/revoke-token", ep___i_revokeToken], + ["i/signin-history", ep___i_signinHistory], + ["i/unpin", ep___i_unpin], + ["i/update-email", ep___i_updateEmail], + ["i/update", ep___i_update], + ["i/user-group-invites", ep___i_userGroupInvites], + ["i/webhooks/create", ep___i_webhooks_create], + ["i/webhooks/list", ep___i_webhooks_list], + ["i/webhooks/show", ep___i_webhooks_show], + ["i/webhooks/update", ep___i_webhooks_update], + ["i/webhooks/delete", ep___i_webhooks_delete], + ["messaging/history", ep___messaging_history], + ["messaging/messages", ep___messaging_messages], + ["messaging/messages/create", ep___messaging_messages_create], + ["messaging/messages/delete", ep___messaging_messages_delete], + ["messaging/messages/read", ep___messaging_messages_read], + ["meta", ep___meta], + ["miauth/gen-token", ep___miauth_genToken], + ["mute/create", ep___mute_create], + ["mute/delete", ep___mute_delete], + ["mute/list", ep___mute_list], + ["my/apps", ep___my_apps], + ["notes", ep___notes], + ["notes/children", ep___notes_children], + ["notes/clips", ep___notes_clips], + ["notes/conversation", ep___notes_conversation], + ["notes/create", ep___notes_create], + ["notes/delete", ep___notes_delete], + ["notes/favorites/create", ep___notes_favorites_create], + ["notes/favorites/delete", ep___notes_favorites_delete], + ["notes/featured", ep___notes_featured], + ["notes/global-timeline", ep___notes_globalTimeline], + ["notes/hybrid-timeline", ep___notes_hybridTimeline], + ["notes/local-timeline", ep___notes_localTimeline], + ["notes/recommended-timeline", ep___notes_recommendedTimeline], + ["notes/mentions", ep___notes_mentions], + ["notes/polls/recommendation", ep___notes_polls_recommendation], + ["notes/polls/vote", ep___notes_polls_vote], + ["notes/reactions", ep___notes_reactions], + ["notes/reactions/create", ep___notes_reactions_create], + ["notes/reactions/delete", ep___notes_reactions_delete], + ["notes/renotes", ep___notes_renotes], + ["notes/replies", ep___notes_replies], + ["notes/search-by-tag", ep___notes_searchByTag], + ["notes/search", ep___notes_search], + ["notes/show", ep___notes_show], + ["notes/state", ep___notes_state], + ["notes/thread-muting/create", ep___notes_threadMuting_create], + ["notes/thread-muting/delete", ep___notes_threadMuting_delete], + ["notes/timeline", ep___notes_timeline], + ["notes/translate", ep___notes_translate], + ["notes/unrenote", ep___notes_unrenote], + ["notes/user-list-timeline", ep___notes_userListTimeline], + ["notes/watching/create", ep___notes_watching_create], + ["notes/watching/delete", ep___notes_watching_delete], + ["notifications/create", ep___notifications_create], + ["notifications/mark-all-as-read", ep___notifications_markAllAsRead], + ["notifications/read", ep___notifications_read], + ["page-push", ep___pagePush], + ["pages/create", ep___pages_create], + ["pages/delete", ep___pages_delete], + ["pages/featured", ep___pages_featured], + ["pages/like", ep___pages_like], + ["pages/show", ep___pages_show], + ["pages/unlike", ep___pages_unlike], + ["pages/update", ep___pages_update], + ["ping", ep___ping], + ["pinned-users", ep___pinnedUsers], + ["recommended-instances", ep___recommendedInstances], + ["custom-motd", ep___customMOTD], + ["custom-splash-icons", ep___customSplashIcons], + ["latest-version", ep___latestVersion], + ["patrons", ep___patrons], + ["release", ep___release], + ["promo/read", ep___promo_read], + ["request-reset-password", ep___requestResetPassword], + ["reset-db", ep___resetDb], + ["reset-password", ep___resetPassword], + ["server-info", ep___serverInfo], + ["stats", ep___stats], + ["sw/register", ep___sw_register], + ["sw/unregister", ep___sw_unregister], + ["test", ep___test], + ["username/available", ep___username_available], + ["users", ep___users], + ["users/clips", ep___users_clips], + ["users/followers", ep___users_followers], + ["users/following", ep___users_following], + ["users/gallery/posts", ep___users_gallery_posts], + ["users/get-frequently-replied-users", ep___users_getFrequentlyRepliedUsers], + ["users/groups/create", ep___users_groups_create], + ["users/groups/delete", ep___users_groups_delete], + ["users/groups/invitations/accept", ep___users_groups_invitations_accept], + ["users/groups/invitations/reject", ep___users_groups_invitations_reject], + ["users/groups/invite", ep___users_groups_invite], + ["users/groups/joined", ep___users_groups_joined], + ["users/groups/leave", ep___users_groups_leave], + ["users/groups/owned", ep___users_groups_owned], + ["users/groups/pull", ep___users_groups_pull], + ["users/groups/show", ep___users_groups_show], + ["users/groups/transfer", ep___users_groups_transfer], + ["users/groups/update", ep___users_groups_update], + ["users/lists/create", ep___users_lists_create], + ["users/lists/delete", ep___users_lists_delete], + ["users/lists/delete-all", ep___users_lists_delete_all], + ["users/lists/list", ep___users_lists_list], + ["users/lists/pull", ep___users_lists_pull], + ["users/lists/push", ep___users_lists_push], + ["users/lists/show", ep___users_lists_show], + ["users/lists/update", ep___users_lists_update], + ["users/notes", ep___users_notes], + ["users/pages", ep___users_pages], + ["users/reactions", ep___users_reactions], + ["users/recommendation", ep___users_recommendation], + ["users/relation", ep___users_relation], + ["users/report-abuse", ep___users_reportAbuse], + ["users/search-by-username-and-host", ep___users_searchByUsernameAndHost], + ["users/search", ep___users_search], + ["users/show", ep___users_show], + ["users/stats", ep___users_stats], + ["admin/drive-capacity-override", ep___admin_driveCapOverride], + ["fetch-rss", ep___fetchRss], ]; export interface IEndpointMeta { - readonly stability?: 'deprecated' | 'experimental' | 'stable'; + readonly stability?: "deprecated" | "experimental" | "stable"; readonly tags?: ReadonlyArray; @@ -698,7 +704,6 @@ export interface IEndpointMeta { * 省略した場合はリミテーションは無いものとして解釈されます。 */ readonly limit?: { - /** * 複数のエンドポイントでリミットを共有したい場合に指定するキー */ 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 333746f42..6a07dec04 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,69 +1,81 @@ -import define from '../../define.js'; -import { AbuseUserReports } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { AbuseUserReports } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - nullable: false, optional: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + nullable: false, + optional: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - nullable: false, optional: false, - format: 'date-time', + type: "string", + nullable: false, + optional: false, + format: "date-time", }, comment: { - type: 'string', - nullable: false, optional: false, + type: "string", + nullable: false, + optional: false, }, resolved: { - type: 'boolean', - nullable: false, optional: false, + type: "boolean", + nullable: false, + optional: false, example: false, }, reporterId: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, targetUserId: { - type: 'string', - nullable: false, optional: false, - format: 'id', + type: "string", + nullable: false, + optional: false, + format: "id", }, assigneeId: { - type: 'string', - nullable: true, optional: false, - format: 'id', + type: "string", + nullable: true, + optional: false, + format: "id", }, reporter: { - type: 'object', - nullable: false, optional: false, - ref: 'User', + type: "object", + nullable: false, + optional: false, + ref: "User", }, targetUser: { - type: 'object', - nullable: false, optional: false, - ref: 'User', + type: "object", + nullable: false, + optional: false, + ref: "User", }, assignee: { - type: 'object', - nullable: true, optional: true, - ref: 'User', + type: "object", + nullable: true, + optional: true, + ref: "User", }, }, }, @@ -71,36 +83,60 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - 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" }, - forwarded: { type: 'boolean', default: false }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + 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", + }, + 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); + const query = makePaginationQuery( + AbuseUserReports.createQueryBuilder("report"), + ps.sinceId, + ps.untilId, + ); switch (ps.state) { - case 'resolved': query.andWhere('report.resolved = TRUE'); break; - case 'unresolved': query.andWhere('report.resolved = FALSE'); break; + case "resolved": + query.andWhere("report.resolved = TRUE"); + break; + case "unresolved": + query.andWhere("report.resolved = FALSE"); + break; } switch (ps.reporterOrigin) { - case 'local': query.andWhere('report.reporterHost IS NULL'); break; - case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break; + case "local": + query.andWhere("report.reporterHost IS NULL"); + break; + case "remote": + query.andWhere("report.reporterHost IS NOT NULL"); + break; } switch (ps.targetUserOrigin) { - case 'local': query.andWhere('report.targetUserHost IS NULL'); break; - case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break; + case "local": + query.andWhere("report.targetUserHost IS NULL"); + break; + case "remote": + query.andWhere("report.targetUserHost IS NOT NULL"); + break; } const reports = await query.take(ps.limit).getMany(); 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 5f8921999..fc1f42fe7 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -1,40 +1,43 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { signup } from '../../../common/signup.js'; -import { IsNull } from 'typeorm'; +import define from "../../../define.js"; +import { Users } from "@/models/index.js"; +import { signup } from "../../../common/signup.js"; +import { IsNull } from "typeorm"; export const meta = { - tags: ['admin'], + tags: ["admin"], res: { - type: 'object', - optional: false, nullable: false, - ref: 'User', + type: "object", + optional: false, + nullable: false, + ref: "User", properties: { token: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { username: Users.localUsernameSchema, password: Users.passwordSchema, }, - required: ['username', 'password'], + 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 noUsers = + (await Users.countBy({ + host: IsNull(), + })) === 0; + if (!(noUsers || me?.isAdmin)) throw new Error("access denied"); const { account, secret } = await signup({ username: ps.username, 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 629d70058..2397d4056 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -1,22 +1,22 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -24,20 +24,20 @@ 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'); + throw new Error("user not found"); } if (user.isAdmin) { - throw new Error('cannot suspend admin'); + throw new Error("cannot suspend admin"); } if (user.isModerator) { - throw new Error('cannot suspend moderator'); + throw new Error("cannot suspend moderator"); } if (Users.isLocalUser(user)) { // 物理削除する前にDelete activityを送信する - await doPostSuspend(user).catch(e => {}); + await doPostSuspend(user).catch((e) => {}); createDeleteAccountJob(user, { soft: false, @@ -54,6 +54,6 @@ export default define(meta, paramDef, async (ps, me) => { if (Users.isLocalUser(user)) { // Terminate streaming - publishUserEvent(user.id, 'terminate', {}); + publishUserEvent(user.id, "terminate", {}); } }); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts index 07cd984b0..184706cec 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts @@ -1,18 +1,18 @@ -import config from '@/config/index.js'; -import { Meta } from '@/models/entities/meta.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { db } from '@/db/postgre.js'; -import define from '../../../define.js'; +import config from "@/config/index.js"; +import { Meta } from "@/models/entities/meta.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { db } from "@/db/postgre.js"; +import define from "../../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -20,81 +20,86 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { const hostedConfig = config.isManagedHosting; - const hosted = (hostedConfig != null && hostedConfig === true); + const hosted = hostedConfig != null && hostedConfig === true; if (hosted) { const set = {} as Partial; if (config.deepl.managed != null && config.deepl.managed === true) { - if (typeof config.deepl.authKey === 'boolean') { + if (typeof config.deepl.authKey === "boolean") { set.deeplAuthKey = config.deepl.authKey; } - if (typeof config.deepl.isPro === 'boolean') { + if (typeof config.deepl.isPro === "boolean") { set.deeplIsPro = config.deepl.isPro; } } if (config.email.managed != null && config.email.managed === true) { set.enableEmail = true; - if (typeof config.email.address === 'string') { + if (typeof config.email.address === "string") { set.email = config.email.address; } - if (typeof config.email.host === 'string') { + if (typeof config.email.host === "string") { set.smtpHost = config.email.host; } - if (typeof config.email.port === 'number') { + if (typeof config.email.port === "number") { set.smtpPort = config.email.port; } - if (typeof config.email.user === 'string') { + if (typeof config.email.user === "string") { set.smtpUser = config.email.user; } - if (typeof config.email.pass === 'string') { + if (typeof config.email.pass === "string") { set.smtpPass = config.email.pass; } - if (typeof config.email.useImplicitSslTls === 'boolean') { + if (typeof config.email.useImplicitSslTls === "boolean") { set.smtpSecure = config.email.useImplicitSslTls; } } - if (config.objectStorage.managed != null && config.objectStorage.managed === true) { + if ( + config.objectStorage.managed != null && + config.objectStorage.managed === true + ) { set.useObjectStorage = true; - if (typeof config.objectStorage.baseUrl === 'string') { + if (typeof config.objectStorage.baseUrl === "string") { set.objectStorageBaseUrl = config.objectStorage.baseUrl; } - if (typeof config.objectStorage.bucket === 'string') { + if (typeof config.objectStorage.bucket === "string") { set.objectStorageBucket = config.objectStorage.bucket; } - if (typeof config.objectStorage.prefix === 'string') { + if (typeof config.objectStorage.prefix === "string") { set.objectStoragePrefix = config.objectStorage.prefix; } - if (typeof config.objectStorage.endpoint === 'string') { + if (typeof config.objectStorage.endpoint === "string") { set.objectStorageEndpoint = config.objectStorage.endpoint; } - if (typeof config.objectStorage.region === 'string') { + if (typeof config.objectStorage.region === "string") { set.objectStorageRegion = config.objectStorage.region; } - if (typeof config.objectStorage.accessKey === 'string') { + if (typeof config.objectStorage.accessKey === "string") { set.objectStorageAccessKey = config.objectStorage.accessKey; } - if (typeof config.objectStorage.secretKey === 'string') { + if (typeof config.objectStorage.secretKey === "string") { set.objectStorageSecretKey = config.objectStorage.secretKey; } - if (typeof config.objectStorage.useSsl === 'boolean') { + if (typeof config.objectStorage.useSsl === "boolean") { set.objectStorageUseSSL = config.objectStorage.useSsl; } - if (typeof config.objectStorage.connnectOverProxy === 'boolean') { + if (typeof config.objectStorage.connnectOverProxy === "boolean") { set.objectStorageUseProxy = config.objectStorage.connnectOverProxy; } - if (typeof config.objectStorage.setPublicReadOnUpload === 'boolean') { - set.objectStorageSetPublicRead = config.objectStorage.setPublicReadOnUpload; + if (typeof config.objectStorage.setPublicReadOnUpload === "boolean") { + set.objectStorageSetPublicRead = + config.objectStorage.setPublicReadOnUpload; } - if (typeof config.objectStorage.s3ForcePathStyle === 'boolean') { - set.objectStorageS3ForcePathStyle = config.objectStorage.s3ForcePathStyle; + if (typeof config.objectStorage.s3ForcePathStyle === "boolean") { + set.objectStorageS3ForcePathStyle = + config.objectStorage.s3ForcePathStyle; } } if (config.summalyProxyUrl !== undefined) { set.summalyProxy = config.summalyProxyUrl; } - await db.transaction(async transactionalEntityManager => { + await db.transaction(async (transactionalEntityManager) => { const metas = await transactionalEntityManager.find(Meta, { order: { - id: 'DESC', + id: "DESC", }, }); @@ -106,7 +111,7 @@ export default define(meta, paramDef, async (ps, me) => { await transactionalEntityManager.save(Meta, set); } }); - insertModerationLog(me, 'updateMeta'); + insertModerationLog(me, "updateMeta"); } return hosted; }); 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 ab2c50b50..be182900f 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -1,26 +1,34 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import define from "../../../define.js"; +import { Ads } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - url: { type: 'string', minLength: 1 }, - memo: { type: 'string' }, - place: { type: 'string' }, - priority: { type: 'string' }, - ratio: { type: 'integer' }, - expiresAt: { type: 'integer' }, - imageUrl: { type: 'string', minLength: 1 }, + url: { type: "string", minLength: 1 }, + memo: { type: "string" }, + place: { type: "string" }, + priority: { type: "string" }, + ratio: { type: "integer" }, + expiresAt: { type: "integer" }, + imageUrl: { type: "string", minLength: 1 }, }, - required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'imageUrl'], + required: [ + "url", + "memo", + "place", + "priority", + "ratio", + "expiresAt", + "imageUrl", + ], } as const; // eslint-disable-next-line import/no-default-export 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 0ead2be00..dd710a447 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -1,28 +1,28 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { Ads } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchAd: { - message: 'No such ad.', - code: 'NO_SUCH_AD', - id: 'ccac9863-3a03-416e-b899-8a64041118b1', + message: "No such ad.", + code: "NO_SUCH_AD", + id: "ccac9863-3a03-416e-b899-8a64041118b1", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, + id: { type: "string", format: "misskey:id" }, }, - required: ['id'], + required: ["id"], } as const; // eslint-disable-next-line import/no-default-export 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 74f154f27..441a4b85d 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -1,28 +1,31 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import define from "../../../define.js"; +import { Ads } from "@/models/index.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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() }); + const query = makePaginationQuery( + Ads.createQueryBuilder("ad"), + ps.sinceId, + ps.untilId, + ).andWhere("ad.expiresAt > :now", { now: new Date() }); const ads = await query.take(ps.limit).getMany(); 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 650f8670e..28fc12104 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -1,35 +1,44 @@ -import define from '../../../define.js'; -import { Ads } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { Ads } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchAd: { - message: 'No such ad.', - code: 'NO_SUCH_AD', - id: 'b7aa1727-1354-47bc-a182-3a9c3973d300', + message: "No such ad.", + code: "NO_SUCH_AD", + id: "b7aa1727-1354-47bc-a182-3a9c3973d300", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, - memo: { type: 'string' }, - url: { type: 'string', minLength: 1 }, - imageUrl: { type: 'string', minLength: 1 }, - place: { type: 'string' }, - priority: { type: 'string' }, - ratio: { type: 'integer' }, - expiresAt: { type: 'integer' }, + id: { type: "string", format: "misskey:id" }, + memo: { type: "string" }, + url: { type: "string", minLength: 1 }, + imageUrl: { type: "string", minLength: 1 }, + place: { type: "string" }, + priority: { type: "string" }, + ratio: { type: "integer" }, + expiresAt: { type: "integer" }, }, - required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt'], + required: [ + "id", + "memo", + "url", + "imageUrl", + "place", + "priority", + "ratio", + "expiresAt", + ], } as const; // eslint-disable-next-line import/no-default-export 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 33076b6d3..b521228fd 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -1,57 +1,64 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import define from "../../../define.js"; +import { Announcements } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, updatedAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, title: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, text: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, imageUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - title: { type: 'string', minLength: 1 }, - text: { type: 'string', minLength: 1 }, - imageUrl: { type: 'string', nullable: true, minLength: 1 }, + title: { type: "string", minLength: 1 }, + text: { type: "string", minLength: 1 }, + imageUrl: { type: "string", nullable: true, minLength: 1 }, }, - required: ['title', 'text', 'imageUrl'], + required: ["title", "text", "imageUrl"], } as const; // eslint-disable-next-line import/no-default-export @@ -63,7 +70,10 @@ export default define(meta, paramDef, async (ps) => { title: ps.title, text: ps.text, imageUrl: ps.imageUrl, - }).then(x => Announcements.findOneByOrFail(x.identifiers[0])); + }).then((x) => Announcements.findOneByOrFail(x.identifiers[0])); - return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null }); + 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 c17765f4f..472a1073b 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -1,28 +1,28 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { Announcements } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchAnnouncement: { - message: 'No such announcement.', - code: 'NO_SUCH_ANNOUNCEMENT', - id: 'ecad8040-a276-4e85-bda9-015a708d291e', + message: "No such announcement.", + code: "NO_SUCH_ANNOUNCEMENT", + id: "ecad8040-a276-4e85-bda9-015a708d291e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, + id: { type: "string", format: "misskey:id" }, }, - required: ['id'], + required: ["id"], } as const; // eslint-disable-next-line import/no-default-export 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 7a5758d75..699343eff 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,52 +1,61 @@ -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 { Announcements, AnnouncementReads } from "@/models/index.js"; +import type { Announcement } from "@/models/entities/announcement.js"; +import define from "../../../define.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, updatedAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, text: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, title: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, imageUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, reads: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, @@ -54,30 +63,37 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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); + const query = makePaginationQuery( + Announcements.createQueryBuilder("announcement"), + ps.sinceId, + ps.untilId, + ); const announcements = await query.take(ps.limit).getMany(); const reads = new Map(); for (const announcement of announcements) { - reads.set(announcement, await AnnouncementReads.countBy({ - announcementId: announcement.id, - })); + reads.set( + announcement, + await AnnouncementReads.countBy({ + announcementId: announcement.id, + }), + ); } - return announcements.map(announcement => ({ + return announcements.map((announcement) => ({ id: announcement.id, createdAt: announcement.createdAt.toISOString(), updatedAt: announcement.updatedAt?.toISOString() ?? null, 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 61ce106d8..cd47e496a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -1,31 +1,31 @@ -import define from '../../../define.js'; -import { Announcements } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { Announcements } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchAnnouncement: { - message: 'No such announcement.', - code: 'NO_SUCH_ANNOUNCEMENT', - id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc', + message: "No such announcement.", + code: "NO_SUCH_ANNOUNCEMENT", + id: "d3aae5a7-6372-4cb4-b61c-f511ffc2d7cc", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, - title: { type: 'string', minLength: 1 }, - text: { type: 'string', minLength: 1 }, - imageUrl: { type: 'string', nullable: true, minLength: 1 }, + id: { type: "string", format: "misskey:id" }, + title: { type: "string", minLength: 1 }, + text: { type: "string", minLength: 1 }, + imageUrl: { type: "string", nullable: true, minLength: 1 }, }, - required: ['id', 'title', 'text', 'imageUrl'], + required: ["id", "title", "text", "imageUrl"], } as const; // eslint-disable-next-line import/no-default-export 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 2d7ef2f23..78c94ac50 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -1,23 +1,22 @@ -import { Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import { Users } from "@/models/index.js"; +import { deleteAccount } from "@/services/delete-account.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, - res: { - }, + res: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export 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 dc1976624..2c93ecd07 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,20 +1,20 @@ -import define from '../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import define from "../../define.js"; +import { deleteFile } from "@/services/drive/delete-file.js"; +import { DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export 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 a4b29770e..df6c17ba9 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,21 +1,21 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - overrideMb: { type: 'number', nullable: true }, + userId: { type: "string", format: "misskey:id" }, + overrideMb: { type: "number", nullable: true }, }, - required: ['userId', 'overrideMb'], + required: ["userId", "overrideMb"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,12 +23,12 @@ 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'); + throw new Error("user not found"); } if (!Users.isLocalUser(user)) { - throw new Error('user is not local user'); - } + throw new Error("user is not local user"); + } /*if (user.isAdmin) { throw new Error('cannot suspend admin'); @@ -41,7 +41,7 @@ export default define(meta, paramDef, async (ps, me) => { driveCapacityOverrideMb: ps.overrideMb, }); - insertModerationLog(me, 'change-drive-capacity-override', { + 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 bab149532..aa8dc9fc2 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,15 +1,15 @@ -import define from '../../../define.js'; -import { createCleanRemoteFilesJob } from '@/queue/index.js'; +import define from "../../../define.js"; +import { createCleanRemoteFilesJob } from "@/queue/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 3db942e6c..ba1402646 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -1,17 +1,17 @@ -import { IsNull } from 'typeorm'; -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import { IsNull } from "typeorm"; +import define from "../../../define.js"; +import { deleteFile } from "@/services/drive/delete-file.js"; +import { DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 ba32aac43..fc494e286 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -1,38 +1,48 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { DriveFiles } from "@/models/index.js"; +import define from "../../../define.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: false, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id', nullable: true }, - type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id", nullable: true }, + type: { + type: "string", + nullable: true, + pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1), + }, + origin: { + type: "string", + enum: ["combined", "local", "remote"], + default: "local", + }, hostname: { - type: 'string', + type: "string", nullable: true, default: null, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", }, }, required: [], @@ -40,31 +50,41 @@ export const paramDef = { // 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); + const query = makePaginationQuery( + DriveFiles.createQueryBuilder("file"), + ps.sinceId, + ps.untilId, + ); if (ps.userId) { - query.andWhere('file.userId = :userId', { userId: 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.origin === "local") { + query.andWhere("file.userHost IS NULL"); + } else if (ps.origin === "remote") { + query.andWhere("file.userHost IS NOT NULL"); } if (ps.hostname) { - query.andWhere('file.userHost = :hostname', { hostname: ps.hostname }); + query.andWhere("file.userHost = :hostname", { hostname: ps.hostname }); } } if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + if (ps.type.endsWith("/*")) { + query.andWhere("file.type like :type", { + type: `${ps.type.replace("/*", "/")}%`, + }); } else { - query.andWhere('file.type = :type', { type: ps.type }); + query.andWhere("file.type = :type", { type: ps.type }); } } const files = await query.take(ps.limit).getMany(); - return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true }); + return await DriveFiles.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 e9117a23c..514c19c54 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,192 +1,225 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { DriveFiles } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'caf3ca38-c6e5-472e-a30c-b05377dcc240', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "caf3ca38-c6e5-472e-a30c-b05377dcc240", }, }, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, userId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, userHost: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', + type: "string", + optional: false, + nullable: true, + description: "The local host is represented with `null`.", }, md5: { - type: 'string', - optional: false, nullable: false, - format: 'md5', - example: '15eca7fba0480996e2245f5185bf39f2', + type: "string", + optional: false, + nullable: false, + format: "md5", + example: "15eca7fba0480996e2245f5185bf39f2", }, name: { - type: 'string', - optional: false, nullable: false, - example: 'lenna.jpg', + type: "string", + optional: false, + nullable: false, + example: "lenna.jpg", }, type: { - type: 'string', - optional: false, nullable: false, - example: 'image/jpeg', + type: "string", + optional: false, + nullable: false, + example: "image/jpeg", }, size: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, example: 51469, }, comment: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, blurhash: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, properties: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { width: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, example: 1280, }, height: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, example: 720, }, avgColor: { - type: 'string', - optional: true, nullable: false, - example: 'rgb(40,65,87)', + type: "string", + optional: true, + nullable: false, + example: "rgb(40,65,87)", }, }, }, storedInternal: { - type: 'boolean', - optional: false, nullable: true, + type: "boolean", + optional: false, + nullable: true, example: true, }, url: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, thumbnailUrl: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, webpublicUrl: { - type: 'string', - optional: false, nullable: true, - format: 'url', + type: "string", + optional: false, + nullable: true, + format: "url", }, accessKey: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, thumbnailAccessKey: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, webpublicAccessKey: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, uri: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, src: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, folderId: { - type: 'string', - optional: false, nullable: true, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", }, isSensitive: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isLink: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", anyOf: [ { properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], }, { properties: { - url: { type: 'string' }, + url: { type: "string" }, }, - required: ['url'], + required: ["url"], }, ], } 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, - }], - }); + const file = ps.fileId + ? await DriveFiles.findOneBy({ id: ps.fileId }) + : await DriveFiles.findOne({ + where: [ + { + url: ps.url, + }, + { + thumbnailUrl: ps.url, + }, + { + webpublicUrl: ps.url, + }, + ], + }); if (file == null) { throw new ApiError(meta.errors.noSuchFile); } if (!me.isAdmin) { - delete file.requestIp; - delete file.requestHeaders; + file.requestIp = undefined; + file.requestHeaders = undefined; } 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 232fbbd57..601750126 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,27 +1,34 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - ids: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, - aliases: { type: 'array', items: { - type: 'string', - } }, + ids: { + type: "array", + items: { + type: "string", + format: "misskey:id", + }, + }, + aliases: { + type: "array", + items: { + type: "string", + }, + }, }, - required: ['ids', 'aliases'], + required: ["ids", "aliases"], } as const; // eslint-disable-next-line import/no-default-export @@ -37,5 +44,5 @@ export default define(meta, paramDef, async (ps) => { }); } - await 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 67349c24e..57768419f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,33 +1,33 @@ -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 rndstr from 'rndstr'; -import { publishBroadcastStream } from '@/services/stream.js'; -import { db } from '@/db/postgre.js'; +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 rndstr from "rndstr"; +import { publishBroadcastStream } from "@/services/stream.js"; +import { db } from "@/db/postgre.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchFile: { - message: 'No such file.', - code: 'MO_SUCH_FILE', - id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf', + message: "No such file.", + code: "MO_SUCH_FILE", + id: "fc46b5a4-6b92-4c33-ac66-b806659bb5cf", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export @@ -36,7 +36,9 @@ export default define(meta, paramDef, async (ps, me) => { if (file == null) throw new ApiError(meta.errors.noSuchFile); - const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; + const name = file.name.split(".")[0].match(/^[a-z0-9_]+$/) + ? file.name.split(".")[0] + : `_${rndstr("a-z0-9", 8)}_`; const emoji = await Emojis.insert({ id: genId(), @@ -48,15 +50,15 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: file.url, publicUrl: file.webpublicUrl ?? file.url, type: file.webpublicType ?? file.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); + }).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); - await db.queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(["meta_emojis"]); - publishBroadcastStream('emojiAdded', { + publishBroadcastStream("emojiAdded", { emoji: await Emojis.pack(emoji.id), }); - insertModerationLog(me, 'addEmoji', { + insertModerationLog(me, "addEmoji", { emojiId: 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 7010ade0d..bc3bd16a8 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,45 +1,47 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.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'; +import define from "../../../define.js"; +import { Emojis } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { ApiError } from "../../../error.js"; +import type { 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'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchEmoji: { - message: 'No such emoji.', - code: 'NO_SUCH_EMOJI', - id: 'e2785b66-dca3-4087-9cac-b93c541cc425', + message: "No such emoji.", + code: "NO_SUCH_EMOJI", + id: "e2785b66-dca3-4087-9cac-b93c541cc425", }, }, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - emojiId: { type: 'string', format: 'misskey:id' }, + emojiId: { type: "string", format: "misskey:id" }, }, - required: ['emojiId'], + required: ["emojiId"], } as const; // eslint-disable-next-line import/no-default-export @@ -54,7 +56,11 @@ export default define(meta, paramDef, async (ps, me) => { try { // Create file - driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); + driveFile = await uploadFromUrl({ + url: emoji.originalUrl, + user: null, + force: true, + }); } catch (e) { throw new ApiError(); } @@ -68,11 +74,11 @@ export default define(meta, paramDef, async (ps, me) => { originalUrl: driveFile.url, publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, - }).then(x => Emojis.findOneByOrFail(x.identifiers[0])); + }).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); - await db.queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(["meta_emojis"]); - publishBroadcastStream('emojiAdded', { + publishBroadcastStream("emojiAdded", { emoji: await Emojis.pack(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 93a6c4e4e..471f38250 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,25 +1,29 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - ids: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, + ids: { + type: "array", + items: { + type: "string", + format: "misskey:id", + }, + }, }, - required: ['ids'], + required: ["ids"], } as const; // eslint-disable-next-line import/no-default-export @@ -30,10 +34,10 @@ export default define(meta, paramDef, async (ps, me) => { for (const emoji of emojis) { await Emojis.delete(emoji.id); - - await db.queryResultCache!.remove(['meta_emojis']); - - insertModerationLog(me, 'deleteEmoji', { + + await db.queryResultCache!.remove(["meta_emojis"]); + + 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 67dbf28d8..abd591620 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -1,30 +1,30 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import define from "../../../define.js"; +import { Emojis } from "@/models/index.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { ApiError } from "../../../error.js"; +import { db } from "@/db/postgre.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchEmoji: { - message: 'No such emoji.', - code: 'NO_SUCH_EMOJI', - id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2', + message: "No such emoji.", + code: "NO_SUCH_EMOJI", + id: "be83669b-773a-44b7-b1f8-e5e5170ac3c2", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, + id: { type: "string", format: "misskey:id" }, }, - required: ['id'], + required: ["id"], } as const; // eslint-disable-next-line import/no-default-export @@ -35,9 +35,9 @@ export default define(meta, paramDef, async (ps, me) => { await Emojis.delete(emoji.id); - await db.queryResultCache!.remove(['meta_emojis']); + await db.queryResultCache!.remove(["meta_emojis"]); - insertModerationLog(me, 'deleteEmoji', { + 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 3f03dc2da..78a062eee 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 define from "../../../define.js"; +import { createImportCustomEmojisJob } from "@/queue/index.js"; +import ms from "ms"; export const meta = { secure: true, @@ -9,11 +9,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export 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 d16689a28..2070b1413 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,50 +1,59 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, aliases: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, category: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', + type: "string", + optional: false, + nullable: true, + description: "The local host is represented with `null`.", }, url: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, @@ -52,40 +61,41 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - query: { type: 'string', nullable: true, default: null }, + query: { type: "string", nullable: true, default: null }, host: { - type: 'string', + type: "string", nullable: true, default: null, - description: 'Use `null` to represent the local host.', + description: "Use `null` to represent the local host.", }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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); + const q = makePaginationQuery( + Emojis.createQueryBuilder("emoji"), + ps.sinceId, + ps.untilId, + ); if (ps.host == null) { - q.andWhere(`emoji.host IS NOT NULL`); + q.andWhere("emoji.host IS NOT NULL"); } else { - q.andWhere(`emoji.host = :host`, { host: toPuny(ps.host) }); + q.andWhere("emoji.host = :host", { host: toPuny(ps.host) }); } if (ps.query) { - q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' }); + q.andWhere("emoji.name like :query", { query: `%${ps.query}%` }); } - const emojis = await q - .orderBy('emoji.id', 'DESC') - .take(ps.limit) - .getMany(); + const emojis = await q.orderBy("emoji.id", "DESC").take(ps.limit).getMany(); return Emojis.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 6192978fa..46b551306 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -1,50 +1,59 @@ -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 define from "../../../define.js"; +import { Emojis } from "@/models/index.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; +import type { Emoji } from "@/models/entities/emoji.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, aliases: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, category: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, host: { - type: 'null', + type: "null", optional: false, - description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', + description: + "The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.", }, url: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, @@ -52,20 +61,23 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - query: { type: 'string', nullable: true, default: null }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + query: { type: "string", nullable: true, default: null }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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`); + const q = makePaginationQuery( + Emojis.createQueryBuilder("emoji"), + ps.sinceId, + ps.untilId, + ).andWhere("emoji.host IS NULL"); let emojis: Emoji[]; @@ -75,10 +87,12 @@ export default define(meta, paramDef, async (ps) => { emojis = await q.getMany(); - emojis = emojis.filter(emoji => - emoji.name.includes(ps.query!) || - emoji.aliases.some(a => a.includes(ps.query!)) || - emoji.category?.includes(ps.query!)); + 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 { 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 a4da40fff..408ee8d2e 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,27 +1,34 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - ids: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, - aliases: { type: 'array', items: { - type: 'string', - } }, + ids: { + type: "array", + items: { + type: "string", + format: "misskey:id", + }, + }, + aliases: { + type: "array", + items: { + type: "string", + }, + }, }, - required: ['ids', 'aliases'], + required: ["ids", "aliases"], } as const; // eslint-disable-next-line import/no-default-export @@ -33,9 +40,9 @@ export default define(meta, paramDef, async (ps) => { for (const emoji of emojis) { await Emojis.update(emoji.id, { updatedAt: new Date(), - aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + aliases: emoji.aliases.filter((x) => !ps.aliases.includes(x)), }); } - await 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 ae3b190f4..a4b3db1e3 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,37 +1,47 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - ids: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, - aliases: { type: 'array', items: { - type: 'string', - } }, + ids: { + type: "array", + items: { + type: "string", + format: "misskey:id", + }, + }, + aliases: { + type: "array", + items: { + type: "string", + }, + }, }, - required: ['ids', 'aliases'], + required: ["ids", "aliases"], } as const; // 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, - }); + await Emojis.update( + { + id: In(ps.ids), + }, + { + updatedAt: new Date(), + aliases: ps.aliases, + }, + ); - await db.queryResultCache!.remove(['meta_emojis']); + await 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 cff58d617..8a0e6c589 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,39 +1,46 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - ids: { type: 'array', items: { - type: 'string', format: 'misskey:id', - } }, + ids: { + type: "array", + items: { + type: "string", + format: "misskey:id", + }, + }, category: { - type: 'string', + type: "string", nullable: true, - description: 'Use `null` to reset the category.', + description: "Use `null` to reset the category.", }, }, - required: ['ids'], + required: ["ids"], } as const; // 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, - }); + await Emojis.update( + { + id: In(ps.ids), + }, + { + updatedAt: new Date(), + category: ps.category, + }, + ); - await db.queryResultCache!.remove(['meta_emojis']); + await 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 5b547b3b7..8024e77bc 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -1,38 +1,41 @@ -import define from '../../../define.js'; -import { Emojis } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; -import { db } from '@/db/postgre.js'; +import define from "../../../define.js"; +import { Emojis } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; +import { db } from "@/db/postgre.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchEmoji: { - message: 'No such emoji.', - code: 'NO_SUCH_EMOJI', - id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8', + message: "No such emoji.", + code: "NO_SUCH_EMOJI", + id: "684dec9d-a8c2-4364-9aa8-456c49cb1dc8", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - id: { type: 'string', format: 'misskey:id' }, - name: { type: 'string' }, + id: { type: "string", format: "misskey:id" }, + name: { type: "string" }, category: { - type: 'string', + type: "string", nullable: true, - description: 'Use `null` to reset the category.', + description: "Use `null` to reset the category.", + }, + aliases: { + type: "array", + items: { + type: "string", + }, }, - aliases: { type: 'array', items: { - type: 'string', - } }, }, - required: ['id', 'name', 'aliases'], + required: ["id", "name", "aliases"], } as const; // eslint-disable-next-line import/no-default-export @@ -48,5 +51,5 @@ export default define(meta, paramDef, async (ps) => { aliases: ps.aliases, }); - await db.queryResultCache!.remove(['meta_emojis']); + await 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 da5420147..3f0a48839 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,20 +1,20 @@ -import define from '../../../define.js'; -import { deleteFile } from '@/services/drive/delete-file.js'; -import { DriveFiles } from '@/models/index.js'; +import define from "../../../define.js"; +import { deleteFile } from "@/services/drive/delete-file.js"; +import { DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, + host: { type: "string" }, }, - required: ['host'], + required: ["host"], } as const; // eslint-disable-next-line import/no-default-export 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 cb2be5ab3..236072bb2 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,21 +1,21 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, + host: { type: "string" }, }, - required: ['host'], + required: ["host"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,7 +23,7 @@ export default define(meta, paramDef, async (ps, me) => { const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { - throw new Error('instance not found'); + throw new Error("instance not found"); } 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 b7ee27db6..a436ecda6 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,20 +1,20 @@ -import define from '../../../define.js'; -import deleteFollowing from '@/services/following/delete.js'; -import { Followings, Users } from '@/models/index.js'; +import define from "../../../define.js"; +import deleteFollowing from "@/services/following/delete.js"; +import { Followings, Users } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, + host: { type: "string" }, }, - required: ['host'], + required: ["host"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,10 +23,14 @@ export default define(meta, paramDef, async (ps, me) => { followerHost: ps.host, }); - const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOneByOrFail({ id: f.followerId }), - Users.findOneByOrFail({ id: f.followeeId }), - ]))); + 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]); 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 278131fb3..2ae5a7693 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,21 +1,21 @@ -import define from '../../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import define from "../../../define.js"; +import { Instances } from "@/models/index.js"; +import { toPuny } from "@/misc/convert-host.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, - isSuspended: { type: 'boolean' }, + host: { type: "string" }, + isSuspended: { type: "boolean" }, }, - required: ['host', 'isSuspended'], + required: ["host", "isSuspended"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,10 +23,13 @@ export default define(meta, paramDef, async (ps, me) => { const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); if (instance == null) { - throw new Error('instance not found'); + throw new Error("instance not found"); } - Instances.update({ host: toPuny(ps.host) }, { - isSuspended: ps.isSuspended, - }); + Instances.update( + { host: 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 dd16473f3..773f56c98 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,23 +1,23 @@ -import define from '../../define.js'; -import { db } from '@/db/postgre.js'; +import define from "../../define.js"; +import { db } from "@/db/postgre.js"; export const meta = { requireCredential: true, requireModerator: true, - tags: ['admin'], + tags: ["admin"], } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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; }[]; + 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); } 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 aca2540fd..79c541540 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,15 +1,16 @@ -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { db } from "@/db/postgre.js"; +import define from "../../define.js"; export const meta = { requireCredential: true, requireModerator: true, - tags: ['admin'], + tags: ["admin"], res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, example: { migrations: { count: 66, @@ -20,22 +21,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - const sizes = await - db.query(` + const sizes = await 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; + .then((recs) => { + const res = {} as Record; for (const rec of recs) { res[rec.table] = { count: parseInt(rec.count, 10), 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 e8b9cb3b0..061f4153c 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,30 +1,30 @@ -import { UserIps } from '@/models/index.js'; -import define from '../../define.js'; +import { UserIps } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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' }, + order: { createdAt: "DESC" }, take: 30, }); - return ips.map(x => ({ + 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 7e950cf87..eaa74cf37 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -1,22 +1,24 @@ -import rndstr from 'rndstr'; -import define from '../../define.js'; -import { RegistrationTickets } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import rndstr from "rndstr"; +import define from "../../define.js"; +import { RegistrationTickets } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { code: { - type: 'string', - optional: false, nullable: false, - example: '2ERUA5VR', + type: "string", + optional: false, + nullable: false, + example: "2ERUA5VR", maxLength: 8, minLength: 8, }, @@ -25,7 +27,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -34,7 +36,7 @@ export const paramDef = { 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) + chars: "2-9A-HJ-NP-Z", // [0-9A-Z] w/o [01IO] (32 patterns) }); await RegistrationTickets.insert({ diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c6b6a3cd9..e9ea48a39 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,381 +1,467 @@ -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../../define.js'; +import config from "@/config/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import define from "../../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: true, requireAdmin: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { driveCapacityPerLocalUserMb: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, driveCapacityPerRemoteUserMb: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, cacheRemoteFiles: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, emailRequiredForSignup: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableHcaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hcaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, enableRecaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, recaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, swPublickey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, mascotImageUrl: { - type: 'string', - optional: false, nullable: false, - default: '/assets/ai.png', + type: "string", + optional: false, + nullable: false, + default: "/assets/ai.png", }, bannerUrl: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, errorImageUrl: { - type: 'string', - optional: false, nullable: false, - default: 'https://xn--931a.moe/aiart/yubitun.png', + type: "string", + optional: false, + nullable: false, + default: "https://xn--931a.moe/aiart/yubitun.png", }, iconUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, maxNoteTextLength: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, emojis: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, aliases: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, category: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, host: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, url: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, }, }, }, ads: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { place: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, url: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, imageUrl: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, }, }, }, enableEmail: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableTwitterIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableGithubIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableDiscordIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableServiceWorker: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, translatorAvailable: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, proxyAccountName: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, recommendedInstances: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, pinnedUsers: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, customMOTD: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, customSplashIcons: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, hiddenTags: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, blockedHosts: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, allowedHosts: { - type: 'array', - optional: true, nullable: false, + type: "array", + optional: true, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, privateMode: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, secureMode: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hcaptchaSecretKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, recaptchaSecretKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, sensitiveMediaDetection: { - type: 'string', - optional: true, nullable: false, + type: "string", + optional: true, + nullable: false, }, sensitiveMediaDetectionSensitivity: { - type: 'string', - optional: true, nullable: false, + type: "string", + optional: true, + nullable: false, }, setSensitiveFlagAutomatically: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, enableSensitiveMediaDetectionForVideos: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, proxyAccountId: { - type: 'string', - optional: true, nullable: true, - format: 'id', + type: "string", + optional: true, + nullable: true, + format: "id", }, twitterConsumerKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, twitterConsumerSecret: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, githubClientId: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, githubClientSecret: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, discordClientId: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, discordClientSecret: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, summaryProxy: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, email: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, smtpSecure: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, smtpHost: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, smtpPort: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, smtpUser: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, smtpPass: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, swPrivateKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, useObjectStorage: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, objectStorageBaseUrl: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStorageBucket: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStoragePrefix: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStorageEndpoint: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStorageRegion: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStoragePort: { - type: 'number', - optional: true, nullable: true, + type: "number", + optional: true, + nullable: true, }, objectStorageAccessKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStorageSecretKey: { - type: 'string', - optional: true, nullable: true, + type: "string", + optional: true, + nullable: true, }, objectStorageUseSSL: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, objectStorageUseProxy: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, objectStorageSetPublicRead: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, enableIpLogging: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, enableActiveEmailValidation: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, defaultReaction: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', - properties: { - }, + type: "object", + properties: {}, required: [], } as const; @@ -438,9 +524,11 @@ export default define(meta, paramDef, async (ps, me) => { hcaptchaSecretKey: instance.hcaptchaSecretKey, recaptchaSecretKey: instance.recaptchaSecretKey, sensitiveMediaDetection: instance.sensitiveMediaDetection, - sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity, + sensitiveMediaDetectionSensitivity: + instance.sensitiveMediaDetectionSensitivity, setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically, - enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, + enableSensitiveMediaDetectionForVideos: + instance.enableSensitiveMediaDetectionForVideos, proxyAccountId: instance.proxyAccountId, twitterConsumerKey: instance.twitterConsumerKey, twitterConsumerSecret: instance.twitterConsumerSecret, 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 7b209c2d9..a65a2c26a 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -1,20 +1,20 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../../define.js"; +import { Users } from "@/models/index.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -22,16 +22,19 @@ export default define(meta, paramDef, async (ps) => { const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { - throw new Error('user not found'); + throw new Error("user not found"); } if (user.isAdmin) { - throw new Error('cannot mark as moderator if admin user'); + throw new Error("cannot mark as moderator if admin user"); } await Users.update(user.id, { isModerator: true, }); - publishInternalEvent('userChangeModeratorState', { id: 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 a01e9f3c6..11f6945b6 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,20 +1,20 @@ -import define from '../../../define.js'; -import { Users } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../../define.js"; +import { Users } from "@/models/index.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -22,12 +22,15 @@ export default define(meta, paramDef, async (ps) => { const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { - throw new Error('user not found'); + throw new Error("user not found"); } await Users.update(user.id, { isModerator: false, }); - publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false }); + 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 b5142fcf0..7bb29ac6a 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -1,42 +1,43 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; -import { PromoNotes } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getNote } from "../../../common/getters.js"; +import { PromoNotes } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'ee449fbe-af2a-453b-9cae-cf2fe7c895fc', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "ee449fbe-af2a-453b-9cae-cf2fe7c895fc", }, alreadyPromoted: { - message: 'The note has already promoted.', - code: 'ALREADY_PROMOTED', - id: 'ae427aa2-7a41-484f-a18c-2c1104051604', + message: "The note has already promoted.", + code: "ALREADY_PROMOTED", + id: "ae427aa2-7a41-484f-a18c-2c1104051604", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - expiresAt: { type: 'integer' }, + noteId: { type: "string", format: "misskey:id" }, + expiresAt: { type: "integer" }, }, - required: ['noteId', 'expiresAt'], + required: ["noteId", "expiresAt"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); 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 8f015c280..621fcdf54 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -1,16 +1,16 @@ -import define from '../../../define.js'; -import { destroy } from '@/queue/index.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; +import define from "../../../define.js"; +import { destroy } from "@/queue/index.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -19,5 +19,5 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { destroy(); - insertModerationLog(me, 'clearQueue'); + 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 70f7d77de..be2423c42 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,53 +1,52 @@ -import { deliverQueue } from '@/queue/queues.js'; -import { URL } from 'node:url'; -import define from '../../../define.js'; +import { deliverQueue } from "@/queue/queues.js"; +import { URL } from "node:url"; +import define from "../../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { anyOf: [ { - type: 'string', + type: "string", }, { - type: 'number', + type: "number", }, ], }, }, - example: [[ - 'example.com', - 12, - ]], + example: [["example.com", 12]], }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const jobs = await deliverQueue.getJobs(['delayed']); + 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]++; + if (res.find((x) => x[0] === host)) { + res.find((x) => x[0] === host)![1]++; } else { res.push([host, 1]); } 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 2235ce8f9..6260ae7d8 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,53 +1,52 @@ -import { URL } from 'node:url'; -import define from '../../../define.js'; -import { inboxQueue } from '@/queue/queues.js'; +import { URL } from "node:url"; +import define from "../../../define.js"; +import { inboxQueue } from "@/queue/queues.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { anyOf: [ { - type: 'string', + type: "string", }, { - type: 'number', + type: "number", }, ], }, }, - example: [[ - 'example.com', - 12, - ]], + example: [["example.com", 12]], }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const jobs = await inboxQueue.getJobs(['delayed']); + 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]++; + if (res.find((x) => x[0] === host)) { + res.find((x) => x[0] === host)![1]++; } else { res.push([host, 1]); } 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 988b5a5e3..0e3801aa1 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -1,38 +1,48 @@ -import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '@/queue/queues.js'; -import define from '../../../define.js'; +import { + deliverQueue, + inboxQueue, + dbQueue, + objectStorageQueue, +} from "@/queue/queues.js"; +import define from "../../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { deliver: { - optional: false, nullable: false, - ref: 'QueueCount', + optional: false, + nullable: false, + ref: "QueueCount", }, inbox: { - optional: false, nullable: false, - ref: 'QueueCount', + optional: false, + nullable: false, + ref: "QueueCount", }, db: { - optional: false, nullable: false, - ref: 'QueueCount', + optional: false, + nullable: false, + ref: "QueueCount", }, objectStorage: { - optional: false, nullable: false, - ref: 'QueueCount', + optional: false, + nullable: false, + ref: "QueueCount", }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 4384e20f0..7bb120ea1 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -1,63 +1,63 @@ -import { URL } from 'node:url'; -import define from '../../../define.js'; -import { addRelay } from '@/services/relay.js'; -import { ApiError } from '../../../error.js'; +import { URL } from "node:url"; +import define from "../../../define.js"; +import { addRelay } from "@/services/relay.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, errors: { invalidUrl: { - message: 'Invalid URL', - code: 'INVALID_URL', - id: 'fb8c92d3-d4e5-44e7-b3d4-800d5cef8b2c', + message: "Invalid URL", + code: "INVALID_URL", + id: "fb8c92d3-d4e5-44e7-b3d4-800d5cef8b2c", }, }, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, inbox: { - description: 'URL of the inbox, must be a https scheme URL', - type: 'string', - optional: false, nullable: false, - format: 'url', + description: "URL of the inbox, must be a https scheme URL", + type: "string", + optional: false, + nullable: false, + format: "url", }, status: { - type: 'string', - optional: false, nullable: false, - default: 'requesting', - enum: [ - 'requesting', - 'accepted', - 'rejected', - ], + type: "string", + optional: false, + nullable: false, + default: "requesting", + enum: ["requesting", "accepted", "rejected"], }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - inbox: { type: 'string' }, + inbox: { type: "string" }, }, - required: ['inbox'], + required: ["inbox"], } 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 new Error('https only'); + if (new URL(ps.inbox).protocol !== "https:") throw new Error("https only"); } catch { throw new ApiError(meta.errors.invalidUrl); } 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 89ec651e6..030a665b4 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -1,38 +1,39 @@ -import define from '../../../define.js'; -import { listRelay } from '@/services/relay.js'; +import define from "../../../define.js"; +import { listRelay } from "@/services/relay.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, inbox: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, status: { - type: 'string', - optional: false, nullable: false, - default: 'requesting', - enum: [ - 'requesting', - 'accepted', - 'rejected', - ], + type: "string", + optional: false, + nullable: false, + default: "requesting", + enum: ["requesting", "accepted", "rejected"], }, }, }, @@ -40,7 +41,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 b59cf72c5..ec3a265e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -1,19 +1,19 @@ -import define from '../../../define.js'; -import { removeRelay } from '@/services/relay.js'; +import define from "../../../define.js"; +import { removeRelay } from "@/services/relay.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - inbox: { type: 'string' }, + inbox: { type: "string" }, }, - required: ['inbox'], + required: ["inbox"], } as const; // eslint-disable-next-line import/no-default-export 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 be4c2dcee..7d0104d2a 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -1,21 +1,23 @@ -import define from '../../define.js'; -import bcrypt from 'bcryptjs'; -import rndstr from 'rndstr'; -import { Users, UserProfiles } from '@/models/index.js'; +import define from "../../define.js"; +import bcrypt from "bcryptjs"; +import rndstr from "rndstr"; +import { Users, UserProfiles } from "@/models/index.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { password: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, minLength: 8, maxLength: 8, }, @@ -24,11 +26,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -36,23 +38,26 @@ export default define(meta, paramDef, async (ps) => { const user = await Users.findOneBy({ id: ps.userId }); if (user == null) { - throw new Error('user not found'); + throw new Error("user not found"); } if (user.isAdmin) { - throw new Error('cannot reset password of admin'); + throw new Error("cannot reset password of admin"); } - const passwd = rndstr('a-zA-Z0-9', 8); + const passwd = rndstr("a-zA-Z0-9", 8); // Generate hash of password const hash = bcrypt.hashSync(passwd); - await UserProfiles.update({ - userId: user.id, - }, { - password: hash, - }); + await UserProfiles.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 3edae4a85..c7cc22bb1 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,24 +1,24 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - reportId: { type: 'string', format: 'misskey:id' }, - forward: { type: 'boolean', default: false }, + reportId: { type: "string", format: "misskey:id" }, + forward: { type: "boolean", default: false }, }, - required: ['reportId'], + required: ["reportId"], } as const; // eslint-disable-next-line import/no-default-export @@ -26,14 +26,18 @@ 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'); + throw new Error("report not found"); } 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); + deliver( + actor, + renderActivity(renderFlag(actor, [targetUser.uri!], report.comment)), + targetUser.inbox, + ); } await AbuseUserReports.update(report.id, { 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 bbdd66e4c..1f9e54d83 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -1,21 +1,21 @@ -import define from '../../define.js'; -import { sendEmail } from '@/services/send-email.js'; +import define from "../../define.js"; +import { sendEmail } from "@/services/send-email.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - to: { type: 'string' }, - subject: { type: 'string' }, - text: { type: 'string' }, + to: { type: "string" }, + subject: { type: "string" }, + text: { type: "string" }, }, - required: ['to', 'subject', 'text'], + required: ["to", "subject", "text"], } as const; // eslint-disable-next-line import/no-default-export 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 85c6fb82e..8a8ec82fb 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -1,85 +1,100 @@ -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 * 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"; export const meta = { requireCredential: true, requireModerator: true, - tags: ['admin', 'meta'], + tags: ["admin", "meta"], res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { machine: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, os: { - type: 'string', - optional: false, nullable: false, - example: 'linux', + type: "string", + optional: false, + nullable: false, + example: "linux", }, node: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, psql: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, cpu: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { model: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, cores: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, mem: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { total: { - type: 'number', - optional: false, nullable: false, - format: 'bytes', + type: "number", + optional: false, + nullable: false, + format: "bytes", }, }, }, fs: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { total: { - type: 'number', - optional: false, nullable: false, - format: 'bytes', + type: "number", + optional: false, + nullable: false, + format: "bytes", }, used: { - type: 'number', - optional: false, nullable: false, - format: 'bytes', + type: "number", + optional: false, + nullable: false, + format: "bytes", }, }, }, net: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { interface: { - type: 'string', - optional: false, nullable: false, - example: 'eth0', + type: "string", + optional: false, + nullable: false, + example: "eth0", }, }, }, @@ -88,7 +103,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -99,15 +114,17 @@ export default define(meta, paramDef, async () => { const fsStats = await si.fsSize(); const netInterface = await si.networkInterfaceDefault(); - const redisServerInfo = await redisClient.info('Server'); - const m = redisServerInfo.match(new RegExp('^redis_version:(.*)', 'm')); + const redisServerInfo = await 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 db.query('SHOW server_version').then(x => x[0].server_version), + psql: await db + .query("SHOW server_version") + .then((x) => x[0].server_version), redis: redis_version, cpu: { model: os.cpus()[0].model, 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 3545536aa..afdfc1391 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,47 +1,55 @@ -import define from '../../define.js'; -import { ModerationLogs } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { ModerationLogs } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, type: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, info: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, }, userId: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, user: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, }, @@ -49,18 +57,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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); + const query = makePaginationQuery( + ModerationLogs.createQueryBuilder("report"), + ps.sinceId, + ps.untilId, + ); const reports = await query.take(ps.limit).getMany(); 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 0d866b311..4492569f2 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,24 +1,25 @@ -import { Signins, UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Signins, UserProfiles, Users } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'object', - nullable: false, optional: false, + type: "object", + nullable: false, + optional: false, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -29,12 +30,12 @@ export default define(meta, paramDef, async (ps, me) => { ]); if (user == null || profile == null) { - throw new Error('user not found'); + throw new Error("user not found"); } const _me = await Users.findOneByOrFail({ id: me.id }); - if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) { - throw new Error('cannot show info of admin'); + if (_me.isModerator && !_me.isAdmin && user.isAdmin) { + throw new Error("cannot show info of admin"); } if (!_me.isAdmin) { @@ -45,9 +46,11 @@ export default define(meta, paramDef, async (ps, me) => { }; } - const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; - Object.keys(profile.integrations).forEach(integration => { - maskedKeys.forEach(key => profile.integrations[integration][key] = ''); + const maskedKeys = ["accessToken", "accessTokenSecret", "refreshToken"]; + Object.keys(profile.integrations).forEach((integration) => { + maskedKeys.forEach( + (key) => (profile.integrations[integration][key] = ""), + ); }); const signins = await Signins.findBy({ userId: user.id }); 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 8e09e72d5..a0883adfe 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,37 +1,66 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Users } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, res: { - type: 'array', - nullable: false, optional: false, + type: "array", + nullable: false, + optional: false, items: { - type: 'object', - nullable: false, optional: false, - ref: 'UserDetailed', + type: "object", + nullable: false, + optional: false, + ref: "UserDetailed", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", 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'] }, - 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 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, + sort: { + type: "string", + enum: [ + "+follower", + "-follower", + "+createdAt", + "-createdAt", + "+updatedAt", + "-updatedAt", + ], + }, + 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 }, hostname: { - type: 'string', + type: "string", nullable: true, default: null, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", }, }, required: [], @@ -39,39 +68,77 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const query = Users.createQueryBuilder('user'); + const query = Users.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; + 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; + 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() + '%' }); + query.andWhere("user.usernameLower like :username", { + username: `${ps.username.toLowerCase()}%`, + }); } if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + 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; + 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); 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 17b9f3b5a..1ff0fa98c 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -1,21 +1,21 @@ -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 define from "../../define.js"; +import { Users } from "@/models/index.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,20 +23,23 @@ 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'); + throw new Error("user not found"); } if (user.isAdmin) { - throw new Error('cannot silence admin'); + throw new Error("cannot silence admin"); } await Users.update(user.id, { isSilenced: true, }); - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true }); + publishInternalEvent("userChangeSilencedState", { + id: user.id, + isSilenced: true, + }); - insertModerationLog(me, 'silence', { + 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 f03bf592a..daa4e8890 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -1,24 +1,24 @@ -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 define from "../../define.js"; +import deleteFollowing from "@/services/following/delete.js"; +import { Users, Followings, Notifications } from "@/models/index.js"; +import type { 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -26,34 +26,34 @@ 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'); + throw new Error("user not found"); } if (user.isAdmin) { - throw new Error('cannot suspend admin'); + throw new Error("cannot suspend admin"); } if (user.isModerator) { - throw new Error('cannot suspend moderator'); + throw new Error("cannot suspend moderator"); } await Users.update(user.id, { isSuspended: true, }); - insertModerationLog(me, 'suspend', { + insertModerationLog(me, "suspend", { targetId: user.id, }); // Terminate streaming if (Users.isLocalUser(user)) { - publishUserEvent(user.id, 'terminate', {}); + publishUserEvent(user.id, "terminate", {}); } (async () => { - await doPostSuspend(user).catch(e => {}); - await unFollowAll(user).catch(e => {}); - await readAllNotify(user).catch(e => {}); + await doPostSuspend(user).catch((e) => {}); + await unFollowAll(user).catch((e) => {}); + await readAllNotify(user).catch((e) => {}); })(); }); @@ -76,10 +76,13 @@ async function unFollowAll(follower: User) { } async function readAllNotify(notifier: User) { - await Notifications.update({ - notifierId: notifier.id, - isRead: false, - }, { - isRead: true, - }); + await Notifications.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 a4b373f5c..9afdc4c1f 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,21 +1,21 @@ -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 define from "../../define.js"; +import { Users } from "@/models/index.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,16 +23,19 @@ 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'); + throw new Error("user not found"); } await Users.update(user.id, { isSilenced: false, }); - publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false }); + publishInternalEvent("userChangeSilencedState", { + id: user.id, + isSilenced: false, + }); - insertModerationLog(me, 'unsilence', { + 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 5cf26251b..714888615 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,21 +1,21 @@ -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 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"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,14 +23,14 @@ 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'); + throw new Error("user not found"); } await Users.update(user.id, { isSuspended: false, }); - insertModerationLog(me, 'unsuspend', { + insertModerationLog(me, "unsuspend", { targetId: user.id, }); 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 3efacdf50..b843a5326 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -1,122 +1,166 @@ -import { Meta } from '@/models/entities/meta.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { db } from '@/db/postgre.js'; -import define from '../../define.js'; +import { Meta } from "@/models/entities/meta.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js"; +import { db } from "@/db/postgre.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireAdmin: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - disableRegistration: { type: 'boolean', nullable: true }, - disableLocalTimeline: { type: 'boolean', nullable: true }, - disableRecommendedTimeline: { type: 'boolean', nullable: true }, - disableGlobalTimeline: { type: 'boolean', nullable: true }, - defaultReaction: { type: 'string', nullable: true }, - recommendedInstances: { type: 'array', nullable: true, items: { - type: 'string', - } }, - pinnedUsers: { type: 'array', nullable: true, items: { - type: 'string', - } }, - customMOTD: { type: 'array', nullable: true, items: { - type: 'string', - } }, - customSplashIcons: { type: 'array', nullable: true, items: { - type: 'string', - } }, - hiddenTags: { type: 'array', nullable: true, items: { - type: 'string', - } }, - blockedHosts: { type: 'array', nullable: true, items: { - type: 'string', - } }, - allowedHosts: { type: 'array', nullable: true, items: { - type: 'string', - } }, - secureMode: { type: 'boolean', nullable: true }, - privateMode: { type: 'boolean', nullable: true }, - themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, - mascotImageUrl: { type: 'string', nullable: true }, - bannerUrl: { type: 'string', nullable: true }, - logoImageUrl: { type: 'string', nullable: true }, - errorImageUrl: { type: 'string', nullable: true }, - iconUrl: { type: 'string', nullable: true }, - backgroundImageUrl: { type: 'string', nullable: true }, - name: { type: 'string', nullable: true }, - description: { type: 'string', nullable: true }, - defaultLightTheme: { type: 'string', nullable: true }, - defaultDarkTheme: { type: 'string', nullable: true }, - localDriveCapacityMb: { type: 'integer' }, - remoteDriveCapacityMb: { type: 'integer' }, - cacheRemoteFiles: { type: 'boolean' }, - emailRequiredForSignup: { type: 'boolean' }, - enableHcaptcha: { type: 'boolean' }, - hcaptchaSiteKey: { type: 'string', nullable: true }, - hcaptchaSecretKey: { type: 'string', nullable: true }, - enableRecaptcha: { type: 'boolean' }, - recaptchaSiteKey: { type: 'string', nullable: true }, - recaptchaSecretKey: { type: 'string', nullable: true }, - sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, - sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, - setSensitiveFlagAutomatically: { type: 'boolean' }, - enableSensitiveMediaDetectionForVideos: { type: 'boolean' }, - proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true }, - maintainerName: { type: 'string', nullable: true }, - maintainerEmail: { type: 'string', nullable: true }, - pinnedPages: { type: 'array', items: { - type: 'string', - } }, - pinnedClipId: { type: 'string', format: 'misskey:id', nullable: true }, - langs: { type: 'array', items: { - type: 'string', - } }, - summalyProxy: { type: 'string', nullable: true }, - deeplAuthKey: { type: 'string', nullable: true }, - deeplIsPro: { type: 'boolean' }, - enableTwitterIntegration: { type: 'boolean' }, - twitterConsumerKey: { type: 'string', nullable: true }, - twitterConsumerSecret: { type: 'string', nullable: true }, - enableGithubIntegration: { type: 'boolean' }, - githubClientId: { type: 'string', nullable: true }, - githubClientSecret: { type: 'string', nullable: true }, - enableDiscordIntegration: { type: 'boolean' }, - discordClientId: { type: 'string', nullable: true }, - discordClientSecret: { type: 'string', nullable: true }, - enableEmail: { type: 'boolean' }, - email: { type: 'string', nullable: true }, - smtpSecure: { type: 'boolean' }, - smtpHost: { type: 'string', nullable: true }, - smtpPort: { type: 'integer', nullable: true }, - smtpUser: { type: 'string', nullable: true }, - smtpPass: { type: 'string', nullable: true }, - enableServiceWorker: { type: 'boolean' }, - swPublicKey: { type: 'string', nullable: true }, - swPrivateKey: { type: 'string', nullable: true }, - tosUrl: { type: 'string', nullable: true }, - repositoryUrl: { type: 'string' }, - feedbackUrl: { type: 'string' }, - useObjectStorage: { type: 'boolean' }, - objectStorageBaseUrl: { type: 'string', nullable: true }, - objectStorageBucket: { type: 'string', nullable: true }, - objectStoragePrefix: { type: 'string', nullable: true }, - objectStorageEndpoint: { type: 'string', nullable: true }, - objectStorageRegion: { type: 'string', nullable: true }, - objectStoragePort: { type: 'integer', nullable: true }, - objectStorageAccessKey: { type: 'string', nullable: true }, - objectStorageSecretKey: { type: 'string', nullable: true }, - objectStorageUseSSL: { type: 'boolean' }, - objectStorageUseProxy: { type: 'boolean' }, - objectStorageSetPublicRead: { type: 'boolean' }, - objectStorageS3ForcePathStyle: { type: 'boolean' }, - enableIpLogging: { type: 'boolean' }, - enableActiveEmailValidation: { type: 'boolean' }, + disableRegistration: { type: "boolean", nullable: true }, + disableLocalTimeline: { type: "boolean", nullable: true }, + disableRecommendedTimeline: { type: "boolean", nullable: true }, + disableGlobalTimeline: { type: "boolean", nullable: true }, + defaultReaction: { type: "string", nullable: true }, + recommendedInstances: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + pinnedUsers: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + customMOTD: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + customSplashIcons: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + hiddenTags: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + blockedHosts: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + allowedHosts: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, + secureMode: { type: "boolean", nullable: true }, + privateMode: { type: "boolean", nullable: true }, + themeColor: { + type: "string", + nullable: true, + pattern: "^#[0-9a-fA-F]{6}$", + }, + mascotImageUrl: { type: "string", nullable: true }, + bannerUrl: { type: "string", nullable: true }, + logoImageUrl: { type: "string", nullable: true }, + errorImageUrl: { type: "string", nullable: true }, + iconUrl: { type: "string", nullable: true }, + backgroundImageUrl: { type: "string", nullable: true }, + name: { type: "string", nullable: true }, + description: { type: "string", nullable: true }, + defaultLightTheme: { type: "string", nullable: true }, + defaultDarkTheme: { type: "string", nullable: true }, + localDriveCapacityMb: { type: "integer" }, + remoteDriveCapacityMb: { type: "integer" }, + cacheRemoteFiles: { type: "boolean" }, + emailRequiredForSignup: { type: "boolean" }, + enableHcaptcha: { type: "boolean" }, + hcaptchaSiteKey: { type: "string", nullable: true }, + hcaptchaSecretKey: { type: "string", nullable: true }, + enableRecaptcha: { type: "boolean" }, + recaptchaSiteKey: { type: "string", nullable: true }, + recaptchaSecretKey: { type: "string", nullable: true }, + sensitiveMediaDetection: { + type: "string", + enum: ["none", "all", "local", "remote"], + }, + sensitiveMediaDetectionSensitivity: { + type: "string", + enum: ["medium", "low", "high", "veryLow", "veryHigh"], + }, + setSensitiveFlagAutomatically: { type: "boolean" }, + enableSensitiveMediaDetectionForVideos: { type: "boolean" }, + proxyAccountId: { type: "string", format: "misskey:id", nullable: true }, + maintainerName: { type: "string", nullable: true }, + maintainerEmail: { type: "string", nullable: true }, + pinnedPages: { + type: "array", + items: { + type: "string", + }, + }, + pinnedClipId: { type: "string", format: "misskey:id", nullable: true }, + langs: { + type: "array", + items: { + type: "string", + }, + }, + summalyProxy: { type: "string", nullable: true }, + deeplAuthKey: { type: "string", nullable: true }, + deeplIsPro: { type: "boolean" }, + enableTwitterIntegration: { type: "boolean" }, + twitterConsumerKey: { type: "string", nullable: true }, + twitterConsumerSecret: { type: "string", nullable: true }, + enableGithubIntegration: { type: "boolean" }, + githubClientId: { type: "string", nullable: true }, + githubClientSecret: { type: "string", nullable: true }, + enableDiscordIntegration: { type: "boolean" }, + discordClientId: { type: "string", nullable: true }, + discordClientSecret: { type: "string", nullable: true }, + enableEmail: { type: "boolean" }, + email: { type: "string", nullable: true }, + smtpSecure: { type: "boolean" }, + smtpHost: { type: "string", nullable: true }, + smtpPort: { type: "integer", nullable: true }, + smtpUser: { type: "string", nullable: true }, + smtpPass: { type: "string", nullable: true }, + enableServiceWorker: { type: "boolean" }, + swPublicKey: { type: "string", nullable: true }, + swPrivateKey: { type: "string", nullable: true }, + tosUrl: { type: "string", nullable: true }, + repositoryUrl: { type: "string" }, + feedbackUrl: { type: "string" }, + useObjectStorage: { type: "boolean" }, + objectStorageBaseUrl: { type: "string", nullable: true }, + objectStorageBucket: { type: "string", nullable: true }, + objectStoragePrefix: { type: "string", nullable: true }, + objectStorageEndpoint: { type: "string", nullable: true }, + objectStorageRegion: { type: "string", nullable: true }, + objectStoragePort: { type: "integer", nullable: true }, + objectStorageAccessKey: { type: "string", nullable: true }, + objectStorageSecretKey: { type: "string", nullable: true }, + objectStorageUseSSL: { type: "boolean" }, + objectStorageUseProxy: { type: "boolean" }, + objectStorageSetPublicRead: { type: "boolean" }, + objectStorageS3ForcePathStyle: { type: "boolean" }, + enableIpLogging: { type: "boolean" }, + enableActiveEmailValidation: { type: "boolean" }, }, required: [], } as const; @@ -125,23 +169,23 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const set = {} as Partial; - if (typeof ps.disableRegistration === 'boolean') { + if (typeof ps.disableRegistration === "boolean") { set.disableRegistration = ps.disableRegistration; } - if (typeof ps.disableLocalTimeline === 'boolean') { + if (typeof ps.disableLocalTimeline === "boolean") { set.disableLocalTimeline = ps.disableLocalTimeline; } - if (typeof ps.disableRecommendedTimeline === 'boolean') { + if (typeof ps.disableRecommendedTimeline === "boolean") { set.disableRecommendedTimeline = ps.disableRecommendedTimeline; } - if (typeof ps.disableGlobalTimeline === 'boolean') { + if (typeof ps.disableGlobalTimeline === "boolean") { set.disableGlobalTimeline = ps.disableGlobalTimeline; } - if (typeof ps.defaultReaction === 'string') { + if (typeof ps.defaultReaction === "string") { set.defaultReaction = ps.defaultReaction; } @@ -177,11 +221,11 @@ export default define(meta, paramDef, async (ps, me) => { set.allowedHosts = ps.allowedHosts.filter(Boolean); } - if (typeof ps.privateMode === 'boolean') { + if (typeof ps.privateMode === "boolean") { set.privateMode = ps.privateMode; } - if (typeof ps.secureMode === 'boolean') { + if (typeof ps.secureMode === "boolean") { set.secureMode = ps.secureMode; } @@ -270,7 +314,8 @@ export default define(meta, paramDef, async (ps, me) => { } if (ps.sensitiveMediaDetectionSensitivity !== undefined) { - set.sensitiveMediaDetectionSensitivity = ps.sensitiveMediaDetectionSensitivity; + set.sensitiveMediaDetectionSensitivity = + ps.sensitiveMediaDetectionSensitivity; } if (ps.setSensitiveFlagAutomatically !== undefined) { @@ -278,7 +323,8 @@ export default define(meta, paramDef, async (ps, me) => { } if (ps.enableSensitiveMediaDetectionForVideos !== undefined) { - set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos; + set.enableSensitiveMediaDetectionForVideos = + ps.enableSensitiveMediaDetectionForVideos; } if (ps.proxyAccountId !== undefined) { @@ -454,7 +500,7 @@ export default define(meta, paramDef, async (ps, me) => { } if (ps.deeplAuthKey !== undefined) { - if (ps.deeplAuthKey === '') { + if (ps.deeplAuthKey === "") { set.deeplAuthKey = null; } else { set.deeplAuthKey = ps.deeplAuthKey; @@ -473,10 +519,10 @@ export default define(meta, paramDef, async (ps, me) => { set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } - await db.transaction(async transactionalEntityManager => { + await db.transaction(async (transactionalEntityManager) => { const metas = await transactionalEntityManager.find(Meta, { order: { - id: 'DESC', + id: "DESC", }, }); @@ -489,5 +535,5 @@ export default define(meta, paramDef, async (ps, me) => { } }); - insertModerationLog(me, 'updateMeta'); + 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 fa21ab783..044515e93 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,20 +1,20 @@ -import { UserProfiles, Users } from '@/models/index.js'; -import define from '../../define.js'; +import { UserProfiles, Users } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - text: { type: 'string' }, + userId: { type: "string", format: "misskey:id" }, + text: { type: "string" }, }, - required: ['userId', 'text'], + required: ["userId", "text"], } as const; // eslint-disable-next-line import/no-default-export @@ -22,10 +22,13 @@ 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'); + throw new Error("user not found"); } - await UserProfiles.update({ userId: user.id }, { - moderationNote: ps.text, - }); + await UserProfiles.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 index 0546acfac..c54291675 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -1,21 +1,21 @@ -import define from '../../define.js'; -import { insertModerationLog } from '@/services/insert-moderation-log.js'; -import { db } from '@/db/postgre.js'; +import define from "../../define.js"; +import { insertModerationLog } from "@/services/insert-moderation-log.js"; +import { db } from "@/db/postgre.js"; export const meta = { - tags: ['admin'], + tags: ["admin"], requireCredential: true, requireModerator: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - full: { type: 'boolean' }, - analyze: { type: 'boolean' }, + full: { type: "boolean" }, + analyze: { type: "boolean" }, }, - required: ['full', 'analyze'], + required: ["full", "analyze"], } as const; // eslint-disable-next-line import/no-default-export @@ -23,14 +23,14 @@ export default define(meta, paramDef, async (ps, me) => { const params: string[] = []; if (ps.full) { - params.push('FULL'); + params.push("FULL"); } if (ps.analyze) { - params.push('ANALYZE'); + params.push("ANALYZE"); } - db.query('VACUUM ' + params.join(' ')); + db.query(`VACUUM ${params.join(" ")}`); - insertModerationLog(me, 'vacuum', ps); + 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 189de042b..f963e3015 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -1,51 +1,60 @@ -import { Announcements, AnnouncementReads } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Announcements, AnnouncementReads } from "@/models/index.js"; +import define from "../define.js"; +import { makePaginationQuery } from "../common/make-pagination-query.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - example: 'xxxxxxxxxx', + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", }, createdAt: { - type: 'string', - optional: false, nullable: false, - format: 'date-time', + type: "string", + optional: false, + nullable: false, + format: "date-time", }, updatedAt: { - type: 'string', - optional: false, nullable: true, - format: 'date-time', + type: "string", + optional: false, + nullable: true, + format: "date-time", }, text: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, title: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, imageUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, isRead: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, }, }, }, @@ -53,33 +62,41 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - withUnreads: { type: 'boolean', default: false }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + withUnreads: { type: "boolean", default: false }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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); + const query = makePaginationQuery( + Announcements.createQueryBuilder("announcement"), + ps.sinceId, + ps.untilId, + ); const announcements = await query.take(ps.limit).getMany(); if (user) { - const reads = (await AnnouncementReads.findBy({ - userId: user.id, - })).map(x => x.announcementId); + const reads = ( + await AnnouncementReads.findBy({ + userId: user.id, + }) + ).map((x) => x.announcementId); 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) => ({ + 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 ae85c44cf..bd99c922e 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,72 +1,94 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; -import { ApiError } from '../../error.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../define.js"; +import { genId } from "@/misc/gen-id.js"; +import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js"; +import { ApiError } from "../../error.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['antennas'], + tags: ["antennas"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchUserList: { - message: 'No such user list.', - code: 'NO_SUCH_USER_LIST', - id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f', + message: "No such user list.", + code: "NO_SUCH_USER_LIST", + id: "95063e93-a283-4b8b-9aa5-bcdb8df69a7f", }, noSuchUserGroup: { - message: 'No such user group.', - code: 'NO_SUCH_USER_GROUP', - id: 'aa3c0b9a-8cae-47c0-92ac-202ce5906682', + message: "No such user group.", + code: "NO_SUCH_USER_GROUP", + id: "aa3c0b9a-8cae-47c0-92ac-202ce5906682", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Antenna', + type: "object", + optional: false, + nullable: false, + ref: "Antenna", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 100 }, - src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] }, - userListId: { type: 'string', format: 'misskey:id', nullable: true }, - userGroupId: { type: 'string', format: 'misskey:id', nullable: true }, - keywords: { type: 'array', items: { - type: 'array', items: { - type: 'string', + name: { type: "string", minLength: 1, maxLength: 100 }, + src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + userListId: { type: "string", format: "misskey:id", nullable: true }, + userGroupId: { type: "string", format: "misskey:id", nullable: true }, + keywords: { + type: "array", + items: { + type: "array", + items: { + type: "string", + }, }, - } }, - excludeKeywords: { type: 'array', items: { - type: 'array', items: { - type: 'string', + }, + excludeKeywords: { + type: "array", + items: { + type: "array", + items: { + type: "string", + }, }, - } }, - users: { type: 'array', items: { - type: 'string', - } }, - caseSensitive: { type: 'boolean' }, - withReplies: { type: 'boolean' }, - withFile: { type: 'boolean' }, - notify: { type: 'boolean' }, + }, + users: { + type: "array", + items: { + type: "string", + }, + }, + caseSensitive: { type: "boolean" }, + withReplies: { type: "boolean" }, + withFile: { type: "boolean" }, + notify: { type: "boolean" }, }, - required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], + required: [ + "name", + "src", + "keywords", + "excludeKeywords", + "users", + "caseSensitive", + "withReplies", + "withFile", + "notify", + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - if(user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup); + if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup); let userList; let userGroupJoining; - if (ps.src === 'list' && ps.userListId) { + if (ps.src === "list" && ps.userListId) { userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, @@ -75,7 +97,7 @@ export default define(meta, paramDef, async (ps, user) => { if (userList == null) { throw new ApiError(meta.errors.noSuchUserList); } - } else if (ps.src === 'group' && ps.userGroupId) { + } else if (ps.src === "group" && ps.userGroupId) { userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, @@ -101,9 +123,9 @@ export default define(meta, paramDef, async (ps, user) => { withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, - }).then(x => Antennas.findOneByOrFail(x.identifiers[0])); + }).then((x) => Antennas.findOneByOrFail(x.identifiers[0])); - publishInternalEvent('antennaCreated', antenna); + publishInternalEvent("antennaCreated", antenna); return await Antennas.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 ced34ba31..6f1d87a53 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -1,30 +1,30 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Antennas } from "@/models/index.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['antennas'], + tags: ["antennas"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchAntenna: { - message: 'No such antenna.', - code: 'NO_SUCH_ANTENNA', - id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df', + message: "No such antenna.", + code: "NO_SUCH_ANTENNA", + id: "b34dcf9d-348f-44bb-99d0-6c9314cfe2df", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - antennaId: { type: 'string', format: 'misskey:id' }, + antennaId: { type: "string", format: "misskey:id" }, }, - required: ['antennaId'], + required: ["antennaId"], } as const; // eslint-disable-next-line import/no-default-export @@ -40,5 +40,5 @@ export default define(meta, paramDef, async (ps, user) => { await Antennas.delete(antenna.id); - publishInternalEvent('antennaDeleted', antenna); + 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 c519b452e..afe4349f9 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -1,26 +1,28 @@ -import define from '../../define.js'; -import { Antennas } from '@/models/index.js'; +import define from "../../define.js"; +import { Antennas } from "@/models/index.js"; export const meta = { - tags: ['antennas', 'account'], + tags: ["antennas", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Antenna', + type: "object", + optional: false, + nullable: false, + ref: "Antenna", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -31,5 +33,5 @@ export default define(meta, paramDef, async (ps, me) => { userId: me.id, }); - return await Promise.all(antennas.map(x => Antennas.pack(x))); + return await Promise.all(antennas.map((x) => Antennas.pack(x))); }); diff --git a/packages/backend/src/server/api/endpoints/antennas/markread.ts b/packages/backend/src/server/api/endpoints/antennas/markread.ts index 5ea3b0c60..d9faf9705 100644 --- a/packages/backend/src/server/api/endpoints/antennas/markread.ts +++ b/packages/backend/src/server/api/endpoints/antennas/markread.ts @@ -1,23 +1,22 @@ -import define from '../../define.js'; -import { Antennas, AntennaNotes } from '@/models/index.js'; -import { FindOptionsWhere } from 'typeorm'; -import { AntennaNote } from '@/models/entities/antenna-note.js'; +import define from "../../define.js"; +import { Antennas, AntennaNotes } from "@/models/index.js"; +import { FindOptionsWhere } from "typeorm"; +import { AntennaNote } from "@/models/entities/antenna-note.js"; export const meta = { - tags: ['antennas', 'account'], + tags: ["antennas", "account"], requireCredential: true, - kind: 'write:account', - + kind: "write:account", } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - antennaId: { type: 'string', format: 'misskey:id' }, + antennaId: { type: "string", format: "misskey:id" }, }, - required: ['antennaId'], + required: ["antennaId"], } as const; // eslint-disable-next-line import/no-default-export @@ -28,18 +27,18 @@ export default define(meta, paramDef, async (ps, me) => { }); if (!antenna) { - return null + return null; } - await AntennaNotes.update({ - antennaId: antenna.id, - read: false, - }, { - read: true, - }) - - return true + await AntennaNotes.update( + { + antennaId: antenna.id, + read: false, + }, + { + read: true, + }, + ); + return true; }); - - diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 8aac55b4a..b34454c2f 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,49 +1,51 @@ -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 { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +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 { ApiError } from "../../error.js"; +import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; export const meta = { - tags: ['antennas', 'account', 'notes'], + tags: ["antennas", "account", "notes"], requireCredential: true, - kind: 'read:account', + kind: "read:account", errors: { noSuchAntenna: { - message: 'No such antenna.', - code: 'NO_SUCH_ANTENNA', - id: '850926e0-fd3b-49b6-b69a-b28a5dbd82fe', + message: "No such antenna.", + code: "NO_SUCH_ANTENNA", + id: "850926e0-fd3b-49b6-b69a-b28a5dbd82fe", }, }, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - antennaId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + antennaId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, - required: ['antennaId'], + required: ["antennaId"], } as const; // eslint-disable-next-line import/no-default-export @@ -57,29 +59,36 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchAntenna); } - 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 }); + 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(); + const notes = await query.take(ps.limit).getMany(); if (notes.length > 0) { readNote(user.id, notes); diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index dd693789c..715c0a655 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -1,35 +1,36 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Antennas } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Antennas } from "@/models/index.js"; export const meta = { - tags: ['antennas', 'account'], + tags: ["antennas", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", errors: { noSuchAntenna: { - message: 'No such antenna.', - code: 'NO_SUCH_ANTENNA', - id: 'c06569fb-b025-4f23-b22d-1fcd20d2816b', + message: "No such antenna.", + code: "NO_SUCH_ANTENNA", + id: "c06569fb-b025-4f23-b22d-1fcd20d2816b", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Antenna', + type: "object", + optional: false, + nullable: false, + ref: "Antenna", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - antennaId: { type: 'string', format: 'misskey:id' }, + antennaId: { type: "string", format: "misskey:id" }, }, - required: ['antennaId'], + required: ["antennaId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index edfedc175..c679c9925 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -1,69 +1,92 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Antennas, UserLists, UserGroupJoinings } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../define.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'], + tags: ["antennas"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchAntenna: { - message: 'No such antenna.', - code: 'NO_SUCH_ANTENNA', - id: '10c673ac-8852-48eb-aa1f-f5b67f069290', + message: "No such antenna.", + code: "NO_SUCH_ANTENNA", + id: "10c673ac-8852-48eb-aa1f-f5b67f069290", }, noSuchUserList: { - message: 'No such user list.', - code: 'NO_SUCH_USER_LIST', - id: '1c6b35c9-943e-48c2-81e4-2844989407f7', + message: "No such user list.", + code: "NO_SUCH_USER_LIST", + id: "1c6b35c9-943e-48c2-81e4-2844989407f7", }, noSuchUserGroup: { - message: 'No such user group.', - code: 'NO_SUCH_USER_GROUP', - id: '109ed789-b6eb-456e-b8a9-6059d567d385', + message: "No such user group.", + code: "NO_SUCH_USER_GROUP", + id: "109ed789-b6eb-456e-b8a9-6059d567d385", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Antenna', + type: "object", + optional: false, + nullable: false, + ref: "Antenna", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - antennaId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 100 }, - src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] }, - userListId: { type: 'string', format: 'misskey:id', nullable: true }, - userGroupId: { type: 'string', format: 'misskey:id', nullable: true }, - keywords: { type: 'array', items: { - type: 'array', items: { - type: 'string', + antennaId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 100 }, + src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + userListId: { type: "string", format: "misskey:id", nullable: true }, + userGroupId: { type: "string", format: "misskey:id", nullable: true }, + keywords: { + type: "array", + items: { + type: "array", + items: { + type: "string", + }, }, - } }, - excludeKeywords: { type: 'array', items: { - type: 'array', items: { - type: 'string', + }, + excludeKeywords: { + type: "array", + items: { + type: "array", + items: { + type: "string", + }, }, - } }, - users: { type: 'array', items: { - type: 'string', - } }, - caseSensitive: { type: 'boolean' }, - withReplies: { type: 'boolean' }, - withFile: { type: 'boolean' }, - notify: { type: 'boolean' }, + }, + users: { + type: "array", + items: { + type: "string", + }, + }, + caseSensitive: { type: "boolean" }, + withReplies: { type: "boolean" }, + withFile: { type: "boolean" }, + notify: { type: "boolean" }, }, - required: ['antennaId', 'name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], + required: [ + "antennaId", + "name", + "src", + "keywords", + "excludeKeywords", + "users", + "caseSensitive", + "withReplies", + "withFile", + "notify", + ], } as const; // eslint-disable-next-line import/no-default-export @@ -81,7 +104,7 @@ export default define(meta, paramDef, async (ps, user) => { let userList; let userGroupJoining; - if (ps.src === 'list' && ps.userListId) { + if (ps.src === "list" && ps.userListId) { userList = await UserLists.findOneBy({ id: ps.userListId, userId: user.id, @@ -90,7 +113,7 @@ export default define(meta, paramDef, async (ps, user) => { if (userList == null) { throw new ApiError(meta.errors.noSuchUserList); } - } else if (ps.src === 'group' && ps.userGroupId) { + } else if (ps.src === "group" && ps.userGroupId) { userGroupJoining = await UserGroupJoinings.findOneBy({ userGroupId: ps.userGroupId, userId: user.id, @@ -115,7 +138,10 @@ export default define(meta, paramDef, async (ps, user) => { notify: ps.notify, }); - publishInternalEvent('antennaUpdated', await Antennas.findOneByOrFail({ id: antenna.id })); + 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 559bc277f..1a522691d 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,9 +1,9 @@ -import define from '../../define.js'; -import Resolver from '@/remote/activitypub/resolver.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import Resolver from "@/remote/activitypub/resolver.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, @@ -12,21 +12,21 @@ export const meta = { max: 30, }, - errors: { - }, + errors: {}, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - uri: { type: 'string' }, + uri: { type: "string" }, }, - required: ['uri'], + required: ["uri"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 6d432d63b..70f383c3d 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,22 +1,22 @@ -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 { SchemaType } from '@/misc/schema.js'; -import { HOUR } from '@/const.js'; -import { shouldBlockInstance } from '@/misc/should-block-instance.js'; +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 type { Note } from "@/models/entities/note.js"; +import type { CacheableLocalUser, User } from "@/models/entities/user.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { isActor, isPost, getApId } from "@/remote/activitypub/type.js"; +import type { SchemaType } from "@/misc/schema.js"; +import { HOUR } from "@/const.js"; +import { shouldBlockInstance } from "@/misc/should-block-instance.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, @@ -27,55 +27,60 @@ export const meta = { errors: { noSuchObject: { - message: 'No such object.', - code: 'NO_SUCH_OBJECT', - id: 'dc94d745-1262-4e63-a17d-fecaa57efc82', + message: "No such object.", + code: "NO_SUCH_OBJECT", + id: "dc94d745-1262-4e63-a17d-fecaa57efc82", }, }, res: { - optional: false, nullable: false, + optional: false, + nullable: false, oneOf: [ { - type: 'object', + type: "object", properties: { type: { - type: 'string', - optional: false, nullable: false, - enum: ['User'], + type: "string", + optional: false, + nullable: false, + enum: ["User"], }, object: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailedNotMe', - } - } + type: "object", + optional: false, + nullable: false, + ref: "UserDetailedNotMe", + }, + }, }, { - type: 'object', + type: "object", properties: { type: { - type: 'string', - optional: false, nullable: false, - enum: ['Note'], + type: "string", + optional: false, + nullable: false, + enum: ["Note"], }, object: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', - } - } - } + type: "object", + optional: false, + nullable: false, + ref: "Note", + }, + }, + }, ], }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - uri: { type: 'string' }, + uri: { type: "string" }, }, - required: ['uri'], + required: ["uri"], } as const; // eslint-disable-next-line import/no-default-export @@ -91,29 +96,38 @@ export default define(meta, paramDef, async (ps, me) => { /*** * Resolve User or Note from URI */ -async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { +async function fetchAny( + uri: string, + me: CacheableLocalUser | null | undefined, +): Promise | null> { // Wait if blocked. if (await shouldBlockInstance(extractDbHost(uri))) return null; const dbResolver = new DbResolver(); - let local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(uri), - dbResolver.getNoteFromApId(uri), - ])); + let local = await mergePack( + me, + ...(await Promise.all([ + dbResolver.getUserFromApId(uri), + dbResolver.getNoteFromApId(uri), + ])), + ); if (local != null) return local; // fetching Object once from remote const resolver = new Resolver(); - const object = await resolver.resolve(uri) as any; + const object = (await resolver.resolve(uri)) as any; // /@user If a URI other than the id is specified, // the URI is determined here if (uri !== object.id) { - local = await mergePack(me, ...await Promise.all([ - dbResolver.getUserFromApId(object.id), - dbResolver.getNoteFromApId(object.id), - ])); + local = await mergePack( + me, + ...(await Promise.all([ + dbResolver.getUserFromApId(object.id), + dbResolver.getNoteFromApId(object.id), + ])), + ); if (local != null) return local; } @@ -124,10 +138,14 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): ); } -async function mergePack(me: CacheableLocalUser | null | undefined, user: User | null | undefined, note: Note | null | undefined): Promise | null> { +async function mergePack( + me: CacheableLocalUser | null | undefined, + user: User | null | undefined, + note: Note | null | undefined, +): Promise | null> { if (user != null) { return { - type: 'User', + type: "User", object: await Users.pack(user, me, { detail: true }), }; } else if (note != null) { @@ -135,7 +153,7 @@ async function mergePack(me: CacheableLocalUser | null | undefined, user: User | const object = await Notes.pack(note, me, { detail: true }); return { - type: 'Note', + type: "Note", object, }; } catch (e) { diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index 15b2089e7..ec3d8be7a 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -1,45 +1,53 @@ -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 { secureRndstr } from '@/misc/secure-rndstr.js'; +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 { secureRndstr } from "@/misc/secure-rndstr.js"; export const meta = { - tags: ['app'], + tags: ["app"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, - ref: 'App', + type: "object", + optional: false, + nullable: false, + ref: "App", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - description: { type: 'string' }, - permission: { type: 'array', uniqueItems: true, items: { - type: 'string', - } }, - callbackUrl: { type: 'string', nullable: true }, + name: { type: "string" }, + description: { type: "string" }, + permission: { + type: "array", + uniqueItems: true, + items: { + type: "string", + }, + }, + callbackUrl: { type: "string", nullable: true }, }, - required: ['name', 'description', 'permission'], + required: ["name", "description", "permission"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - if(user && user.movedToUri != null) return await Apps.pack("", null, { - detail: true, - includeSecret: true, - });; + if (user?.movedToUri != null) + return await Apps.pack("", null, { + detail: true, + includeSecret: true, + }); // Generate secret const secret = secureRndstr(32, true); // for backward compatibility - const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); + const permission = unique( + ps.permission.map((v) => v.replace(/^(.+)(\/|-)(read|write)$/, "$3:$1")), + ); // Create account const app = await Apps.insert({ @@ -51,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => { permission, callbackUrl: ps.callbackUrl, secret: secret, - }).then(x => Apps.findOneByOrFail(x.identifiers[0])); + }).then((x) => Apps.findOneByOrFail(x.identifiers[0])); return await Apps.pack(app, null, { detail: true, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index 451969d97..a14d0997b 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -1,31 +1,32 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Apps } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Apps } from "@/models/index.js"; export const meta = { - tags: ['app'], + tags: ["app"], errors: { noSuchApp: { - message: 'No such app.', - code: 'NO_SUCH_APP', - id: 'dce83913-2dc6-4093-8a7b-71dbb11718a3', + message: "No such app.", + code: "NO_SUCH_APP", + id: "dce83913-2dc6-4093-8a7b-71dbb11718a3", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'App', + type: "object", + optional: false, + nullable: false, + ref: "App", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - appId: { type: 'string', format: 'misskey:id' }, + appId: { type: "string", format: "misskey:id" }, }, - required: ['appId'], + required: ["appId"], } as const; // eslint-disable-next-line import/no-default-export @@ -41,6 +42,6 @@ export default define(meta, paramDef, async (ps, user, token) => { return await Apps.pack(ap, user, { detail: true, - includeSecret: isSecure && (ap.userId === user!.id), + 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 b5c06792b..c39652976 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -1,12 +1,12 @@ -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 { secureRndstr } from '@/misc/secure-rndstr.js'; +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 { secureRndstr } from "@/misc/secure-rndstr.js"; export const meta = { - tags: ['auth'], + tags: ["auth"], requireCredential: true, @@ -14,26 +14,25 @@ export const meta = { errors: { noSuchSession: { - message: 'No such session.', - code: 'NO_SUCH_SESSION', - id: '9c72d8de-391a-43c1-9d06-08d29efde8df', + message: "No such session.", + code: "NO_SUCH_SESSION", + id: "9c72d8de-391a-43c1-9d06-08d29efde8df", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - token: { type: 'string' }, + token: { type: "string" }, }, - required: ['token'], + required: ["token"], } 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 }); + const session = await AuthSessions.findOneBy({ token: ps.token }); if (session == null) { throw new ApiError(meta.errors.noSuchSession); @@ -53,9 +52,9 @@ export default define(meta, paramDef, async (ps, user) => { const app = await Apps.findOneByOrFail({ id: session.appId }); // Generate Hash - const sha256 = crypto.createHash('sha256'); + const sha256 = crypto.createHash("sha256"); sha256.update(accessToken + app.secret); - const hash = sha256.digest('hex'); + const hash = sha256.digest("hex"); const now = new Date(); 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 717c3e508..cbe949fe3 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -1,46 +1,49 @@ -import { v4 as uuid } from 'uuid'; -import config from '@/config/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { v4 as uuid } from "uuid"; +import config from "@/config/index.js"; +import define from "../../../define.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'], + tags: ["auth"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { token: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, url: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, }, }, errors: { noSuchApp: { - message: 'No such app.', - code: 'NO_SUCH_APP', - id: '92f93e63-428e-4f2f-a5a4-39e1407fe998', + message: "No such app.", + code: "NO_SUCH_APP", + id: "92f93e63-428e-4f2f-a5a4-39e1407fe998", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - appSecret: { type: 'string' }, + appSecret: { type: "string" }, }, - required: ['appSecret'], + required: ["appSecret"], } as const; // eslint-disable-next-line import/no-default-export @@ -63,7 +66,7 @@ export default define(meta, paramDef, async (ps) => { createdAt: new Date(), appId: app.id, token: token, - }).then(x => AuthSessions.findOneByOrFail(x.identifiers[0])); + }).then((x) => AuthSessions.findOneByOrFail(x.identifiers[0])); return { token: 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 3f3a4d142..ba0094180 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -1,48 +1,52 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { AuthSessions } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { AuthSessions } from "@/models/index.js"; export const meta = { - tags: ['auth'], + tags: ["auth"], requireCredential: false, errors: { noSuchSession: { - message: 'No such session.', - code: 'NO_SUCH_SESSION', - id: 'bd72c97d-eba7-4adb-a467-f171b8847250', + message: "No such session.", + code: "NO_SUCH_SESSION", + id: "bd72c97d-eba7-4adb-a467-f171b8847250", }, }, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, app: { - type: 'object', - optional: false, nullable: false, - ref: 'App', + type: "object", + optional: false, + nullable: false, + ref: "App", }, token: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - token: { type: 'string' }, + token: { type: "string" }, }, - required: ['token'], + required: ["token"], } as const; // eslint-disable-next-line import/no-default-export 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 89884ed38..375f62785 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -1,57 +1,60 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { Apps, AuthSessions, AccessTokens, Users } from "@/models/index.js"; export const meta = { - tags: ['auth'], + tags: ["auth"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { accessToken: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, user: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailedNotMe', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailedNotMe", }, }, }, errors: { noSuchApp: { - message: 'No such app.', - code: 'NO_SUCH_APP', - id: 'fcab192a-2c5a-43b7-8ad8-9b7054d8d40d', + message: "No such app.", + code: "NO_SUCH_APP", + id: "fcab192a-2c5a-43b7-8ad8-9b7054d8d40d", }, noSuchSession: { - message: 'No such session.', - code: 'NO_SUCH_SESSION', - id: '5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3', + message: "No such session.", + code: "NO_SUCH_SESSION", + id: "5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3", }, pendingSession: { - message: 'This session is not completed yet.', - code: 'PENDING_SESSION', - id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e', + message: "This session is not completed yet.", + code: "PENDING_SESSION", + id: "8c8a4145-02cc-4cca-8e66-29ba60445a8e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - appSecret: { type: 'string' }, - token: { type: 'string' }, + appSecret: { type: "string" }, + token: { type: "string" }, }, - required: ['appSecret', 'token'], + required: ["appSecret", "token"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 4e88f32fb..f2f7bc7b7 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,12 +1,12 @@ -import create from '@/services/blocking/create.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, NoteWatchings, Users } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import create from "@/services/blocking/create.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getUser } from "../../common/getters.js"; +import { Blockings, NoteWatchings, Users } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['account'], + tags: ["account"], limit: { duration: HOUR, @@ -15,41 +15,42 @@ export const meta = { requireCredential: true, - kind: 'write:blocks', + kind: "write:blocks", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '7cc4f851-e2f1-4621-9633-ec9e1d00c01e', + message: "No such user.", + code: "NO_SUCH_USER", + id: "7cc4f851-e2f1-4621-9633-ec9e1d00c01e", }, blockeeIsYourself: { - message: 'Blockee is yourself.', - code: 'BLOCKEE_IS_YOURSELF', - id: '88b19138-f28d-42c0-8499-6a31bbd0fdc6', + message: "Blockee is yourself.", + code: "BLOCKEE_IS_YOURSELF", + id: "88b19138-f28d-42c0-8499-6a31bbd0fdc6", }, alreadyBlocking: { - message: 'You are already blocking that user.', - code: 'ALREADY_BLOCKING', - id: '787fed64-acb9-464a-82eb-afbd745b9614', + message: "You are already blocking that user.", + code: "ALREADY_BLOCKING", + id: "787fed64-acb9-464a-82eb-afbd745b9614", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailedNotMe', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailedNotMe", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -62,8 +63,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const blockee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 37215bbd6..fde20dce4 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,12 +1,12 @@ -import deleteBlocking from '@/services/blocking/delete.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; -import { Blockings, Users } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import deleteBlocking from "@/services/blocking/delete.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getUser } from "../../common/getters.js"; +import { Blockings, Users } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['account'], + tags: ["account"], limit: { duration: HOUR, @@ -15,41 +15,42 @@ export const meta = { requireCredential: true, - kind: 'write:blocks', + kind: "write:blocks", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '8621d8bf-c358-4303-a066-5ea78610eb3f', + message: "No such user.", + code: "NO_SUCH_USER", + id: "8621d8bf-c358-4303-a066-5ea78610eb3f", }, blockeeIsYourself: { - message: 'Blockee is yourself.', - code: 'BLOCKEE_IS_YOURSELF', - id: '06f6fac6-524b-473c-a354-e97a40ae6eac', + message: "Blockee is yourself.", + code: "BLOCKEE_IS_YOURSELF", + id: "06f6fac6-524b-473c-a354-e97a40ae6eac", }, notBlocking: { - message: 'You are not blocking that user.', - code: 'NOT_BLOCKING', - id: '291b2efa-60c6-45c0-9f6a-045c8f9b02cd', + message: "You are not blocking that user.", + code: "NOT_BLOCKING", + id: "291b2efa-60c6-45c0-9f6a-045c8f9b02cd", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailedNotMe', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailedNotMe", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -62,8 +63,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get blockee - const blockee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const blockee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 29095ebe2..718ed14ea 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -1,43 +1,46 @@ -import define from '../../define.js'; -import { Blockings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Blockings } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'read:blocks', + kind: "read:blocks", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Blocking', + type: "object", + optional: false, + nullable: false, + ref: "Blocking", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 query = makePaginationQuery( + Blockings.createQueryBuilder("blocking"), + ps.sinceId, + ps.untilId, + ).andWhere("blocking.blockerId = :meId", { meId: me.id }); - const blockings = await query - .take(ps.limit) - .getMany(); + const blockings = await query.take(ps.limit).getMany(); return await Blockings.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 94dcfe502..ec13215d5 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -1,39 +1,45 @@ -import define from '../../define.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'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Channels, DriveFiles } from "@/models/index.js"; +import type { Channel } from "@/models/entities/channel.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { - tags: ['channels'], + tags: ["channels"], requireCredential: true, - kind: 'write:channels', + kind: "write:channels", res: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 128 }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, - bannerId: { type: 'string', format: 'misskey:id', nullable: true }, + name: { type: "string", minLength: 1, maxLength: 128 }, + description: { + type: "string", + nullable: true, + minLength: 1, + maxLength: 2048, + }, + bannerId: { type: "string", format: "misskey:id", nullable: true }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -57,7 +63,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, description: ps.description || null, bannerId: banner ? banner.id : null, - } as Channel).then(x => Channels.findOneByOrFail(x.identifiers[0])); + } as Channel).then((x) => Channels.findOneByOrFail(x.identifiers[0])); return await Channels.pack(channel, user); }); diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 13ad6ca7d..15e08db33 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -1,36 +1,38 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; +import define from "../../define.js"; +import { Channels } from "@/models/index.js"; export const meta = { - tags: ['channels'], + tags: ["channels"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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'); + const query = Channels.createQueryBuilder("channel") + .where("channel.lastNotedAt IS NOT NULL") + .orderBy("channel.lastNotedAt", "DESC"); const channels = await query.take(10).getMany(); - return await Promise.all(channels.map(x => Channels.pack(x, me))); + return await Promise.all(channels.map((x) => Channels.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 895ffed0b..1dd4f7f92 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -1,31 +1,31 @@ -import define from '../../define.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'; +import define from "../../define.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'], + tags: ["channels"], requireCredential: true, - kind: 'write:channels', + kind: "write:channels", errors: { noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: 'c0031718-d573-4e85-928e-10039f1fbb68', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "c0031718-d573-4e85-928e-10039f1fbb68", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - channelId: { type: 'string', format: 'misskey:id' }, + channelId: { type: "string", format: "misskey:id" }, }, - required: ['channelId'], + required: ["channelId"], } as const; // eslint-disable-next-line import/no-default-export @@ -45,5 +45,5 @@ export default define(meta, paramDef, async (ps, user) => { followeeId: channel.id, }); - publishUserEvent(user.id, 'followChannel', channel); + publishUserEvent(user.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 e4aa4d161..1c91312a6 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -1,43 +1,48 @@ -import define from '../../define.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Channels, ChannelFollowings } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['channels', 'account'], + tags: ["channels", "account"], requireCredential: true, - kind: 'read:channels', + kind: "read:channels", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 5 }, }, required: [], } 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 query = makePaginationQuery( + ChannelFollowings.createQueryBuilder(), + ps.sinceId, + ps.untilId, + ).andWhere({ followerId: me.id }); - const followings = await query - .take(ps.limit) - .getMany(); + const followings = await query.take(ps.limit).getMany(); - return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me))); + return await Promise.all( + followings.map((x) => Channels.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 ed7e41cac..25abfdbb7 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -1,43 +1,46 @@ -import define from '../../define.js'; -import { Channels } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Channels } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['channels', 'account'], + tags: ["channels", "account"], requireCredential: true, - kind: 'read:channels', + kind: "read:channels", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 5 }, }, required: [], } 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 query = makePaginationQuery( + Channels.createQueryBuilder(), + ps.sinceId, + ps.untilId, + ).andWhere({ userId: me.id }); - const channels = await query - .take(ps.limit) - .getMany(); + const channels = await query.take(ps.limit).getMany(); - return await Promise.all(channels.map(x => Channels.pack(x, me))); + return await Promise.all(channels.map((x) => Channels.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 1c8461af4..54db43c6a 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -1,34 +1,35 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Channels } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Channels } from "@/models/index.js"; export const meta = { - tags: ['channels'], + tags: ["channels"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, errors: { noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: '6f6c314b-7486-4897-8966-c04a66a02923', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "6f6c314b-7486-4897-8966-c04a66a02923", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - channelId: { type: 'string', format: 'misskey:id' }, + channelId: { type: "string", format: "misskey:id" }, }, - required: ['channelId'], + required: ["channelId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 18ba6b2e3..8196753c6 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,45 +1,47 @@ -import define from '../../define.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'; +import define from "../../define.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'], + tags: ["notes", "channels"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - channelId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + channelId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, - required: ['channelId'], + required: ["channelId"], } as const; // eslint-disable-next-line import/no-default-export @@ -53,20 +55,26 @@ export default define(meta, paramDef, async (ps, user) => { } //#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'); + 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 const timeline = await query.take(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index e065d897a..66f661b05 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -1,30 +1,30 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Channels, ChannelFollowings } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Channels, ChannelFollowings } from "@/models/index.js"; +import { publishUserEvent } from "@/services/stream.js"; export const meta = { - tags: ['channels'], + tags: ["channels"], requireCredential: true, - kind: 'write:channels', + kind: "write:channels", errors: { noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "19959ee9-0153-4c51-bbd9-a98c49dc59d6", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - channelId: { type: 'string', format: 'misskey:id' }, + channelId: { type: "string", format: "misskey:id" }, }, - required: ['channelId'], + required: ["channelId"], } as const; // eslint-disable-next-line import/no-default-export @@ -42,5 +42,5 @@ export default define(meta, paramDef, async (ps, user) => { followeeId: channel.id, }); - publishUserEvent(user.id, 'unfollowChannel', channel); + 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 13104f324..802cce355 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -1,50 +1,56 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Channels, DriveFiles } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Channels, DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['channels'], + tags: ["channels"], requireCredential: true, - kind: 'write:channels', + kind: "write:channels", res: { - type: 'object', - optional: false, nullable: false, - ref: 'Channel', + type: "object", + optional: false, + nullable: false, + ref: "Channel", }, errors: { noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: 'f9c5467f-d492-4c3c-9a8d-a70dacc86512', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "f9c5467f-d492-4c3c-9a8d-a70dacc86512", }, accessDenied: { - message: 'You do not have edit privilege of the channel.', - code: 'ACCESS_DENIED', - id: '1fb7cb09-d46a-4fdf-b8df-057788cce513', + message: "You do not have edit privilege of the channel.", + code: "ACCESS_DENIED", + id: "1fb7cb09-d46a-4fdf-b8df-057788cce513", }, noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "e86c14a4-0da2-4032-8df3-e737a04c7f3b", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - channelId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 128 }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, - bannerId: { type: 'string', format: 'misskey:id', nullable: true }, + channelId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 128 }, + description: { + type: "string", + nullable: true, + minLength: 1, + maxLength: 2048, + }, + bannerId: { type: "string", format: "misskey:id", nullable: true }, }, - required: ['channelId'], + required: ["channelId"], } as const; // eslint-disable-next-line import/no-default-export 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 216676020..941f106f5 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { activeUsersChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts', 'users'], + tags: ["charts", "users"], requireCredentialPrivateMode: true, res: getJsonSchema(activeUsersChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 a8f6e4564..532c101fc 100644 --- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts +++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { apRequestChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { apRequestChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts'], + tags: ["charts"], requireCredentialPrivateMode: true, res: getJsonSchema(apRequestChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 14f82e39d..aa75f7732 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { driveChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { driveChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts', 'drive'], + tags: ["charts", "drive"], requireCredentialPrivateMode: true, res: getJsonSchema(driveChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 141e005ee..d53dd073c 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { federationChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { federationChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts'], + tags: ["charts"], requireCredentialPrivateMode: true, res: getJsonSchema(federationChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 d34153bc1..b4d43ce4b 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { hashtagChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { hashtagChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts', 'hashtags'], + tags: ["charts", "hashtags"], requireCredentialPrivateMode: true, res: getJsonSchema(hashtagChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - tag: { type: 'string' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + tag: { type: "string" }, }, - required: ['span', 'tag'], + required: ["span", "tag"], } 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); + return await 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 3d9619d24..8a246706b 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { instanceChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { instanceChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts'], + tags: ["charts"], requireCredentialPrivateMode: true, res: getJsonSchema(instanceChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - host: { type: 'string' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + host: { type: "string" }, }, - required: ['span', 'host'], + required: ["span", "host"], } 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); + return await 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 42befed27..50bca79be 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { notesChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { notesChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts', 'notes'], + tags: ["charts", "notes"], requireCredentialPrivateMode: true, res: getJsonSchema(notesChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 cb73b4ac9..c8636eacc 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserDriveChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { perUserDriveChart } from "@/services/chart/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['charts', 'drive', 'users'], + tags: ["charts", "drive", "users"], requireCredentialPrivateMode: true, res: getJsonSchema(perUserDriveChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - userId: { type: 'string', format: 'misskey:id' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['span', 'userId'], + required: ["span", "userId"], } 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); + return await 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 697a5f37a..c89e5ffdb 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -1,9 +1,9 @@ -import define from '../../../define.js'; -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserFollowingChart } from '@/services/chart/index.js'; +import define from "../../../define.js"; +import { getJsonSchema } from "@/services/chart/core.js"; +import { perUserFollowingChart } from "@/services/chart/index.js"; export const meta = { - tags: ['charts', 'users', 'following'], + tags: ["charts", "users", "following"], requireCredentialPrivateMode: true, res: getJsonSchema(perUserFollowingChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - userId: { type: 'string', format: 'misskey:id' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['span', 'userId'], + required: ["span", "userId"], } 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); + return await 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 5b576754d..4f710ee7e 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserNotesChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { perUserNotesChart } from "@/services/chart/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['charts', 'users', 'notes'], + tags: ["charts", "users", "notes"], requireCredentialPrivateMode: true, res: getJsonSchema(perUserNotesChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - userId: { type: 'string', format: 'misskey:id' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['span', 'userId'], + required: ["span", "userId"], } 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); + return await 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 61c4527b9..ac366ceb0 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { perUserReactionsChart } from '@/services/chart/index.js'; -import define from '../../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { perUserReactionsChart } from "@/services/chart/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['charts', 'users', 'reactions'], + tags: ["charts", "users", "reactions"], requireCredentialPrivateMode: true, res: getJsonSchema(perUserReactionsChart.schema), @@ -13,17 +13,22 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, - userId: { type: 'string', format: 'misskey:id' }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['span', 'userId'], + required: ["span", "userId"], } 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); + return await 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 0c799287c..de35e04ad 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -1,9 +1,9 @@ -import { getJsonSchema } from '@/services/chart/core.js'; -import { usersChart } from '@/services/chart/index.js'; -import define from '../../define.js'; +import { getJsonSchema } from "@/services/chart/core.js"; +import { usersChart } from "@/services/chart/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['charts', 'users'], + tags: ["charts", "users"], requireCredentialPrivateMode: true, res: getJsonSchema(usersChart.schema), @@ -13,16 +13,20 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - span: { type: 'string', enum: ['day', 'hour'] }, - limit: { type: 'integer', minimum: 1, maximum: 500, default: 30 }, - offset: { type: 'integer', nullable: true, default: null }, + span: { type: "string", enum: ["day", "hour"] }, + limit: { type: "integer", minimum: 1, maximum: 500, default: 30 }, + offset: { type: "integer", nullable: true, default: null }, }, - required: ['span'], + required: ["span"], } 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); + return await 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 91baa8eb7..ef36c34e3 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -1,44 +1,44 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; -import { ApiError } from '../../error.js'; -import { genId } from '@/misc/gen-id.js'; -import { getNote } from '../../common/getters.js'; +import define from "../../define.js"; +import { ClipNotes, Clips } from "@/models/index.js"; +import { ApiError } from "../../error.js"; +import { genId } from "@/misc/gen-id.js"; +import { getNote } from "../../common/getters.js"; export const meta = { - tags: ['account', 'notes', 'clips'], + tags: ["account", "notes", "clips"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf', + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "d6e76cc0-a1b5-4c7c-a287-73fa9c716dcf", }, noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "fc8c0b49-c7a3-4664-a0a6-b418d386bb8b", }, alreadyClipped: { - message: 'The note has already been clipped.', - code: 'ALREADY_CLIPPED', - id: '734806c4-542c-463a-9311-15c512803965', + message: "The note has already been clipped.", + code: "ALREADY_CLIPPED", + id: "734806c4-542c-463a-9311-15c512803965", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clipId: { type: 'string', format: 'misskey:id' }, - noteId: { type: 'string', format: 'misskey:id' }, + clipId: { type: "string", format: "misskey:id" }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['clipId', 'noteId'], + required: ["clipId", "noteId"], } as const; // eslint-disable-next-line import/no-default-export @@ -52,8 +52,9 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchClip); } - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index 4afe4222a..bac716b38 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -1,29 +1,35 @@ -import define from '../../define.js'; -import { genId } from '@/misc/gen-id.js'; -import { Clips } from '@/models/index.js'; +import define from "../../define.js"; +import { genId } from "@/misc/gen-id.js"; +import { Clips } from "@/models/index.js"; export const meta = { - tags: ['clips'], + tags: ["clips"], requireCredential: true, - kind: 'write:account', + kind: "write:account", res: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 100 }, - isPublic: { type: 'boolean', default: false }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, + name: { type: "string", minLength: 1, maxLength: 100 }, + isPublic: { type: "boolean", default: false }, + description: { + type: "string", + nullable: true, + minLength: 1, + maxLength: 2048, + }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -35,7 +41,7 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, isPublic: ps.isPublic, description: ps.description, - }).then(x => Clips.findOneByOrFail(x.identifiers[0])); + }).then((x) => Clips.findOneByOrFail(x.identifiers[0])); return await Clips.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 b6c0eb702..2aac8b97a 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -1,29 +1,29 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Clips } from "@/models/index.js"; export const meta = { - tags: ['clips'], + tags: ["clips"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: '70ca08ba-6865-4630-b6fb-8494759aa754', + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "70ca08ba-6865-4630-b6fb-8494759aa754", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clipId: { type: 'string', format: 'misskey:id' }, + clipId: { type: "string", format: "misskey:id" }, }, - required: ['clipId'], + required: ["clipId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 378811eba..ea4f64053 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -1,26 +1,28 @@ -import define from '../../define.js'; -import { Clips } from '@/models/index.js'; +import define from "../../define.js"; +import { Clips } from "@/models/index.js"; export const meta = { - tags: ['clips', 'account'], + tags: ["clips", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -31,5 +33,5 @@ export default define(meta, paramDef, async (ps, me) => { userId: me.id, }); - return await Promise.all(clips.map(x => Clips.pack(x))); + return await Promise.all(clips.map((x) => Clips.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 eea6f0a0d..bc8a77ea2 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -1,47 +1,49 @@ -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 { ApiError } from '../../error.js'; -import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; +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 { ApiError } from "../../error.js"; +import { generateBlockedUserQuery } from "../../common/generate-block-query.js"; export const meta = { - tags: ['account', 'notes', 'clips'], + tags: ["account", "notes", "clips"], requireCredential: false, requireCredentialPrivateMode: true, - kind: 'read:account', + kind: "read:account", errors: { noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "1d7645e6-2b6d-4635-b0fe-fe22b0e72e00", }, }, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clipId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + clipId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['clipId'], + required: ["clipId"], } as const; // eslint-disable-next-line import/no-default-export @@ -54,24 +56,32 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchClip); } - if (!clip.isPublic && (user == null || (clip.userId !== user.id))) { + if (!clip.isPublic && (user == null || clip.userId !== user.id)) { throw new ApiError(meta.errors.noSuchClip); } - 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 }); + 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); @@ -79,9 +89,7 @@ export default define(meta, paramDef, async (ps, user) => { generateBlockedUserQuery(query, user); } - const notes = await query - .take(ps.limit) - .getMany(); + const notes = await query.take(ps.limit).getMany(); return await Notes.packMany(notes, user); }); diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 8b90e31f6..925086f47 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -1,57 +1,58 @@ -import define from '../../define.js'; -import { ClipNotes, Clips } from '@/models/index.js'; -import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; - -export const meta = { - tags: ['account', 'notes', 'clips'], - - requireCredential: true, - - kind: 'write:account', - - errors: { - noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', - }, - - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'aff017de-190e-434b-893e-33a9ff5049d8', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - clipId: { type: 'string', format: 'misskey:id' }, - noteId: { type: 'string', format: 'misskey:id' }, - }, - required: ['clipId', 'noteId'], -} 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); - } - - 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 ClipNotes.delete({ - noteId: note.id, - clipId: clip.id, - }); -}); +import define from "../../define.js"; +import { ClipNotes, Clips } from "@/models/index.js"; +import { ApiError } from "../../error.js"; +import { getNote } from "../../common/getters.js"; + +export const meta = { + tags: ["account", "notes", "clips"], + + requireCredential: true, + + kind: "write:account", + + errors: { + noSuchClip: { + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "b80525c6-97f7-49d7-a42d-ebccd49cfd52", + }, + + noSuchNote: { + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "aff017de-190e-434b-893e-33a9ff5049d8", + }, + }, +} as const; + +export const paramDef = { + type: "object", + properties: { + clipId: { type: "string", format: "misskey:id" }, + noteId: { type: "string", format: "misskey:id" }, + }, + required: ["clipId", "noteId"], +} 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); + } + + 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 ClipNotes.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 aec4c1253..870ad6a42 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -1,36 +1,37 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Clips } from "@/models/index.js"; export const meta = { - tags: ['clips', 'account'], + tags: ["clips", "account"], requireCredential: false, requireCredentialPrivateMode: true, - kind: 'read:account', + kind: "read:account", errors: { noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20', + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "c3c5fe33-d62c-44d2-9ea5-d997703f5c20", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clipId: { type: 'string', format: 'misskey:id' }, + clipId: { type: "string", format: "misskey:id" }, }, - required: ['clipId'], + required: ["clipId"], } as const; // eslint-disable-next-line import/no-default-export @@ -44,7 +45,7 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchClip); } - if (!clip.isPublic && (me == null || (clip.userId !== me.id))) { + if (!clip.isPublic && (me == null || clip.userId !== me.id)) { throw new ApiError(meta.errors.noSuchClip); } diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index b67d844f6..e16bf197c 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -1,38 +1,44 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Clips } from '@/models/index.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Clips } from "@/models/index.js"; export const meta = { - tags: ['clips'], + tags: ["clips"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchClip: { - message: 'No such clip.', - code: 'NO_SUCH_CLIP', - id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', + message: "No such clip.", + code: "NO_SUCH_CLIP", + id: "b4d92d70-b216-46fa-9a3f-a8c811699257", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clipId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 100 }, - isPublic: { type: 'boolean' }, - description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, + clipId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 100 }, + isPublic: { type: "boolean" }, + description: { + type: "string", + nullable: true, + minLength: 1, + maxLength: 2048, + }, }, - required: ['clipId', 'name'], + required: ["clipId", "name"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts b/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts index 42969cff9..e3ff9eb0a 100644 --- a/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts @@ -1,19 +1,19 @@ -import { Emojis } from '@/models/index.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { IsNull, In } from 'typeorm'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; -import define from '../../define.js'; +import { Emojis } from "@/models/index.js"; +import type { Emoji } from "@/models/entities/emoji.js"; +import { IsNull, In } from "typeorm"; +import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; +import define from "../../define.js"; export const meta = { requireCredential: false, requireCredentialPrivateMode: true, allowGet: true, - tags: ['meta'], + tags: ["meta"], } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -23,10 +23,10 @@ export default define(meta, paramDef, async () => { const now = Date.now(); const emojis: Emoji[] = await Emojis.find({ where: { host: IsNull(), type: In(FILE_TYPE_BROWSERSAFE) }, - select: ['name', 'originalUrl', 'publicUrl', 'category'], + select: ["name", "originalUrl", "publicUrl", "category"], }); - const emojiList = emojis.map(emoji => ({ + const emojiList = emojis.map((emoji) => ({ shortcode: emoji.name, url: emoji.originalUrl, static_url: emoji.publicUrl, diff --git a/packages/backend/src/server/api/endpoints/compatibility/instance-info.ts b/packages/backend/src/server/api/endpoints/compatibility/instance-info.ts index f73d8bf82..89a7600a4 100644 --- a/packages/backend/src/server/api/endpoints/compatibility/instance-info.ts +++ b/packages/backend/src/server/api/endpoints/compatibility/instance-info.ts @@ -1,24 +1,31 @@ -import * as mfm from 'mfm-js'; -import { toHtml } from '@/mfm/to-html.js'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, Notes, Instances, UserProfiles, Emojis, DriveFiles } from '@/models/index.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { User } from '@/models/entities/user.js'; -import { IsNull, In } from 'typeorm'; -import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from '@/const.js'; -import define from '../../define.js'; +import * as mfm from "mfm-js"; +import { toHtml } from "@/mfm/to-html.js"; +import config from "@/config/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { + Users, + Notes, + Instances, + UserProfiles, + Emojis, + DriveFiles, +} from "@/models/index.js"; +import type { Emoji } from "@/models/entities/emoji.js"; +import type { User } from "@/models/entities/user.js"; +import { IsNull, In } from "typeorm"; +import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js"; +import define from "../../define.js"; export const meta = { requireCredential: false, requireCredentialPrivateMode: true, allowGet: true, - tags: ['meta'], + tags: ["meta"], } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -26,97 +33,89 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const now = Date.now(); - const [ - meta, - total, - localPosts, - instanceCount, - firstAdmin, - emojis, - ] = await Promise.all([ - fetchMeta(true), - Users.count({ where: { host: IsNull() } }), - Notes.count({ where: { userHost: IsNull(), replyId: IsNull() } }), - Instances.count(), - Users.findOne({ - where: { host: IsNull(), isAdmin: true, isDeleted: false, isBot: false }, - order: { id: 'ASC' }, - }), - Emojis.find({ - where: { host: IsNull(), type: In(FILE_TYPE_BROWSERSAFE) }, - select: ['id', 'name', 'originalUrl', 'publicUrl'], - }).then(l => - l.reduce((a, e) => - { - a[e.name] = e - return a + const [meta, total, localPosts, instanceCount, firstAdmin, emojis] = + await Promise.all([ + fetchMeta(true), + Users.count({ where: { host: IsNull() } }), + Notes.count({ where: { userHost: IsNull(), replyId: IsNull() } }), + Instances.count(), + Users.findOne({ + where: { + host: IsNull(), + isAdmin: true, + isDeleted: false, + isBot: false, }, - {} as Record - ) - ), - ]); + order: { id: "ASC" }, + }), + Emojis.find({ + where: { host: IsNull(), type: In(FILE_TYPE_BROWSERSAFE) }, + select: ["id", "name", "originalUrl", "publicUrl"], + }).then((l) => + l.reduce((a, e) => { + a[e.name] = e; + return a; + }, {} as Record), + ), + ]); - let descSplit = splitN(meta.description, '\n', 2); - let shortDesc = markup(descSplit.length > 0 ? descSplit[0]: ''); - let longDesc = markup(meta.description ?? ''); + const descSplit = splitN(meta.description, "\n", 2); + const shortDesc = markup(descSplit.length > 0 ? descSplit[0] : ""); + const longDesc = markup(meta.description ?? ""); return { - "uri": config.hostname, - "title": meta.name, - "short_description": shortDesc, - "description": longDesc, - "email": meta.maintainerEmail, - "version": config.version, - "urls": { - "streaming_api": `wss://${config.host}` + uri: config.hostname, + title: meta.name, + short_description: shortDesc, + description: longDesc, + email: meta.maintainerEmail, + version: config.version, + urls: { + streaming_api: `wss://${config.host}`, + }, + stats: { + user_count: total, + status_count: localPosts, + domain_count: instanceCount, + }, + thumbnail: meta.logoImageUrl, + languages: meta.langs, + registrations: !meta.disableRegistration, + approval_required: false, + invites_enabled: false, + configuration: { + accounts: { + max_featured_tags: 16, }, - "stats": { - "user_count": total, - "status_count": localPosts, - "domain_count": instanceCount + statuses: { + max_characters: MAX_NOTE_TEXT_LENGTH, + max_media_attachments: 16, + characters_reserved_per_url: 0, }, - "thumbnail": meta.logoImageUrl, - "languages": meta.langs, - "registrations": !meta.disableRegistration, - "approval_required": false, - "invites_enabled": false, - "configuration": { - "accounts": { - "max_featured_tags": 16 - }, - "statuses": { - "max_characters": MAX_NOTE_TEXT_LENGTH, - "max_media_attachments": 16, - "characters_reserved_per_url": 0 - }, - "media_attachments": { - "supported_mime_types": FILE_TYPE_BROWSERSAFE, - "image_size_limit": 10485760, - "image_matrix_limit": 16777216, - "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 2304000 - }, - "polls": { - "max_options": 10, - "max_characters_per_option": 50, - "min_expiration": 15, - "max_expiration": -1 - } + media_attachments: { + supported_mime_types: FILE_TYPE_BROWSERSAFE, + image_size_limit: 10485760, + image_matrix_limit: 16777216, + video_size_limit: 41943040, + video_frame_rate_limit: 60, + video_matrix_limit: 2304000, }, - "contact_account": await getContact(firstAdmin, emojis), - "rules": [] + polls: { + max_options: 10, + max_characters_per_option: 50, + min_expiration: 15, + max_expiration: -1, + }, + }, + contact_account: await getContact(firstAdmin, emojis), + rules: [], }; }); -const splitN = ( - s: string | null, - split: string, - n: number -): string[] => { +const splitN = (s: string | null, split: string, n: number): string[] => { const ret: string[] = []; if (s == null) return ret; - if (s === '') { + if (s === "") { ret.push(s); return ret; } @@ -149,7 +148,7 @@ type ContactType = { fields?: { name: string; value: string; - verified_at:string | null; + verified_at: string | null; }[]; locked: boolean; bot: boolean; @@ -164,7 +163,7 @@ type ContactType = { const getContact = async ( user: User | null, - emojis: Record + emojis: Record, ): Promise => { if (!user) return null; @@ -181,18 +180,22 @@ const getContact = async ( following_count: user.followingCount, statuses_count: user.notesCount, last_status_at: user.lastActiveDate?.toISOString(), - emojis: emojis ? user.emojis.filter((e, i, a) => e in emojis && a.indexOf(e) == i).map(e => ({ - shortcode: e, - static_url: emojis[e].publicUrl, - url: emojis[e].originalUrl, - visible_in_picker: true, - })) : [], + emojis: emojis + ? user.emojis + .filter((e, i, a) => e in emojis && a.indexOf(e) === i) + .map((e) => ({ + shortcode: e, + static_url: emojis[e].publicUrl, + url: emojis[e].originalUrl, + visible_in_picker: true, + })) + : [], }; const [profile] = await Promise.all([ - UserProfiles.findOne({ where: { userId: user.id }}), - loadDriveFiles(contact, 'avatar', user.avatarId), - loadDriveFiles(contact, 'header', user.bannerId), + UserProfiles.findOne({ where: { userId: user.id } }), + loadDriveFiles(contact, "avatar", user.avatarId), + loadDriveFiles(contact, "header", user.bannerId), ]); if (!profile) { @@ -201,19 +204,23 @@ const getContact = async ( contact = { ...contact, - note: markup(profile.description ?? ''), + note: markup(profile.description ?? ""), noindex: profile.noCrawle, - fields: profile.fields.map(f => ({ + fields: profile.fields.map((f) => ({ name: f.name, value: f.value, verified_at: null, - })) + })), }; return contact; -} +}; -const loadDriveFiles = async (contact: any, key: string, fileId: string | null) => { +const loadDriveFiles = async ( + contact: any, + key: string, + fileId: string | null, +) => { if (fileId) { const file = await DriveFiles.findOneBy({ id: fileId }); if (file) { @@ -221,6 +228,6 @@ const loadDriveFiles = async (contact: any, key: string, fileId: string | null) contact[`${key}_static`] = contact[key]; } } -} +}; -const markup = (text: string): string => toHtml(mfm.parse(text)) ?? ''; +const markup = (text: string): string => toHtml(mfm.parse(text)) ?? ""; diff --git a/packages/backend/src/server/api/endpoints/custom-motd.ts b/packages/backend/src/server/api/endpoints/custom-motd.ts index fd58424bd..b9d463f0f 100644 --- a/packages/backend/src/server/api/endpoints/custom-motd.ts +++ b/packages/backend/src/server/api/endpoints/custom-motd.ts @@ -1,25 +1,27 @@ // import { IsNull } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import define from '../define.js'; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -27,6 +29,6 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const meta = await fetchMeta(); - const motd = await Promise.all(meta.customMOTD.map(x => x)); + const motd = await Promise.all(meta.customMOTD.map((x) => x)); return motd; }); diff --git a/packages/backend/src/server/api/endpoints/custom-splash-icons.ts b/packages/backend/src/server/api/endpoints/custom-splash-icons.ts index 380e2131b..cdbd76dd8 100644 --- a/packages/backend/src/server/api/endpoints/custom-splash-icons.ts +++ b/packages/backend/src/server/api/endpoints/custom-splash-icons.ts @@ -1,25 +1,27 @@ // import { IsNull } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import define from '../define.js'; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -27,6 +29,6 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const meta = await fetchMeta(); - const icons = await Promise.all(meta.customSplashIcons.map(x => x)); + const icons = await Promise.all(meta.customSplashIcons.map((x) => x)); return icons; }); diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 82497adef..a52aec318 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -1,32 +1,35 @@ -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { DriveFiles } from '@/models/index.js'; -import define from '../define.js'; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { DriveFiles } from "@/models/index.js"; +import define from "../define.js"; export const meta = { - tags: ['drive', 'account'], + tags: ["drive", "account"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { capacity: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, usage: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -39,7 +42,10 @@ export default define(meta, paramDef, async (ps, user) => { const usage = await DriveFiles.calcDriveUsageOf(user.id); return { - capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb), + capacity: + 1024 * + 1024 * + (user.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 40e6c16c9..ee189ecd1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -1,53 +1,69 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { DriveFiles } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, - type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, + type: { + type: "string", + nullable: true, + pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1), + }, }, required: [], } 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 }); + 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 }); + query.andWhere("file.folderId = :folderId", { folderId: ps.folderId }); } else { - query.andWhere('file.folderId IS NULL'); + query.andWhere("file.folderId IS NULL"); } if (ps.type) { - if (ps.type.endsWith('/*')) { - query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + if (ps.type.endsWith("/*")) { + query.andWhere("file.type like :type", { + type: `${ps.type.replace("/*", "/")}%`, + }); } else { - query.andWhere('file.type = :type', { type: ps.type }); + query.andWhere("file.type = :type", { type: ps.type }); } } 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 415a8cc69..0ba74f6b6 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,41 +1,43 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { DriveFiles, Notes } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFiles, Notes } from "@/models/index.js"; export const meta = { - tags: ['drive', 'notes'], + tags: ["drive", "notes"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", - description: 'Find the notes to which the given file is attached.', + description: "Find the notes to which the given file is attached.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'c118ece3-2e4b-4296-99d1-51756e32d232', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "c118ece3-2e4b-4296-99d1-51756e32d232", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export @@ -50,8 +52,8 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - const notes = await Notes.createQueryBuilder('note') - .where(':file = ANY(note.fileIds)', { file: file.id }) + const notes = await Notes.createQueryBuilder("note") + .where(":file = ANY(note.fileIds)", { file: file.id }) .getMany(); return await Notes.packMany(notes, user, { diff --git a/packages/backend/src/server/api/endpoints/drive/files/caption-image.ts b/packages/backend/src/server/api/endpoints/drive/files/caption-image.ts index 81455a501..7fc510f27 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/caption-image.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/caption-image.ts @@ -1,40 +1,42 @@ -import define from '../../../define.js'; -import { createWorker } from 'tesseract.js'; +import define from "../../../define.js"; +import { createWorker } from "tesseract.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", - description: 'Return caption of image', + description: "Return caption of image", res: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - url: { type: 'string' }, + url: { type: "string" }, }, - required: ['url'], + required: ["url"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { - const worker = createWorker({ - logger: m => console.log(m) + logger: (m) => console.log(m), }); await worker.load(); - await worker.loadLanguage('eng'); - await worker.initialize('eng'); - const { data: { text } } = await worker.recognize(ps.url); + await worker.loadLanguage("eng"); + await worker.initialize("eng"); + const { + data: { text }, + } = await worker.recognize(ps.url); await worker.terminate(); return text; 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 bbae9bf4e..a57564266 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,27 +1,28 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; +import define from "../../../define.js"; +import { DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", - description: 'Check if a given file exists.', + description: "Check if a given file exists.", res: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - md5: { type: 'string' }, + md5: { type: "string" }, }, - required: ['md5'], + required: ["md5"], } as const; // eslint-disable-next-line import/no-default-export 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 054ce8a1f..32225dfb1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,15 +1,15 @@ -import { addFile } from '@/services/drive/add-file.js'; -import { DriveFiles } 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 { HOUR } from '@/const.js'; -import define from '../../../define.js'; -import { apiLogger } from '../../../logger.js'; -import { ApiError } from '../../../error.js'; +import { addFile } from "@/services/drive/add-file.js"; +import { DriveFiles } 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 { HOUR } from "@/const.js"; +import define from "../../../define.js"; +import { apiLogger } from "../../../logger.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, @@ -20,92 +20,111 @@ export const meta = { requireFile: true, - kind: 'write:drive', + kind: "write:drive", - description: 'Upload a new drive file.', + description: "Upload a new drive file.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, errors: { invalidFileName: { - message: 'Invalid file name.', - code: 'INVALID_FILE_NAME', - id: 'f449b209-0c60-4e51-84d5-29486263bfd4', + message: "Invalid file name.", + code: "INVALID_FILE_NAME", + id: "f449b209-0c60-4e51-84d5-29486263bfd4", }, inappropriate: { - message: 'Cannot upload the file because it has been determined that it possibly contains inappropriate content.', - code: 'INAPPROPRIATE', - id: 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2', + message: + "Cannot upload the file because it has been determined that it possibly contains inappropriate content.", + code: "INAPPROPRIATE", + id: "bec5bd69-fba3-43c9-b4fb-2894b66ad5d2", }, noFreeSpace: { - message: 'Cannot upload the file because you have no free space of drive.', - code: 'NO_FREE_SPACE', - id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064', + message: + "Cannot upload the file because you have no free space of drive.", + code: "NO_FREE_SPACE", + id: "d08dbc37-a6a9-463a-8c47-96c32ab5f064", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, - name: { type: 'string', nullable: true, default: null }, - comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null }, - isSensitive: { type: 'boolean', default: false }, - force: { type: 'boolean', default: false }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, + name: { type: "string", nullable: true, default: null }, + comment: { + type: "string", + nullable: true, + maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, + default: null, + }, + isSensitive: { type: "boolean", default: false }, + force: { type: "boolean", default: false }, }, required: [], } 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) { +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; - } else if (name === 'blob') { - name = null; - } else if (!DriveFiles.validateFileName(name)) { - throw new ApiError(meta.errors.invalidFileName); } - } else { - name = null; - } - const meta = await fetchMeta(); + const meta = await fetchMeta(); - 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); + 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 { + cleanup!(); } - 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 { - 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 6108ae7da..37e63146f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -1,39 +1,39 @@ -import { deleteFile } from '@/services/drive/delete-file.js'; -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { DriveFiles, Users } from '@/models/index.js'; +import { deleteFile } from "@/services/drive/delete-file.js"; +import { publishDriveStream } from "@/services/stream.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFiles, Users } from "@/models/index.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'write:drive', + kind: "write:drive", - description: 'Delete an existing drive file.', + description: "Delete an existing drive file.", errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: '908939ec-e52b-4458-b395-1025195cea58', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "908939ec-e52b-4458-b395-1025195cea58", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '5eb8d909-2540-4970-90b8-dd6f86088121', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "5eb8d909-2540-4970-90b8-dd6f86088121", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export @@ -44,7 +44,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { + if (!(user.isAdmin || user.isModerator ) && file.userId !== user.id) { throw new ApiError(meta.errors.accessDenied); } @@ -52,5 +52,5 @@ export default define(meta, paramDef, async (ps, user) => { await deleteFile(file); // Publish fileDeleted event - publishDriveStream(user.id, 'fileDeleted', file.id); + publishDriveStream(user.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 f2bc7348c..1bd396848 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,32 +1,34 @@ -import { DriveFiles } from '@/models/index.js'; -import define from '../../../define.js'; +import { DriveFiles } from "@/models/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", - description: 'Search for a drive file by a hash of the contents.', + description: "Search for a drive file by a hash of the contents.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - md5: { type: 'string' }, + md5: { type: "string" }, }, - required: ['md5'], + required: ["md5"], } as const; // eslint-disable-next-line import/no-default-export 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 245fb45a6..fcc7bca03 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -1,34 +1,41 @@ -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { IsNull } from 'typeorm'; +import define from "../../../define.js"; +import { DriveFiles } from "@/models/index.js"; +import { IsNull } from "typeorm"; export const meta = { requireCredential: true, - tags: ['drive'], + tags: ["drive"], - kind: 'read:drive', + kind: "read:drive", - description: 'Search for a drive file by the given parameters.', + description: "Search for a drive file by the given parameters.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, + name: { type: "string" }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -39,5 +46,7 @@ export default define(meta, paramDef, async (ps, user) => { folderId: ps.folderId ?? IsNull(), }); - return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); + return await Promise.all( + files.map((file) => DriveFiles.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 2c604c54c..4147f44dc 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,52 +1,53 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles, Users } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles, Users } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", - description: 'Show the properties of a drive file.', + description: "Show the properties of a drive file.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: '067bc436-2718-4795-b0fb-ecbe43949e31', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "067bc436-2718-4795-b0fb-ecbe43949e31", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '25b73c73-68b1-41d0-bad1-381cfdf6579f', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "25b73c73-68b1-41d0-bad1-381cfdf6579f", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", anyOf: [ { properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], }, { properties: { - url: { type: 'string' }, + url: { type: "string" }, }, - required: ['url'], + required: ["url"], }, ], } as const; @@ -59,13 +60,17 @@ export default define(meta, paramDef, async (ps, user) => { 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, - }], + where: [ + { + url: ps.url, + }, + { + webpublicUrl: ps.url, + }, + { + thumbnailUrl: ps.url, + }, + ], }); } @@ -73,7 +78,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { + if (!(user.isAdmin || user.isModerator ) && file.userId !== user.id) { throw new ApiError(meta.errors.accessDenied); } 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 fa2ec8519..60613641a 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,61 +1,62 @@ -import { publishDriveStream } from '@/services/stream.js'; -import { DriveFiles, DriveFolders, Users } from '@/models/index.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { publishDriveStream } from "@/services/stream.js"; +import { DriveFiles, DriveFolders, Users } from "@/models/index.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'write:drive', + kind: "write:drive", - description: 'Update the properties of a drive file.', + description: "Update the properties of a drive file.", errors: { invalidFileName: { - message: 'Invalid file name.', - code: 'INVALID_FILE_NAME', - id: '395e7156-f9f0-475e-af89-53c3c23080c2', + message: "Invalid file name.", + code: "INVALID_FILE_NAME", + id: "395e7156-f9f0-475e-af89-53c3c23080c2", }, noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'e7778c7e-3af9-49cd-9690-6dbc3e6c972d', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "e7778c7e-3af9-49cd-9690-6dbc3e6c972d", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '01a53b27-82fc-445b-a0c1-b558465a8ed2', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "01a53b27-82fc-445b-a0c1-b558465a8ed2", }, noSuchFolder: { - message: 'No such folder.', - code: 'NO_SUCH_FOLDER', - id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73', + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "ea8fb7a5-af77-4a08-b608-c0218176cd73", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true }, - name: { type: 'string' }, - isSensitive: { type: 'boolean' }, - comment: { type: 'string', nullable: true, maxLength: 512 }, + fileId: { type: "string", format: "misskey:id" }, + folderId: { type: "string", format: "misskey:id", nullable: true }, + name: { type: "string" }, + isSensitive: { type: "boolean" }, + comment: { type: "string", nullable: true, maxLength: 512 }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export @@ -66,7 +67,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchFile); } - if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) { + if (!(user.isAdmin || user.isModerator ) && file.userId !== user.id) { throw new ApiError(meta.errors.accessDenied); } @@ -106,7 +107,7 @@ export default define(meta, paramDef, async (ps, user) => { const fileObj = await DriveFiles.pack(file, { self: true }); // Publish fileUpdated event - publishDriveStream(user.id, 'fileUpdated', fileObj); + publishDriveStream(user.id, "fileUpdated", fileObj); return fileObj; }); diff --git a/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 88a448f21..bbcee8562 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,42 +1,55 @@ -import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; -import define from '../../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { HOUR } from '@/const.js'; +import { uploadFromUrl } from "@/services/drive/upload-from-url.js"; +import define from "../../../define.js"; +import { DriveFiles } from "@/models/index.js"; +import { publishMainStream } from "@/services/stream.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], limit: { duration: HOUR, max: 60, }, - description: 'Request the server to download a new drive file from the specified URL.', + description: + "Request the server to download a new drive file from the specified URL.", requireCredential: true, - kind: 'write:drive', + kind: "write:drive", } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - url: { type: 'string' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, - isSensitive: { type: 'boolean', default: false }, - comment: { type: 'string', nullable: true, maxLength: 512, default: null }, - marker: { type: 'string', nullable: true, default: null }, - force: { type: 'boolean', default: false }, + url: { type: "string" }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, + isSensitive: { type: "boolean", default: false }, + comment: { type: "string", nullable: true, maxLength: 512, default: null }, + marker: { type: "string", nullable: true, default: null }, + force: { type: "boolean", default: false }, }, - required: ['url'], + required: ["url"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { - DriveFiles.pack(file, { self: true }).then(packedFile => { - publishMainStream(user.id, 'urlUploadFinished', { + uploadFromUrl({ + url: ps.url, + user, + folderId: ps.folderId, + sensitive: ps.isSensitive, + force: ps.force, + comment: ps.comment, + }).then((file) => { + DriveFiles.pack(file, { self: true }).then((packedFile) => { + 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 d4d530ba9..98642d8bf 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -1,48 +1,58 @@ -import define from '../../define.js'; -import { DriveFolders } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { DriveFolders } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFolder', + type: "object", + optional: false, + nullable: false, + ref: "DriveFolder", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + folderId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, }, required: [], } 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 }); + 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 }); + query.andWhere("folder.parentId = :parentId", { parentId: ps.folderId }); } else { - query.andWhere('folder.parentId IS NULL'); + query.andWhere("folder.parentId IS NULL"); } const folders = await query.take(ps.limit).getMany(); - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); + return await Promise.all(folders.map((folder) => 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 3d7f514c8..db9703522 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -1,36 +1,37 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import { publishDriveStream } from "@/services/stream.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFolders } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'write:drive', + kind: "write:drive", errors: { noSuchFolder: { - message: 'No such folder.', - code: 'NO_SUCH_FOLDER', - id: '53326628-a00d-40a6-a3cd-8975105c0f95', + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "53326628-a00d-40a6-a3cd-8975105c0f95", }, }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFolder', + type: "object" as const, + optional: false as const, + nullable: false as const, + ref: "DriveFolder", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', default: "Untitled", maxLength: 200 }, - parentId: { type: 'string', format: 'misskey:id', nullable: true }, + name: { type: "string", default: "Untitled", maxLength: 200 }, + parentId: { type: "string", format: "misskey:id", nullable: true }, }, required: [], } as const; @@ -58,12 +59,12 @@ export default define(meta, paramDef, async (ps, user) => { name: ps.name, parentId: parent !== null ? parent.id : null, userId: user.id, - }).then(x => DriveFolders.findOneByOrFail(x.identifiers[0])); + }).then((x) => DriveFolders.findOneByOrFail(x.identifiers[0])); const folderObj = await DriveFolders.pack(folder); // Publish folderCreated event - publishDriveStream(user.id, 'folderCreated', folderObj); + publishDriveStream(user.id, "folderCreated", 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 ab9d411ec..2d03a5c2c 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -1,36 +1,36 @@ -import define from '../../../define.js'; -import { publishDriveStream } from '@/services/stream.js'; -import { ApiError } from '../../../error.js'; -import { DriveFolders, DriveFiles } from '@/models/index.js'; +import define from "../../../define.js"; +import { publishDriveStream } from "@/services/stream.js"; +import { ApiError } from "../../../error.js"; +import { DriveFolders, DriveFiles } from "@/models/index.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'write:drive', + kind: "write:drive", errors: { noSuchFolder: { - message: 'No such folder.', - code: 'NO_SUCH_FOLDER', - id: '1069098f-c281-440f-b085-f9932edbe091', + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "1069098f-c281-440f-b085-f9932edbe091", }, hasChildFilesOrFolders: { - message: 'This folder has child files or folders.', - code: 'HAS_CHILD_FILES_OR_FOLDERS', - id: 'b0fc8a17-963c-405d-bfbc-859a487295e1', + message: "This folder has child files or folders.", + code: "HAS_CHILD_FILES_OR_FOLDERS", + id: "b0fc8a17-963c-405d-bfbc-859a487295e1", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - folderId: { type: 'string', format: 'misskey:id' }, + folderId: { type: "string", format: "misskey:id" }, }, - required: ['folderId'], + required: ["folderId"], } as const; // eslint-disable-next-line import/no-default-export @@ -57,5 +57,5 @@ export default define(meta, paramDef, async (ps, user) => { await DriveFolders.delete(folder.id); // Publish folderCreated event - publishDriveStream(user.id, 'folderDeleted', folder.id); + publishDriveStream(user.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 1feab273a..ab0ccce8e 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -1,32 +1,39 @@ -import define from '../../../define.js'; -import { DriveFolders } from '@/models/index.js'; -import { IsNull } from 'typeorm'; +import define from "../../../define.js"; +import { DriveFolders } from "@/models/index.js"; +import { IsNull } from "typeorm"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFolder', + type: "object", + optional: false, + nullable: false, + ref: "DriveFolder", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - parentId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, + name: { type: "string" }, + parentId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -37,5 +44,5 @@ export default define(meta, paramDef, async (ps, user) => { parentId: ps.parentId ?? IsNull(), }); - return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); + return await Promise.all(folders.map((folder) => DriveFolders.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 1e7aa2b16..6655c2336 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -1,35 +1,36 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFolders } from "@/models/index.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFolder', + type: "object", + optional: false, + nullable: false, + ref: "DriveFolder", }, errors: { noSuchFolder: { - message: 'No such folder.', - code: 'NO_SUCH_FOLDER', - id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9', + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "d74ab9eb-bb09-4bba-bf24-fb58f761e1e9", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - folderId: { type: 'string', format: 'misskey:id' }, + folderId: { type: "string", format: "misskey:id" }, }, - required: ['folderId'], + required: ["folderId"], } as const; // eslint-disable-next-line import/no-default-export 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 1aa2e8429..f720c2e99 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -1,50 +1,51 @@ -import { publishDriveStream } from '@/services/stream.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { DriveFolders } from '@/models/index.js'; +import { publishDriveStream } from "@/services/stream.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { DriveFolders } from "@/models/index.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'write:drive', + kind: "write:drive", errors: { noSuchFolder: { - message: 'No such folder.', - code: 'NO_SUCH_FOLDER', - id: 'f7974dac-2c0d-4a27-926e-23583b28e98e', + message: "No such folder.", + code: "NO_SUCH_FOLDER", + id: "f7974dac-2c0d-4a27-926e-23583b28e98e", }, noSuchParentFolder: { - message: 'No such parent folder.', - code: 'NO_SUCH_PARENT_FOLDER', - id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1', + message: "No such parent folder.", + code: "NO_SUCH_PARENT_FOLDER", + id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1", }, recursiveNesting: { - message: 'It can not be structured like nesting folders recursively.', - code: 'NO_SUCH_PARENT_FOLDER', - id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1', + message: "It can not be structured like nesting folders recursively.", + code: "NO_SUCH_PARENT_FOLDER", + id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFolder', + type: "object", + optional: false, + nullable: false, + ref: "DriveFolder", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - folderId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', maxLength: 200 }, - parentId: { type: 'string', format: 'misskey:id', nullable: true }, + folderId: { type: "string", format: "misskey:id" }, + name: { type: "string", maxLength: 200 }, + parentId: { type: "string", format: "misskey:id", nullable: true }, }, - required: ['folderId'], + required: ["folderId"], } as const; // eslint-disable-next-line import/no-default-export @@ -112,7 +113,7 @@ export default define(meta, paramDef, async (ps, user) => { const folderObj = await DriveFolders.pack(folder); // Publish folderUpdated event - publishDriveStream(user.id, 'folderUpdated', folderObj); + publishDriveStream(user.id, "folderUpdated", 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 99e8d024f..6effba741 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -1,46 +1,56 @@ -import define from '../../define.js'; -import { DriveFiles } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { DriveFiles } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['drive'], + tags: ["drive"], requireCredential: true, - kind: 'read:drive', + kind: "read:drive", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'DriveFile', + type: "object", + optional: false, + nullable: false, + ref: "DriveFile", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - type: { type: 'string', pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + type: { + type: "string", + pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1), + }, }, required: [], } 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 }); + 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('/*', '/') + '%' }); + if (ps.type.endsWith("/*")) { + query.andWhere("file.type like :type", { + type: `${ps.type.replace("/*", "/")}%`, + }); } else { - query.andWhere('file.type = :type', { type: ps.type }); + query.andWhere("file.type = :type", { type: ps.type }); } } 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 07064ce9f..c16b0b4d2 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -1,33 +1,36 @@ -import define from '../../define.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; +import define from "../../define.js"; +import { validateEmailForAccount } from "@/services/validate-email-for-account.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { available: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, reason: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - emailAddress: { type: 'string' }, + emailAddress: { type: "string" }, }, - required: ['emailAddress'], + required: ["emailAddress"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index c17412677..2e4ff909f 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -1,23 +1,23 @@ -import define from '../define.js'; -import endpoints from '../endpoints.js'; +import define from "../define.js"; +import endpoints from "../endpoints.js"; export const meta = { requireCredential: false, - tags: ['meta'], + tags: ["meta"], } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - endpoint: { type: 'string' }, + endpoint: { type: "string" }, }, - required: ['endpoint'], + required: ["endpoint"], } 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); + const ep = endpoints.find((x) => x.name === ps.endpoint); if (ep == null) return null; return { params: Object.entries(ep.params.properties || {}).map(([k, v]) => ({ diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index b20da96eb..748de95e5 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -1,34 +1,36 @@ -import define from '../define.js'; -import endpoints from '../endpoints.js'; +import define from "../define.js"; +import endpoints from "../endpoints.js"; export const meta = { requireCredential: false, - tags: ['meta'], + tags: ["meta"], res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, example: [ - 'admin/abuse-user-reports', - 'admin/accounts/create', - 'admin/announcements/create', - '...', + "admin/abuse-user-reports", + "admin/accounts/create", + "admin/announcements/create", + "...", ], }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { - return endpoints.map(x => x.name); + 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 3dc9d4e9f..dfcb5acc4 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,6 @@ -import { createExportCustomEmojisJob } from '@/queue/index.js'; -import define from '../define.js'; -import { HOUR } from '@/const.js'; +import { createExportCustomEmojisJob } from "@/queue/index.js"; +import define from "../define.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -12,7 +12,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 01df3939e..b2ce9dfd9 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -1,44 +1,47 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Followings } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, requireAdmin: true, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Following', + type: "object", + optional: false, + nullable: false, + ref: "Following", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + host: { type: "string" }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, - required: ['host'], + required: ["host"], } 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 }); + const query = makePaginationQuery( + Followings.createQueryBuilder("following"), + ps.sinceId, + ps.untilId, + ).andWhere("following.followeeHost = :host", { host: ps.host }); - const followings = await query - .take(ps.limit) - .getMany(); + const followings = await query.take(ps.limit).getMany(); return await Followings.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 17abf2e12..2a05b7303 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -1,44 +1,47 @@ -import define from '../../define.js'; -import { Followings } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Followings } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, requireAdmin: true, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Following', + type: "object", + optional: false, + nullable: false, + ref: "Following", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + host: { type: "string" }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, - required: ['host'], + required: ["host"], } 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 }); + const query = makePaginationQuery( + Followings.createQueryBuilder("following"), + ps.sinceId, + ps.untilId, + ).andWhere("following.followerHost = :host", { host: ps.host }); - const followings = await query - .take(ps.limit) - .getMany(); + const followings = await query.take(ps.limit).getMany(); return await Followings.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 41750f13e..cd3697b1c 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -1,116 +1,166 @@ -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 config from "@/config/index.js"; +import define from "../../define.js"; +import { Instances } from "@/models/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'FederationInstance', + type: "object", + optional: false, + nullable: false, + ref: "FederationInstance", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' }, - blocked: { type: 'boolean', nullable: true }, - notResponding: { type: 'boolean', nullable: true }, - suspended: { type: 'boolean', nullable: true }, - federating: { type: 'boolean', nullable: true }, - subscribing: { type: 'boolean', nullable: true }, - publishing: { type: 'boolean', nullable: true }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, - offset: { type: 'integer', default: 0 }, - sort: { type: 'string' }, + host: { + type: "string", + nullable: true, + description: "Omit or use `null` to not filter by host.", + }, + blocked: { type: "boolean", nullable: true }, + notResponding: { type: "boolean", nullable: true }, + suspended: { type: "boolean", nullable: true }, + federating: { type: "boolean", nullable: true }, + subscribing: { type: "boolean", nullable: true }, + publishing: { type: "boolean", nullable: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 30 }, + offset: { type: "integer", default: 0 }, + sort: { type: "string" }, }, required: [], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const query = Instances.createQueryBuilder('instance'); + 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; + 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; + default: + query.orderBy("instance.id", "DESC"); + break; } - if (typeof ps.blocked === 'boolean') { + if (typeof ps.blocked === "boolean") { const meta = await fetchMeta(true); if (ps.blocked) { - query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); + query.andWhere("instance.host IN (:...blocks)", { + blocks: meta.blockedHosts, + }); } else { - query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); + query.andWhere("instance.host NOT IN (:...blocks)", { + blocks: meta.blockedHosts, + }); } } - if (typeof ps.notResponding === 'boolean') { + if (typeof ps.notResponding === "boolean") { if (ps.notResponding) { - query.andWhere('instance.isNotResponding = TRUE'); + query.andWhere("instance.isNotResponding = TRUE"); } else { - query.andWhere('instance.isNotResponding = FALSE'); + query.andWhere("instance.isNotResponding = FALSE"); } } - if (typeof ps.suspended === 'boolean') { + if (typeof ps.suspended === "boolean") { if (ps.suspended) { - query.andWhere('instance.isSuspended = TRUE'); + query.andWhere("instance.isSuspended = TRUE"); } else { - query.andWhere('instance.isSuspended = FALSE'); + query.andWhere("instance.isSuspended = FALSE"); } } - if (typeof ps.federating === 'boolean') { + if (typeof ps.federating === "boolean") { if (ps.federating) { - query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); + query.andWhere( + "((instance.followingCount > 0) OR (instance.followersCount > 0))", + ); } else { - query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))'); + query.andWhere( + "((instance.followingCount = 0) AND (instance.followersCount = 0))", + ); } } - if (typeof ps.subscribing === 'boolean') { + if (typeof ps.subscribing === "boolean") { if (ps.subscribing) { - query.andWhere('instance.followersCount > 0'); + query.andWhere("instance.followersCount > 0"); } else { - query.andWhere('instance.followersCount = 0'); + query.andWhere("instance.followersCount = 0"); } } - if (typeof ps.publishing === 'boolean') { + if (typeof ps.publishing === "boolean") { if (ps.publishing) { - query.andWhere('instance.followingCount > 0'); + query.andWhere("instance.followingCount > 0"); } else { - query.andWhere('instance.followingCount = 0'); + query.andWhere("instance.followingCount = 0"); } } if (ps.host) { - query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' }); + query.andWhere("instance.host like :host", { + host: `%${ps.host.toLowerCase()}%`, + }); } const instances = await query.take(ps.limit).skip(ps.offset).getMany(); 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 8e6c59fc8..0bac6f810 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -1,35 +1,37 @@ -import define from '../../define.js'; -import { Instances } from '@/models/index.js'; -import { toPuny } from '@/misc/convert-host.js'; +import define from "../../define.js"; +import { Instances } from "@/models/index.js"; +import { toPuny } from "@/misc/convert-host.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, requireCredentialPrivateMode: true, res: { - oneOf: [{ - type: 'object', - ref: 'FederationInstance', - }, { - type: 'null', - }], + oneOf: [ + { + type: "object", + ref: "FederationInstance", + }, + { + type: "null", + }, + ], }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, + host: { type: "string" }, }, - required: ['host'], + required: ["host"], } 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) }); + const instance = await Instances.findOneBy({ host: toPuny(ps.host) }); return instance ? await Instances.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 e02c7b97e..893bc1f6b 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -1,10 +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 { IsNull, MoreThan, Not } from "typeorm"; +import { Followings, Instances } from "@/models/index.js"; +import { awaitAll } from "@/prelude/await-all.js"; +import define from "../../define.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: false, @@ -13,48 +13,53 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, required: [], } 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()), - }, - }), - ]); + 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()), + }, + }), + ]); - 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); + 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: Instances.packMany(topSubInstances), 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 409cc7695..ca2990d58 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,19 +1,19 @@ -import define from '../../define.js'; -import { getRemoteUser } from '../../common/getters.js'; -import { updatePerson } from '@/remote/activitypub/models/person.js'; +import define from "../../define.js"; +import { getRemoteUser } from "../../common/getters.js"; +import { updatePerson } from "@/remote/activitypub/models/person.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index a9b3f3a8c..6a28bdfc9 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -1,43 +1,46 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Users } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['federation'], + tags: ["federation"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailedNotMe', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailedNotMe", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - host: { type: 'string' }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + host: { type: "string" }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, - required: ['host'], + required: ["host"], } 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 }); + const query = makePaginationQuery( + Users.createQueryBuilder("user"), + ps.sinceId, + ps.untilId, + ).andWhere("user.host = :host", { host: ps.host }); - const users = await query - .take(ps.limit) - .getMany(); + const users = await query.take(ps.limit).getMany(); return await Users.packMany(users, me, { detail: true }); }); diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 05fa22a9e..541e74286 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -1,12 +1,12 @@ -import Parser from 'rss-parser'; -import { getResponse } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import define from '../define.js'; +import Parser from "rss-parser"; +import { getResponse } from "@/misc/fetch.js"; +import config from "@/config/index.js"; +import define from "../define.js"; const rssParser = new Parser(); export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, allowGet: true, @@ -14,21 +14,21 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - url: { type: 'string' }, + url: { type: "string" }, }, - required: ['url'], + required: ["url"], } 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', + method: "GET", headers: Object.assign({ - 'User-Agent': config.userAgent, - Accept: 'application/rss+xml, */*', + "User-Agent": config.userAgent, + Accept: "application/rss+xml, */*", }), timeout: 5000, }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 3a12a55b8..489c273ae 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,13 +1,13 @@ -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 { IdentifiableError } from '@/misc/identifiable-error.js'; -import { HOUR } from '@/const.js'; +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 { IdentifiableError } from "@/misc/identifiable-error.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['following', 'users'], + tags: ["following", "users"], limit: { duration: HOUR, @@ -16,53 +16,54 @@ export const meta = { requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5', + message: "No such user.", + code: "NO_SUCH_USER", + id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5", }, followeeIsYourself: { - message: 'Followee is yourself.', - code: 'FOLLOWEE_IS_YOURSELF', - id: '26fbe7bb-a331-4857-af17-205b426669a9', + message: "Followee is yourself.", + code: "FOLLOWEE_IS_YOURSELF", + id: "26fbe7bb-a331-4857-af17-205b426669a9", }, alreadyFollowing: { - message: 'You are already following that user.', - code: 'ALREADY_FOLLOWING', - id: '35387507-38c7-4cb9-9197-300b93783fa0', + message: "You are already following that user.", + code: "ALREADY_FOLLOWING", + id: "35387507-38c7-4cb9-9197-300b93783fa0", }, blocking: { - message: 'You are blocking that user.', - code: 'BLOCKING', - id: '4e2206ec-aa4f-4960-b865-6c23ac38e2d9', + message: "You are blocking that user.", + code: "BLOCKING", + id: "4e2206ec-aa4f-4960-b865-6c23ac38e2d9", }, blocked: { - message: 'You are blocked by that user.', - code: 'BLOCKED', - id: 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0', + message: "You are blocked by that user.", + code: "BLOCKED", + id: "c4ab57cc-4e41-45e9-bfd9-584f61e35ce0", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -75,8 +76,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const followee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -94,8 +96,10 @@ export default define(meta, paramDef, async (ps, user) => { 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); + 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; } diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index a454f2d72..aa01711b6 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,12 +1,12 @@ -import deleteFollowing from '@/services/following/delete.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 { HOUR } from '@/const.js'; +import deleteFollowing from "@/services/following/delete.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 { HOUR } from "@/const.js"; export const meta = { - tags: ['following', 'users'], + tags: ["following", "users"], limit: { duration: HOUR, @@ -15,41 +15,42 @@ export const meta = { requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8', + message: "No such user.", + code: "NO_SUCH_USER", + id: "5b12c78d-2b28-4dca-99d2-f56139b42ff8", }, followeeIsYourself: { - message: 'Followee is yourself.', - code: 'FOLLOWEE_IS_YOURSELF', - id: 'd9e400b9-36b0-4808-b1d8-79e707f1296c', + message: "Followee is yourself.", + code: "FOLLOWEE_IS_YOURSELF", + id: "d9e400b9-36b0-4808-b1d8-79e707f1296c", }, notFollowing: { - message: 'You are not following that user.', - code: 'NOT_FOLLOWING', - id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09', + message: "You are not following that user.", + code: "NOT_FOLLOWING", + id: "5dbf82f5-c92b-40b1-87d1-6c8c0741fd09", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -62,8 +63,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get followee - const followee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const followee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index cf3a21406..e7d8da00b 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,12 +1,12 @@ -import deleteFollowing from '@/services/following/delete.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 { HOUR } from '@/const.js'; +import deleteFollowing from "@/services/following/delete.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 { HOUR } from "@/const.js"; export const meta = { - tags: ['following', 'users'], + tags: ["following", "users"], limit: { duration: HOUR, @@ -15,41 +15,42 @@ export const meta = { requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8', + message: "No such user.", + code: "NO_SUCH_USER", + id: "5b12c78d-2b28-4dca-99d2-f56139b42ff8", }, followerIsYourself: { - message: 'Follower is yourself.', - code: 'FOLLOWER_IS_YOURSELF', - id: '07dc03b9-03da-422d-885b-438313707662', + message: "Follower is yourself.", + code: "FOLLOWER_IS_YOURSELF", + id: "07dc03b9-03da-422d-885b-438313707662", }, notFollowing: { - message: 'The other use is not following you.', - code: 'NOT_FOLLOWING', - id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09', + message: "The other use is not following you.", + code: "NOT_FOLLOWING", + id: "5dbf82f5-c92b-40b1-87d1-6c8c0741fd09", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -62,8 +63,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get follower - const follower = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const follower = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); 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 e5df55375..9e57ad043 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -1,47 +1,49 @@ -import acceptFollowRequest from '@/services/following/requests/accept.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import acceptFollowRequest from "@/services/following/requests/accept.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['following', 'account'], + tags: ["following", "account"], requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '66ce1645-d66c-46bb-8b79-96739af885bd', + message: "No such user.", + code: "NO_SUCH_USER", + id: "66ce1645-d66c-46bb-8b79-96739af885bd", }, noFollowRequest: { - message: 'No follow request.', - code: 'NO_FOLLOW_REQUEST', - id: 'bcde4f8b-0913-4614-8881-614e522fb041', + message: "No follow request.", + code: "NO_FOLLOW_REQUEST", + id: "bcde4f8b-0913-4614-8881-614e522fb041", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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); + 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); + await acceptFollowRequest(user, follower).catch((e) => { + if (e.id === "8884c2dd-5795-4ac9-b27e-6a01d38190f9") + throw new ApiError(meta.errors.noFollowRequest); throw e; }); 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 80d37fb07..7e7f9fe49 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -1,51 +1,53 @@ -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 { IdentifiableError } from '@/misc/identifiable-error.js'; +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 { IdentifiableError } from "@/misc/identifiable-error.js"; export const meta = { - tags: ['following', 'account'], + tags: ["following", "account"], requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '4e68c551-fc4c-4e46-bb41-7d4a37bf9dab', + message: "No such user.", + code: "NO_SUCH_USER", + id: "4e68c551-fc4c-4e46-bb41-7d4a37bf9dab", }, followRequestNotFound: { - message: 'Follow request not found.', - code: 'FOLLOW_REQUEST_NOT_FOUND', - id: '089b125b-d338-482a-9a09-e2622ac9f8d4', + message: "Follow request not found.", + code: "FOLLOW_REQUEST_NOT_FOUND", + id: "089b125b-d338-482a-9a09-e2622ac9f8d4", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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); + const followee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -53,7 +55,8 @@ export default define(meta, paramDef, async (ps, user) => { await cancelFollowRequest(followee, user); } catch (e) { if (e instanceof IdentifiableError) { - if (e.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound); + if (e.id === "17447091-ce07-46dd-b331-c1fd4f15b1e7") + throw new ApiError(meta.errors.followRequestNotFound); } throw e; } 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 a8f42c481..79c021e60 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -1,34 +1,39 @@ -import define from '../../../define.js'; -import { FollowRequests } from '@/models/index.js'; +import define from "../../../define.js"; +import { FollowRequests } from "@/models/index.js"; export const meta = { - tags: ['following', 'account'], + tags: ["following", "account"], requireCredential: true, - kind: 'read:following', + kind: "read:following", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, follower: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, followee: { - type: 'object', - optional: false, nullable: false, - ref: 'UserLite', + type: "object", + optional: false, + nullable: false, + ref: "UserLite", }, }, }, @@ -36,7 +41,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -47,5 +52,5 @@ export default define(meta, paramDef, async (ps, user) => { followeeId: user.id, }); - return await Promise.all(reqs.map(req => FollowRequests.pack(req))); + return await Promise.all(reqs.map((req) => FollowRequests.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 cebe60428..4a7bb9851 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -1,37 +1,38 @@ -import { rejectFollowRequest } from '@/services/following/reject.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import { rejectFollowRequest } from "@/services/following/reject.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['following', 'account'], + tags: ["following", "account"], requireCredential: true, - kind: 'write:following', + kind: "write:following", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'abc2ffa6-25b2-4380-ba99-321ff3a94555', + message: "No such user.", + code: "NO_SUCH_USER", + id: "abc2ffa6-25b2-4380-ba99-321ff3a94555", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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); + const follower = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index 52232c5cc..f5852e96a 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -1,35 +1,39 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import define from "../../define.js"; +import { GalleryPosts } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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'); + 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"); const posts = await query.take(10).getMany(); diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index 5286dcd8b..458e99c31 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -1,34 +1,36 @@ -import define from '../../define.js'; -import { GalleryPosts } from '@/models/index.js'; +import define from "../../define.js"; +import { GalleryPosts } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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'); + const query = GalleryPosts.createQueryBuilder("post") + .andWhere("post.likedCount > 0") + .orderBy("post.likedCount", "DESC"); const posts = await query.take(10).getMany(); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index f556ec513..ae4c04cd6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -1,36 +1,41 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { GalleryPosts } from '@/models/index.js'; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; +import { GalleryPosts } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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'); + const query = makePaginationQuery( + GalleryPosts.createQueryBuilder("post"), + ps.sinceId, + ps.untilId, + ).innerJoinAndSelect("post.user", "user"); const posts = await query.take(ps.limit).getMany(); 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 eec4d2ba1..2b2e7afaa 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,17 +1,17 @@ -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 { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { HOUR } from '@/const.js'; +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 { ApiError } from "../../../error.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: true, - kind: 'write:gallery', + kind: "write:gallery", limit: { duration: HOUR, @@ -19,52 +19,64 @@ export const meta = { }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, - errors: { - - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - title: { type: 'string', minLength: 1 }, - description: { type: 'string', nullable: true }, - fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 32, items: { - type: 'string', format: 'misskey:id', - } }, - isSensitive: { type: 'boolean', default: false }, + title: { type: "string", minLength: 1 }, + description: { type: "string", nullable: true }, + fileIds: { + type: "array", + uniqueItems: true, + minItems: 1, + maxItems: 32, + items: { + type: "string", + format: "misskey:id", + }, + }, + isSensitive: { type: "boolean", default: false }, }, - required: ['title', 'fileIds'], + required: ["title", "fileIds"], } 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); + const files = ( + await Promise.all( + ps.fileIds.map((fileId) => + DriveFiles.findOneBy({ + id: fileId, + userId: user.id, + }), + ), + ) + ).filter((file): file is DriveFile => file != null); if (files.length === 0) { throw new Error(); } - 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])); + 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])); return await GalleryPosts.pack(post, user); }); 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 b00ee0e2a..42447f853 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -1,29 +1,29 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { GalleryPosts } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: true, - kind: 'write:gallery', + kind: "write:gallery", errors: { noSuchPost: { - message: 'No such post.', - code: 'NO_SUCH_POST', - id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5', + message: "No such post.", + code: "NO_SUCH_POST", + id: "ae52f367-4bd7-4ecd-afc6-5672fff427f5", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - postId: { type: 'string', format: 'misskey:id' }, + postId: { type: "string", format: "misskey:id" }, }, - required: ['postId'], + required: ["postId"], } as const; // eslint-disable-next-line import/no-default-export 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 9e722e71c..a7bfeafb2 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -1,36 +1,36 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import define from "../../../define.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'], + tags: ["gallery"], requireCredential: true, - kind: 'write:gallery-likes', + kind: "write:gallery-likes", errors: { noSuchPost: { - message: 'No such post.', - code: 'NO_SUCH_POST', - id: '56c06af3-1287-442f-9701-c93f7c4a62ff', + message: "No such post.", + code: "NO_SUCH_POST", + id: "56c06af3-1287-442f-9701-c93f7c4a62ff", }, alreadyLiked: { - message: 'The post has already been liked.', - code: 'ALREADY_LIKED', - id: '40e9ed56-a59c-473a-bf3f-f289c54fb5a7', + message: "The post has already been liked.", + code: "ALREADY_LIKED", + id: "40e9ed56-a59c-473a-bf3f-f289c54fb5a7", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - postId: { type: 'string', format: 'misskey:id' }, + postId: { type: "string", format: "misskey:id" }, }, - required: ['postId'], + required: ["postId"], } as const; // eslint-disable-next-line import/no-default-export @@ -58,5 +58,5 @@ export default define(meta, paramDef, async (ps, user) => { userId: user.id, }); - GalleryPosts.increment({ id: post.id }, 'likedCount', 1); + GalleryPosts.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 48468f410..4bb577065 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -1,34 +1,35 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { GalleryPosts } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { GalleryPosts } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: false, requireCredentialPrivateMode: true, errors: { noSuchPost: { - message: 'No such post.', - code: 'NO_SUCH_POST', - id: '1137bf14-c5b0-4604-85bb-5b5371b1cd45', + message: "No such post.", + code: "NO_SUCH_POST", + id: "1137bf14-c5b0-4604-85bb-5b5371b1cd45", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - postId: { type: 'string', format: 'misskey:id' }, + postId: { type: "string", format: "misskey:id" }, }, - required: ['postId'], + required: ["postId"], } as const; // eslint-disable-next-line import/no-default-export 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 d136239e5..be6dec648 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -1,35 +1,35 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { GalleryPosts, GalleryLikes } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { GalleryPosts, GalleryLikes } from "@/models/index.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: true, - kind: 'write:gallery-likes', + kind: "write:gallery-likes", errors: { noSuchPost: { - message: 'No such post.', - code: 'NO_SUCH_POST', - id: 'c32e6dd0-b555-4413-925e-b3757d19ed84', + message: "No such post.", + code: "NO_SUCH_POST", + id: "c32e6dd0-b555-4413-925e-b3757d19ed84", }, notLiked: { - message: 'You have not liked that post.', - code: 'NOT_LIKED', - id: 'e3e8e06e-be37-41f7-a5b4-87a8250288f0', + message: "You have not liked that post.", + code: "NOT_LIKED", + id: "e3e8e06e-be37-41f7-a5b4-87a8250288f0", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - postId: { type: 'string', format: 'misskey:id' }, + postId: { type: "string", format: "misskey:id" }, }, - required: ['postId'], + required: ["postId"], } as const; // eslint-disable-next-line import/no-default-export @@ -51,5 +51,5 @@ export default define(meta, paramDef, async (ps, user) => { // Delete like await GalleryLikes.delete(exist.id); - GalleryPosts.decrement({ id: post.id }, 'likedCount', 1); + GalleryPosts.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 b333d947d..428dd72e8 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,16 +1,16 @@ -import define from '../../../define.js'; -import { DriveFiles, GalleryPosts } from '@/models/index.js'; -import { GalleryPost } from '@/models/entities/gallery-post.js'; -import { ApiError } from '../../../error.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { HOUR } from '@/const.js'; +import define from "../../../define.js"; +import { DriveFiles, GalleryPosts } from "@/models/index.js"; +import { GalleryPost } from "@/models/entities/gallery-post.js"; +import { ApiError } from "../../../error.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['gallery'], + tags: ["gallery"], requireCredential: true, - kind: 'write:gallery', + kind: "write:gallery", limit: { duration: HOUR, @@ -18,53 +18,66 @@ export const meta = { }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, - errors: { - - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - postId: { type: 'string', format: 'misskey:id' }, - title: { type: 'string', minLength: 1 }, - description: { type: 'string', nullable: true }, - fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 32, items: { - type: 'string', format: 'misskey:id', - } }, - isSensitive: { type: 'boolean', default: false }, + postId: { type: "string", format: "misskey:id" }, + title: { type: "string", minLength: 1 }, + description: { type: "string", nullable: true }, + fileIds: { + type: "array", + uniqueItems: true, + minItems: 1, + maxItems: 32, + items: { + type: "string", + format: "misskey:id", + }, + }, + isSensitive: { type: "boolean", default: false }, }, - required: ['postId', 'title', 'fileIds'], + required: ["postId", "title", "fileIds"], } 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); + const files = ( + await Promise.all( + ps.fileIds.map((fileId) => + DriveFiles.findOneBy({ + id: fileId, + userId: user.id, + }), + ), + ) + ).filter((file): file is DriveFile => file != null); if (files.length === 0) { throw new Error(); } - 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), - }); + 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), + }, + ); const post = await GalleryPosts.findOneByOrFail({ id: ps.postId }); 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 a8febe05b..8e9b271f0 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,17 +1,17 @@ -import { MoreThan } from 'typeorm'; -import { USER_ONLINE_THRESHOLD } from '@/const.js'; -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { MoreThan } from "typeorm"; +import { USER_ONLINE_THRESHOLD } from "@/const.js"; +import { Users } from "@/models/index.js"; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 4b18cb76a..9fb61e2cc 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -1,66 +1,110 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import define from "../../define.js"; +import { Hashtags } from "@/models/index.js"; export const meta = { - tags: ['hashtags'], + tags: ["hashtags"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Hashtag', + type: "object", + optional: false, + nullable: false, + ref: "Hashtag", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - attachedToUserOnly: { type: 'boolean', default: false }, - attachedToLocalUserOnly: { type: 'boolean', default: false }, - attachedToRemoteUserOnly: { type: 'boolean', default: false }, - sort: { type: 'string', enum: ['+mentionedUsers', '-mentionedUsers', '+mentionedLocalUsers', '-mentionedLocalUsers', '+mentionedRemoteUsers', '-mentionedRemoteUsers', '+attachedUsers', '-attachedUsers', '+attachedLocalUsers', '-attachedLocalUsers', '+attachedRemoteUsers', '-attachedRemoteUsers'] }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + attachedToUserOnly: { type: "boolean", default: false }, + attachedToLocalUserOnly: { type: "boolean", default: false }, + attachedToRemoteUserOnly: { type: "boolean", default: false }, + sort: { + type: "string", + enum: [ + "+mentionedUsers", + "-mentionedUsers", + "+mentionedLocalUsers", + "-mentionedLocalUsers", + "+mentionedRemoteUsers", + "-mentionedRemoteUsers", + "+attachedUsers", + "-attachedUsers", + "+attachedLocalUsers", + "-attachedLocalUsers", + "+attachedRemoteUsers", + "-attachedRemoteUsers", + ], + }, }, - required: ['sort'], + required: ["sort"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const query = Hashtags.createQueryBuilder('tag'); + const query = Hashtags.createQueryBuilder("tag"); - if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); - if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); - if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + 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; + 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', + "tag.name", + "tag.mentionedUsersCount", + "tag.mentionedLocalUsersCount", + "tag.mentionedRemoteUsersCount", + "tag.attachedUsersCount", + "tag.attachedLocalUsersCount", + "tag.attachedRemoteUsersCount", ]); const tags = await query.take(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index ed1abf1a1..2a57c025f 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -1,41 +1,43 @@ -import define from '../../define.js'; -import { Hashtags } from '@/models/index.js'; +import define from "../../define.js"; +import { Hashtags } from "@/models/index.js"; export const meta = { - tags: ['hashtags'], + tags: ["hashtags"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - query: { type: 'string' }, - offset: { type: 'integer', default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + query: { type: "string" }, + offset: { type: "integer", default: 0 }, }, - required: ['query'], + required: ["query"], } 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') + 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(); - 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 409233c24..9ac421a3b 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -1,40 +1,43 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Hashtags } from '@/models/index.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Hashtags } from "@/models/index.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; export const meta = { - tags: ['hashtags'], + tags: ["hashtags"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Hashtag', + type: "object", + optional: false, + nullable: false, + ref: "Hashtag", }, errors: { noSuchHashtag: { - message: 'No such hashtag.', - code: 'NO_SUCH_HASHTAG', - id: '110ee688-193e-4a3a-9ecf-c167b2e6981e', + message: "No such hashtag.", + code: "NO_SUCH_HASHTAG", + id: "110ee688-193e-4a3a-9ecf-c167b2e6981e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - tag: { type: 'string' }, + tag: { type: "string" }, }, - required: ['tag'], + required: ["tag"], } 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) }); + const hashtag = await Hashtags.findOneBy({ + name: normalizeForSearch(ps.tag), + }); if (hashtag == null) { throw new ApiError(meta.errors.noSuchHashtag); } diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index 8795927e6..a02ef5400 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -1,10 +1,10 @@ -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 { safeForSql } from '@/misc/safe-for-sql.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import { Brackets } from "typeorm"; +import define from "../../define.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Notes } 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"; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -21,33 +21,39 @@ const rangeA = 1000 * 60 * 60; // 60分 const max = 5; export const meta = { - tags: ['hashtags'], + tags: ["hashtags"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { tag: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, chart: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, usersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, @@ -55,7 +61,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -63,19 +69,22 @@ export const paramDef = { // 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 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'`); - })) + 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']) + .select(["note.tags", "note.userId"]) .cache(60000) // 1 min .getMany(); @@ -85,14 +94,14 @@ export default define(meta, paramDef, async () => { const tags: { name: string; - users: Note['userId'][]; + 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); + const x = tags.find((x) => x.name === tag); if (x) { if (!x.users.includes(note.userId)) { x.users.push(note.userId); @@ -109,7 +118,7 @@ export default define(meta, paramDef, async () => { // タグを人気順に並べ替え const hots = tags .sort((a, b) => b.users.length - a.users.length) - .map(tag => tag.name) + .map((tag) => tag.name) .slice(0, max); //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する @@ -121,32 +130,48 @@ export default define(meta, paramDef, async () => { 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)) - ))); + 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 countsLog = await Promise.all(countPromises); //#endregion - const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') - .select('count(distinct note.userId)') - .where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`) - .andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) }) - .cache(60000 * 60) // 60 min - .getRawOne() - .then(x => parseInt(x.count, 10)) - )); + const totalCounts = await Promise.all( + hots.map((tag) => + 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]), + chart: countsLog.map((counts) => counts[i]), usersCount: totalCounts[i], })); diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 1d18a9ce7..509bddc78 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -1,60 +1,90 @@ -import define from '../../define.js'; -import { Users } from '@/models/index.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import define from "../../define.js"; +import { Users } from "@/models/index.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; export const meta = { requireCredential: false, requireCredentialPrivateMode: true, - tags: ['hashtags', 'users'], + tags: ["hashtags", "users"], res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - 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" }, + 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", + }, }, - required: ['tag', 'sort'], + 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) }); + const query = Users.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; + 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(); diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 22aedfeee..a98a09cc8 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,20 +1,21 @@ -import { Users } from '@/models/index.js'; -import define from '../define.js'; +import { Users } from "@/models/index.js"; +import define from "../define.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, res: { - type: 'object', - optional: false, nullable: false, - ref: 'MeDetailed', + type: "object", + optional: false, + nullable: false, + ref: "MeDetailed", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 35806b2bc..692deb3e7 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,6 @@ -import * as speakeasy from 'speakeasy'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import * as speakeasy from "speakeasy"; +import define from "../../../define.js"; +import { UserProfiles } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -9,31 +9,31 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - token: { type: 'string' }, + token: { type: "string" }, }, - required: ['token'], + required: ["token"], } 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 token = ps.token.replace(/\s/g, ""); const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.twoFactorTempSecret == null) { - throw new Error('二段階認証の設定が開始されていません'); + throw new Error("二段階認証の設定が開始されていません"); } const verified = (speakeasy as any).totp.verify({ secret: profile.twoFactorTempSecret, - encoding: 'base32', + encoding: "base32", token: token, }); if (!verified) { - throw new Error('not verified'); + throw new Error("not verified"); } await UserProfiles.update(user.id, { 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 1afb34bfd..bc1faefb5 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,19 @@ -import bcrypt from 'bcryptjs'; -import { promisify } from 'node:util'; -import * as cbor from 'cbor'; -import define from '../../../define.js'; +import bcrypt from "bcryptjs"; +import { promisify } from "node:util"; +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'; +} from "@/models/index.js"; +import config from "@/config/index.js"; +import { procedures, hash } from "../../../2fa.js"; +import { publishMainStream } from "@/services/stream.js"; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; -const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); +const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8")); export const meta = { requireCredential: true, @@ -22,15 +22,21 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - clientDataJSON: { type: 'string' }, - attestationObject: { type: 'string' }, - password: { type: 'string' }, - challengeId: { type: 'string' }, - name: { type: 'string' }, + clientDataJSON: { type: "string" }, + attestationObject: { type: "string" }, + password: { type: "string" }, + challengeId: { type: "string" }, + name: { type: "string" }, }, - required: ['clientDataJSON', 'attestationObject', 'password', 'challengeId', 'name'], + required: [ + "clientDataJSON", + "attestationObject", + "password", + "challengeId", + "name", + ], } as const; // eslint-disable-next-line import/no-default-export @@ -41,36 +47,36 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); + 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.type !== "webauthn.create") { + throw new Error("not a creation attestation"); } - if (clientData.origin !== config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); + if (clientData.origin !== `${config.scheme}://${config.host}`) { + throw new Error("origin mismatch"); } - const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); + 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'); + 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'); + throw new Error("user not present"); } const authData = Buffer.from(attestation.authData); @@ -79,11 +85,11 @@ export default define(meta, paramDef, async (ps, user) => { const publicKeyData = authData.slice(55 + credentialIdLength); const publicKey: Map = await cborDecodeFirst(publicKeyData); if (publicKey.get(3) !== -7) { - throw new Error('alg mismatch'); + throw new Error("alg mismatch"); } if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); + throw new Error("unsupported fmt"); } const verificationData = (procedures as any)[attestation.fmt].verify({ @@ -94,17 +100,17 @@ export default define(meta, paramDef, async (ps, user) => { publicKey, rpIdHash, }); - if (!verificationData.valid) throw new Error('signature invalid'); + 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'), + challenge: hash(clientData.challenge).toString("hex"), }); if (!attestationChallenge) { - throw new Error('non-existent challenge'); + throw new Error("non-existent challenge"); } await AttestationChallenges.delete({ @@ -117,24 +123,28 @@ export default define(meta, paramDef, async (ps, user) => { new Date().getTime() - attestationChallenge.createdAt.getTime() >= 5 * 60 * 1000 ) { - throw new Error('expired challenge'); + throw new Error("expired challenge"); } - const credentialIdString = credentialId.toString('hex'); + const credentialIdString = credentialId.toString("hex"); await UserSecurityKeys.insert({ userId: user.id, id: credentialIdString, lastUsed: new Date(), name: ps.name, - publicKey: verificationData.publicKey.toString('hex'), + publicKey: verificationData.publicKey.toString("hex"), }); // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user.id, user, { + detail: true, + includeSecrets: true, + }), + ); return { id: credentialIdString, 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 4bfa24f97..de98ba016 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,5 @@ -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import define from "../../../define.js"; +import { UserProfiles } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,11 +8,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - value: { type: 'boolean' }, + value: { type: "boolean" }, }, - required: ['value'], + required: ["value"], } as const; // eslint-disable-next-line import/no-default-export 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 e906b8204..89aec05c0 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,10 @@ -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 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"; const randomBytes = promisify(crypto.randomBytes); @@ -15,11 +15,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, + password: { type: "string" }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -30,26 +30,27 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); + 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 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'), + challenge: hash(Buffer.from(challenge, "utf-8")).toString("hex"), createdAt: new Date(), registrationChallenge: true, }); 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 33f571772..65414bb02 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,9 @@ -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 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"; export const meta = { requireCredential: true, @@ -12,11 +12,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, + password: { type: "string" }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -27,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } // Generate user's secret key @@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => { // Get the data URL of the authenticator URL const url = speakeasy.otpauthURL({ secret: secret.base32, - encoding: 'base32', + encoding: "base32", label: user.username, issuer: 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 eb2f75308..4db39ded8 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 define from '../../../define.js'; -import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import bcrypt from "bcryptjs"; +import define from "../../../define.js"; +import { UserProfiles, UserSecurityKeys, Users } from "@/models/index.js"; +import { publishMainStream } from "@/services/stream.js"; export const meta = { requireCredential: true, @@ -10,12 +10,12 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, - credentialId: { type: 'string' }, + password: { type: "string" }, + credentialId: { type: "string" }, }, - required: ['password', 'credentialId'], + required: ["password", "credentialId"], } as const; // eslint-disable-next-line import/no-default-export @@ -26,7 +26,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } // Make sure we only delete the user's own creds @@ -36,10 +36,14 @@ export default define(meta, paramDef, async (ps, user) => { }); // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - })); + 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 45e7a9863..2c22e7598 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,6 @@ -import bcrypt from 'bcryptjs'; -import define from '../../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import bcrypt from "bcryptjs"; +import define from "../../../define.js"; +import { UserProfiles } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -9,11 +9,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, + password: { type: "string" }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -24,7 +24,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } await UserProfiles.update(user.id, { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index eca955884..398b4e424 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; +import define from "../../define.js"; +import { AccessTokens } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,33 +8,50 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - sort: { type: 'string', enum: ['+createdAt', '-createdAt', '+lastUsedAt', '-lastUsedAt'] }, + sort: { + type: "string", + enum: ["+createdAt", "-createdAt", "+lastUsedAt", "-lastUsedAt"], + }, }, required: [], } 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 }); + const query = AccessTokens.createQueryBuilder("token").where( + "token.userId = :userId", + { userId: user.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; + 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(); - 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 68bd103a6..7f065fc9c 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,5 @@ -import define from '../../define.js'; -import { AccessTokens, Apps } from '@/models/index.js'; +import define from "../../define.js"; +import { AccessTokens, Apps } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,11 +8,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, - sort: { type: 'string', enum: ['desc', 'asc'], default: "desc" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, + sort: { type: "string", enum: ["desc", "asc"], default: "desc" }, }, required: [], } as const; @@ -27,11 +27,15 @@ export default define(meta, paramDef, async (ps, user) => { take: ps.limit, skip: ps.offset, order: { - id: ps.sort === 'asc' ? 1 : -1, + id: ps.sort === "asc" ? 1 : -1, }, }); - return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { - detail: true, - }))); + return await Promise.all( + tokens.map((token) => + Apps.pack(token.appId, user, { + 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 f9f6a33a8..e44686ed3 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,6 @@ -import bcrypt from 'bcryptjs'; -import define from '../../define.js'; -import { UserProfiles } from '@/models/index.js'; +import bcrypt from "bcryptjs"; +import define from "../../define.js"; +import { UserProfiles } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -9,12 +9,12 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - currentPassword: { type: 'string' }, - newPassword: { type: 'string', minLength: 1 }, + currentPassword: { type: "string" }, + newPassword: { type: "string", minLength: 1 }, }, - required: ['currentPassword', 'newPassword'], + required: ["currentPassword", "newPassword"], } as const; // eslint-disable-next-line import/no-default-export @@ -25,7 +25,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.currentPassword, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } // Generate hash of password 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 ede4a9d03..566a34631 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,7 @@ -import bcrypt from 'bcryptjs'; -import { UserProfiles, Users } from '@/models/index.js'; -import { deleteAccount } from '@/services/delete-account.js'; -import define from '../../define.js'; +import bcrypt from "bcryptjs"; +import { UserProfiles, Users } from "@/models/index.js"; +import { deleteAccount } from "@/services/delete-account.js"; +import define from "../../define.js"; export const meta = { requireCredential: true, @@ -10,11 +10,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, + password: { type: "string" }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -29,7 +29,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } await deleteAccount(user); 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 682d39552..eb3f92c41 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,6 @@ -import define from '../../define.js'; -import { createExportBlockingJob } from '@/queue/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createExportBlockingJob } from "@/queue/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -12,7 +12,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 3d56ab7ee..e88a68ec5 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,6 @@ -import define from '../../define.js'; -import { createExportFollowingJob } from '@/queue/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createExportFollowingJob } from "@/queue/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -12,10 +12,10 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - excludeMuting: { type: 'boolean', default: false }, - excludeInactive: { type: 'boolean', default: false }, + excludeMuting: { type: "boolean", default: false }, + excludeInactive: { type: "boolean", default: false }, }, required: [], } as const; 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 b6cc1eea4..ce95df2d6 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,6 @@ -import define from '../../define.js'; -import { createExportMuteJob } from '@/queue/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createExportMuteJob } from "@/queue/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -12,7 +12,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 4856b84ab..8cf85e719 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,6 @@ -import define from '../../define.js'; -import { createExportNotesJob } from '@/queue/index.js'; -import { DAY } from '@/const.js'; +import define from "../../define.js"; +import { createExportNotesJob } from "@/queue/index.js"; +import { DAY } from "@/const.js"; export const meta = { secure: true, @@ -12,7 +12,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 1aa02707d..f4c423657 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,6 @@ -import define from '../../define.js'; -import { createExportUserListsJob } from '@/queue/index.js'; -import { MINUTE } from '@/const.js'; +import define from "../../define.js"; +import { createExportUserListsJob } from "@/queue/index.js"; +import { MINUTE } from "@/const.js"; export const meta = { secure: true, @@ -12,7 +12,7 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 3c420e4d0..f246f6cfe 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -1,44 +1,48 @@ -import define from '../../define.js'; -import { NoteFavorites } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { NoteFavorites } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'notes', 'favorites'], + tags: ["account", "notes", "favorites"], requireCredential: true, - kind: 'read:favorites', + kind: "read:favorites", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'NoteFavorite', + type: "object", + optional: false, + nullable: false, + ref: "NoteFavorite", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 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(); + const favorites = await query.take(ps.limit).getMany(); return await NoteFavorites.packMany(favorites, user); }); 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 a38383f30..6b3e429c9 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -1,55 +1,61 @@ -import define from '../../../define.js'; -import { GalleryLikes } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import define from "../../../define.js"; +import { GalleryLikes } from "@/models/index.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'gallery'], + tags: ["account", "gallery"], requireCredential: true, - kind: 'read:gallery-likes', + kind: "read:gallery-likes", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, post: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, - } + }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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'); + const query = makePaginationQuery( + GalleryLikes.createQueryBuilder("like"), + ps.sinceId, + ps.untilId, + ) + .andWhere("like.userId = :meId", { meId: user.id }) + .leftJoinAndSelect("like.post", "post"); - const likes = await query - .take(ps.limit) - .getMany(); + const likes = await query.take(ps.limit).getMany(); return await GalleryLikes.packMany(likes, user); }); 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 2ecd47f1b..f6fa573aa 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -1,43 +1,46 @@ -import { GalleryPosts } from '@/models/index.js'; -import define from '../../../define.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import { GalleryPosts } from "@/models/index.js"; +import define from "../../../define.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'gallery'], + tags: ["account", "gallery"], requireCredential: true, - kind: 'read:gallery', + kind: "read:gallery", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 query = makePaginationQuery( + GalleryPosts.createQueryBuilder("post"), + ps.sinceId, + ps.untilId, + ).andWhere("post.userId = :meId", { meId: user.id }); - const posts = await query - .take(ps.limit) - .getMany(); + const posts = await query.take(ps.limit).getMany(); return await GalleryPosts.packMany(posts, user); }); 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 e7d7518c5..7699cf5d4 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,27 +1,29 @@ -import define from '../../define.js'; -import { MutedNotes } from '@/models/index.js'; +import define from "../../define.js"; +import { MutedNotes } from "@/models/index.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { count: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -31,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { return { count: await MutedNotes.countBy({ userId: user.id, - reason: 'word', + 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 5e5bcba7a..2df138dc3 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,8 @@ -import define from '../../define.js'; -import { createImportBlockingJob } from '@/queue/index.js'; -import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createImportBlockingJob } from "@/queue/index.js"; +import { ApiError } from "../../error.js"; +import { DriveFiles } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -15,37 +15,37 @@ export const meta = { errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'ebb53e5f-6574-9c0c-0b92-7ca6def56d7e', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "ebb53e5f-6574-9c0c-0b92-7ca6def56d7e", }, unexpectedFileType: { - message: 'We need csv file.', - code: 'UNEXPECTED_FILE_TYPE', - id: 'b6fab7d6-d945-d67c-dfdb-32da1cd12cfe', + message: "We need csv file.", + code: "UNEXPECTED_FILE_TYPE", + id: "b6fab7d6-d945-d67c-dfdb-32da1cd12cfe", }, tooBigFile: { - message: 'That file is too big.', - code: 'TOO_BIG_FILE', - id: 'b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf', + message: "That file is too big.", + code: "TOO_BIG_FILE", + id: "b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf", }, emptyFile: { - message: 'That file is empty.', - code: 'EMPTY_FILE', - id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6', + message: "That file is empty.", + code: "EMPTY_FILE", + id: "6f3a4dcc-f060-a707-4950-806fbdbe60d6", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export 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 5f0cf9156..5cb62b7b9 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,8 @@ -import define from '../../define.js'; -import { createImportFollowingJob } from '@/queue/index.js'; -import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createImportFollowingJob } from "@/queue/index.js"; +import { ApiError } from "../../error.js"; +import { DriveFiles } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -14,37 +14,37 @@ export const meta = { errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'b98644cf-a5ac-4277-a502-0b8054a709a3', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "b98644cf-a5ac-4277-a502-0b8054a709a3", }, unexpectedFileType: { - message: 'Must be a CSV or JSON file.', - code: 'UNEXPECTED_FILE_TYPE', - id: '660f3599-bce0-4f95-9dde-311fd841c183', + message: "Must be a CSV or JSON file.", + code: "UNEXPECTED_FILE_TYPE", + id: "660f3599-bce0-4f95-9dde-311fd841c183", }, tooBigFile: { - message: 'That file is too big.', - code: 'TOO_BIG_FILE', - id: 'dee9d4ed-ad07-43ed-8b34-b2856398bc60', + message: "That file is too big.", + code: "TOO_BIG_FILE", + id: "dee9d4ed-ad07-43ed-8b34-b2856398bc60", }, emptyFile: { - message: 'That file is empty.', - code: 'EMPTY_FILE', - id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691', + message: "That file is empty.", + code: "EMPTY_FILE", + id: "31a1b42c-06f7-42ae-8a38-a661c5c9f691", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export 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 4165da020..4cf27b28b 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,8 @@ -import define from '../../define.js'; -import { createImportMutingJob } from '@/queue/index.js'; -import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createImportMutingJob } from "@/queue/index.js"; +import { ApiError } from "../../error.js"; +import { DriveFiles } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -15,37 +15,37 @@ export const meta = { errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'e674141e-bd2a-ba85-e616-aefb187c9c2a', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "e674141e-bd2a-ba85-e616-aefb187c9c2a", }, unexpectedFileType: { - message: 'We need csv file.', - code: 'UNEXPECTED_FILE_TYPE', - id: '568c6e42-c86c-ba09-c004-517f83f9f1a8', + message: "We need csv file.", + code: "UNEXPECTED_FILE_TYPE", + id: "568c6e42-c86c-ba09-c004-517f83f9f1a8", }, tooBigFile: { - message: 'That file is too big.', - code: 'TOO_BIG_FILE', - id: '9b4ada6d-d7f7-0472-0713-4f558bd1ec9c', + message: "That file is too big.", + code: "TOO_BIG_FILE", + id: "9b4ada6d-d7f7-0472-0713-4f558bd1ec9c", }, emptyFile: { - message: 'That file is empty.', - code: 'EMPTY_FILE', - id: 'd2f12af1-e7b4-feac-86a3-519548f2728e', + message: "That file is empty.", + code: "EMPTY_FILE", + id: "d2f12af1-e7b4-feac-86a3-519548f2728e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export 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 6b3949c99..7b30ad45d 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,8 @@ -import define from '../../define.js'; -import { createImportUserListsJob } from '@/queue/index.js'; -import { ApiError } from '../../error.js'; -import { DriveFiles } from '@/models/index.js'; -import { HOUR } from '@/const.js'; +import define from "../../define.js"; +import { createImportUserListsJob } from "@/queue/index.js"; +import { ApiError } from "../../error.js"; +import { DriveFiles } from "@/models/index.js"; +import { HOUR } from "@/const.js"; export const meta = { secure: true, @@ -14,37 +14,37 @@ export const meta = { errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'ea9cc34f-c415-4bc6-a6fe-28ac40357049', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "ea9cc34f-c415-4bc6-a6fe-28ac40357049", }, unexpectedFileType: { - message: 'We need csv file.', - code: 'UNEXPECTED_FILE_TYPE', - id: 'a3c9edda-dd9b-4596-be6a-150ef813745c', + message: "We need csv file.", + code: "UNEXPECTED_FILE_TYPE", + id: "a3c9edda-dd9b-4596-be6a-150ef813745c", }, tooBigFile: { - message: 'That file is too big.', - code: 'TOO_BIG_FILE', - id: 'ae6e7a22-971b-4b52-b2be-fc0b9b121fe9', + message: "That file is too big.", + code: "TOO_BIG_FILE", + id: "ae6e7a22-971b-4b52-b2be-fc0b9b121fe9", }, emptyFile: { - message: 'That file is empty.', - code: 'EMPTY_FILE', - id: '99efe367-ce6e-4d44-93f8-5fae7b040356', + message: "That file is empty.", + code: "EMPTY_FILE", + id: "99efe367-ce6e-4d44-93f8-5fae7b040356", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - fileId: { type: 'string', format: 'misskey:id' }, + fileId: { type: "string", format: "misskey:id" }, }, - required: ['fileId'], + required: ["fileId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts index a7a32aee6..27ad34236 100644 --- a/packages/backend/src/server/api/endpoints/i/known-as.ts +++ b/packages/backend/src/server/api/endpoints/i/known-as.ts @@ -59,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => { if (!unfiltered) { updates.alsoKnownAs = null; } else { - if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5); + if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5); if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1); if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote); @@ -68,10 +68,10 @@ export default define(meta, paramDef, async (ps, user) => { (e) => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.noSuchUser); - } + }, ); - let toUrl: string | null = knownAs.uri; + const toUrl: string | null = knownAs.uri; if (!toUrl) { throw new ApiError(meta.errors.uriNull); } diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index d22b178b3..6e495c219 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -1,22 +1,22 @@ -import type { User } from '@/models/entities/user.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import { DAY } from '@/const.js'; -import DeliverManager from '@/remote/activitypub/deliver-manager.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { apiLogger } from '../../logger.js'; -import deleteFollowing from '@/services/following/delete.js'; -import create from '@/services/following/create.js'; -import { getUser } from '@/server/api/common/getters.js'; -import { Followings, Users } from '@/models/index.js'; -import { UserProfiles } from '@/models/index.js'; -import config from '@/config/index.js'; -import { publishMainStream } from '@/services/stream.js'; +import type { User } from "@/models/entities/user.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { DAY } from "@/const.js"; +import DeliverManager from "@/remote/activitypub/deliver-manager.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { genId } from "@/misc/gen-id.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { apiLogger } from "../../logger.js"; +import deleteFollowing from "@/services/following/delete.js"; +import create from "@/services/following/create.js"; +import { getUser } from "@/server/api/common/getters.js"; +import { Followings, Users } from "@/models/index.js"; +import { UserProfiles } from "@/models/index.js"; +import config from "@/config/index.js"; +import { publishMainStream } from "@/services/stream.js"; export const meta = { - tags: ['users'], + tags: ["users"], secure: true, requireCredential: true, @@ -28,29 +28,30 @@ export const meta = { errors: { noSuchMoveTarget: { - message: 'No such move target.', - code: 'NO_SUCH_MOVE_TARGET', - id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4', + message: "No such move target.", + code: "NO_SUCH_MOVE_TARGET", + id: "b5c90186-4ab0-49c8-9bba-a1f76c202ba4", }, remoteAccountForbids: { - message: 'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?', - code: 'REMOTE_ACCOUNT_FORBIDS', - id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4', + message: + "Remote account doesn't have proper 'Known As' alias. Did you remember to set it?", + code: "REMOTE_ACCOUNT_FORBIDS", + id: "b5c90186-4ab0-49c8-9bba-a1f766282ba4", }, notRemote: { - message: 'User is not remote. You can only migrate to other instances.', - code: 'NOT_REMOTE', - id: '4362f8dc-731f-4ad8-a694-be2a88922a24', + message: "User is not remote. You can only migrate to other instances.", + code: "NOT_REMOTE", + id: "4362f8dc-731f-4ad8-a694-be2a88922a24", }, adminForbidden: { - message: 'Admins cant migrate.', - code: 'NOT_ADMIN_FORBIDDEN', - id: '4362e8dc-731f-4ad8-a694-be2a88922a24', + message: "Admins cant migrate.", + code: "NOT_ADMIN_FORBIDDEN", + id: "4362e8dc-731f-4ad8-a694-be2a88922a24", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5', + message: "No such user.", + code: "NO_SUCH_USER", + id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5", }, uriNull: { message: "User ActivityPup URI is null.", @@ -71,18 +72,18 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - moveToAccount: { type: 'string' }, + moveToAccount: { type: "string" }, }, - required: ['moveToAccount'], + required: ["moveToAccount"], } as const; function moveActivity(toUrl: string, fromUrl: string) { const activity = { id: genId(), actor: fromUrl, - type: 'Move', + type: "Move", object: fromUrl, target: toUrl, } as any; @@ -101,36 +102,39 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchMoveTarget); } - if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5); - if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1); - if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote); + if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5); + if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1); + if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote); - const userAddress: string[] = unfiltered.split('@'); - const moveTo: User = await resolveUser(userAddress[0], userAddress[1]).catch(e => { - apiLogger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.noSuchMoveTarget); - }); + const userAddress: string[] = unfiltered.split("@"); + const moveTo: User = await resolveUser(userAddress[0], userAddress[1]).catch( + (e) => { + apiLogger.warn(`failed to resolve remote user: ${e}`); + throw new ApiError(meta.errors.noSuchMoveTarget); + }, + ); let fromUrl: string | null = user.uri; - if(!fromUrl) { + if (!fromUrl) { fromUrl = `${config.url}/users/${user.id}`; } let toUrl: string | null = moveTo.uri; - if(!toUrl) { + if (!toUrl) { throw new ApiError(meta.errors.uriNull); } let allowed = false; - moveTo.alsoKnownAs?.forEach(element => { + moveTo.alsoKnownAs?.forEach((element) => { if (fromUrl!.includes(element)) allowed = true; }); - if (!allowed || !toUrl || !fromUrl) throw new ApiError(meta.errors.remoteAccountForbids); + if (!((allowed && toUrl ) && fromUrl)) + throw new ApiError(meta.errors.remoteAccountForbids); const updates = {} as Partial; - if (!toUrl) toUrl = ''; + if (!toUrl) toUrl = ""; updates.movedToUri = toUrl; await Users.update(user.id, updates); @@ -145,23 +149,26 @@ export default define(meta, paramDef, async (ps, user) => { dm.execute(); // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); + publishMainStream(user.id, "meUpdated", iObj); const followings = await Followings.findBy({ followeeId: user.id, }); - followings.forEach(async following => { + followings.forEach(async (following) => { //if follower is local if (!following.followerHost) { - const follower = await getUser(following.followerId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const follower = await getUser(following.followerId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); await deleteFollowing(follower!, user); try { await create(follower!, moveTo); - } catch (e) { /* empty */ } + } catch (e) { + /* empty */ + } } }); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 2b343dabd..af65ad27b 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,13 +1,19 @@ -import { Brackets } from 'typeorm'; -import { Notifications, Followings, Mutings, Users, UserProfiles } 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 { Brackets } from "typeorm"; +import { + Notifications, + Followings, + Mutings, + Users, + UserProfiles, +} 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"; export const meta = { - tags: ['account', 'notifications'], + tags: ["account", "notifications"], requireCredential: true, @@ -16,34 +22,44 @@ export const meta = { max: 15, }, - kind: 'read:notifications', + kind: "read:notifications", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Notification', + type: "object", + optional: false, + nullable: false, + ref: "Notification", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - following: { type: 'boolean', default: false }, - unreadOnly: { type: 'boolean', default: false }, - markAsRead: { type: 'boolean', default: true }, - includeTypes: { type: 'array', items: { - type: 'string', enum: notificationTypes, - } }, - excludeTypes: { type: 'array', items: { - type: 'string', enum: notificationTypes, - } }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + following: { type: "boolean", default: false }, + unreadOnly: { type: "boolean", default: false }, + markAsRead: { type: "boolean", default: true }, + includeTypes: { + type: "array", + items: { + type: "string", + enum: notificationTypes, + }, + }, + excludeTypes: { + type: "array", + items: { + type: "string", + enum: notificationTypes, + }, + }, }, required: [], } as const; @@ -55,86 +71,113 @@ export default define(meta, paramDef, async (ps, user) => { return []; } // excludeTypes に全指定されている場合はクエリしない - if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { + 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 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 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 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 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'); + 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.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.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'); - })); + 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.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 }); + 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 }); + query.andWhere("notification.type NOT IN (:...excludeTypes)", { + excludeTypes: ps.excludeTypes, + }); } if (ps.unreadOnly) { - query.andWhere('notification.isRead = false'); + 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)); + readNotification( + user.id, + notifications.map((x) => x.id), + ); } - const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); + const notes = notifications + .filter((notification) => + ["mention", "reply", "quote"].includes(notification.type), + ) + .map((notification) => notification.note!); if (notes.length > 0) { read(user.id, notes); 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 987387237..8400a2abd 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -1,29 +1,32 @@ -import { PageLikes } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { PageLikes } from "@/models/index.js"; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'pages'], + tags: ["account", "pages"], requireCredential: true, - kind: 'read:page-likes', + kind: "read:page-likes", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, page: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, }, }, @@ -31,24 +34,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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'); + const query = makePaginationQuery( + PageLikes.createQueryBuilder("like"), + ps.sinceId, + ps.untilId, + ) + .andWhere("like.userId = :meId", { meId: user.id }) + .leftJoinAndSelect("like.page", "page"); - const likes = await query - .take(ps.limit) - .getMany(); + const likes = await query.take(ps.limit).getMany(); return PageLikes.packMany(likes, user); }); diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 7e1820d45..5ab1ebf04 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -1,43 +1,46 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Pages } from "@/models/index.js"; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'pages'], + tags: ["account", "pages"], requireCredential: true, - kind: 'read:pages', + kind: "read:pages", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 query = makePaginationQuery( + Pages.createQueryBuilder("page"), + ps.sinceId, + ps.untilId, + ).andWhere("page.userId = :meId", { meId: user.id }); - const pages = await query - .take(ps.limit) - .getMany(); + const pages = await query.take(ps.limit).getMany(); return await Pages.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 67b7026be..e6b2fd6d9 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -1,56 +1,60 @@ -import { addPinned } from '@/services/i/pin.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; +import { addPinned } from "@/services/i/pin.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Users } from "@/models/index.js"; export const meta = { - tags: ['account', 'notes'], + tags: ["account", "notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '56734f8b-3928-431e-bf80-6ff87df40cb3', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "56734f8b-3928-431e-bf80-6ff87df40cb3", }, pinLimitExceeded: { - message: 'You can not pin notes any more.', - code: 'PIN_LIMIT_EXCEEDED', - id: '72dab508-c64d-498f-8740-a8eec1ba385a', + message: "You can not pin notes any more.", + code: "PIN_LIMIT_EXCEEDED", + id: "72dab508-c64d-498f-8740-a8eec1ba385a", }, alreadyPinned: { - message: 'That note has already been pinned.', - code: 'ALREADY_PINNED', - id: '8b18c2b7-68fe-4edb-9892-c0cbaeb6c913', + message: "That note has already been pinned.", + code: "ALREADY_PINNED", + id: "8b18c2b7-68fe-4edb-9892-c0cbaeb6c913", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'MeDetailed', + type: "object", + optional: false, + nullable: false, + ref: "MeDetailed", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } 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); + 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; }); 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 7ff6409ca..994fb6efd 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,17 +1,17 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { MessagingMessages, UserGroupJoinings } from '@/models/index.js'; +import { publishMainStream } from "@/services/stream.js"; +import define from "../../define.js"; +import { MessagingMessages, UserGroupJoinings } from "@/models/index.js"; export const meta = { - tags: ['account', 'messaging'], + tags: ["account", "messaging"], requireCredential: true, - kind: 'write:account', + kind: "write:account", } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -19,23 +19,31 @@ export const paramDef = { // 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, - }); + 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())); + 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'); + publishMainStream(user.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 49f3deb33..43ce3ed4f 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,17 +1,17 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import { NoteUnreads } from '@/models/index.js'; +import { publishMainStream } from "@/services/stream.js"; +import define from "../../define.js"; +import { NoteUnreads } from "@/models/index.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'write:account', + kind: "write:account", } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -24,6 +24,6 @@ export default define(meta, paramDef, async (ps, user) => { }); // 全て既読になったイベントを発行 - publishMainStream(user.id, 'readAllUnreadMentions'); - publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); + publishMainStream(user.id, "readAllUnreadMentions"); + publishMainStream(user.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 45b6e98c8..e15b89a16 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -1,31 +1,31 @@ -import define from '../../define.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'; +import define from "../../define.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'], + tags: ["account"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchAnnouncement: { - message: 'No such announcement.', - code: 'NO_SUCH_ANNOUNCEMENT', - id: '184663db-df88-4bc2-8b52-fb85f0681939', + message: "No such announcement.", + code: "NO_SUCH_ANNOUNCEMENT", + id: "184663db-df88-4bc2-8b52-fb85f0681939", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - announcementId: { type: 'string', format: 'misskey:id' }, + announcementId: { type: "string", format: "misskey:id" }, }, - required: ['announcementId'], + required: ["announcementId"], } as const; // eslint-disable-next-line import/no-default-export @@ -55,7 +55,7 @@ export default define(meta, paramDef, async (ps, user) => { userId: user.id, }); - if (!await Users.getHasUnreadAnnouncement(user.id)) { - publishMainStream(user.id, 'readAllAnnouncements'); + if (!(await Users.getHasUnreadAnnouncement(user.id))) { + publishMainStream(user.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 af929b04e..342d2c957 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,12 @@ -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 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"; export const meta = { requireCredential: true, @@ -11,11 +15,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, + password: { type: "string" }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -29,7 +33,7 @@ export default define(meta, paramDef, async (ps, user) => { const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { - throw new Error('incorrect password'); + throw new Error("incorrect password"); } const newToken = generateUserToken(); @@ -39,11 +43,15 @@ export default define(meta, paramDef, async (ps, user) => { }); // Publish event - publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken }); - publishMainStream(user.id, 'myTokenRegenerated'); + publishInternalEvent("userTokenRegenerated", { + id: user.id, + oldToken, + newToken, + }); + publishMainStream(user.id, "myTokenRegenerated"); // Terminate streaming setTimeout(() => { - publishUserEvent(user.id, 'terminate', {}); + publishUserEvent(user.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 d0b16dbc4..f4503c82f 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,5 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,21 +8,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, required: [], } 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 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(); 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 cc5d5a8c6..53ce6222f 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 define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { requireCredential: true, @@ -9,31 +9,36 @@ export const meta = { errors: { noSuchKey: { - message: 'No such key.', - code: 'NO_SUCH_KEY', - id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a', + message: "No such key.", + code: "NO_SUCH_KEY", + id: "97a1e8e7-c0f7-47d2-957a-92e61256e01a", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - key: { type: 'string' }, - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + key: { type: "string" }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, - required: ['key'], + required: ["key"], } 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 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(); 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 a79319744..c87b1a93a 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 define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { requireCredential: true, @@ -9,31 +9,36 @@ export const meta = { errors: { noSuchKey: { - message: 'No such key.', - code: 'NO_SUCH_KEY', - id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a', + message: "No such key.", + code: "NO_SUCH_KEY", + id: "ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - key: { type: 'string' }, - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + key: { type: "string" }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, - required: ['key'], + required: ["key"], } 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 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(); 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 ac209c06a..ba2bbe09e 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,5 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,21 +8,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, required: [], } 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 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(); @@ -31,13 +36,19 @@ export default define(meta, paramDef, async (ps, user) => { 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' : - type === 'string' ? 'string' : - type === 'boolean' ? 'boolean' : - type === 'object' ? 'object' : - null as never; + item.value === null + ? "null" + : Array.isArray(item.value) + ? "array" + : type === "number" + ? "number" + : type === "string" + ? "string" + : type === "boolean" + ? "boolean" + : type === "object" + ? "object" + : (null as never); } 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 5ea1a9d34..72f84dd6c 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,5 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,24 +8,29 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, required: [], } 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 }); + 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 }); 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 92473654c..e745024f2 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 define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; -import { ApiError } from '../../../error.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; +import { ApiError } from "../../../error.js"; export const meta = { requireCredential: true, @@ -9,31 +9,36 @@ export const meta = { errors: { noSuchKey: { - message: 'No such key.', - code: 'NO_SUCH_KEY', - id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019', + message: "No such key.", + code: "NO_SUCH_KEY", + id: "1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - key: { type: 'string' }, - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + key: { type: "string" }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, - required: ['key'], + required: ["key"], } 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 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(); 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 de4b313e2..96e6678d4 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,5 @@ -import define from '../../../define.js'; -import { RegistryItems } from '@/models/index.js'; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; export const meta = { requireCredential: true, @@ -8,24 +8,24 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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 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; + if (res.some((scope) => scope.join(".") === item.scope.join("."))) continue; res.push(item.scope); } 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 d380b428a..16ec83d38 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,7 @@ -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 { publishMainStream } from "@/services/stream.js"; +import define from "../../../define.js"; +import { RegistryItems } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; export const meta = { requireCredential: true, @@ -10,24 +10,29 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - key: { type: 'string', minLength: 1 }, + key: { type: "string", minLength: 1 }, value: {}, - scope: { type: 'array', default: [], items: { - type: 'string', pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), - } }, + scope: { + type: "array", + default: [], + items: { + type: "string", + pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1), + }, + }, }, - required: ['key', 'value'], + required: ["key", "value"], } 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 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(); @@ -50,7 +55,7 @@ export default define(meta, paramDef, async (ps, user) => { } // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする - publishMainStream(user.id, 'registryUpdated', { + 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 c69245379..da51ab852 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 define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { publishUserEvent } from '@/services/stream.js'; +import define from "../../define.js"; +import { AccessTokens } from "@/models/index.js"; +import { publishUserEvent } from "@/services/stream.js"; export const meta = { requireCredential: true, @@ -9,11 +9,11 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - tokenId: { type: 'string', format: 'misskey:id' }, + tokenId: { type: "string", format: "misskey:id" }, }, - required: ['tokenId'], + required: ["tokenId"], } as const; // eslint-disable-next-line import/no-default-export @@ -27,6 +27,6 @@ export default define(meta, paramDef, async (ps, user) => { }); // Terminate streaming - publishUserEvent(user.id, 'terminate'); + publishUserEvent(user.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 ca3741166..da2fa23da 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 define from '../../define.js'; -import { Signins } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { Signins } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { requireCredential: true, @@ -9,21 +9,24 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 }); + const query = makePaginationQuery( + Signins.createQueryBuilder("signin"), + ps.sinceId, + ps.untilId, + ).andWhere("signin.userId = :meId", { meId: user.id }); const history = await query.take(ps.limit).getMany(); - return await Promise.all(history.map(record => Signins.pack(record))); + return await Promise.all(history.map((record) => Signins.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 9912689da..aeb49be16 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -1,42 +1,44 @@ -import { removePinned } from '@/services/i/pin.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; +import { removePinned } from "@/services/i/pin.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { Users } from "@/models/index.js"; export const meta = { - tags: ['account', 'notes'], + tags: ["account", "notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '454170ce-9d63-4a43-9da1-ea10afe81e21', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "454170ce-9d63-4a43-9da1-ea10afe81e21", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'MeDetailed', + type: "object", + optional: false, + nullable: false, + ref: "MeDetailed", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } 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); + await removePinned(user, ps.noteId).catch((e) => { + if (e.id === "b302d4cf-c050-400a-bbb3-be208681f40c") + throw new ApiError(meta.errors.noSuchNote); throw e; }); 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 7cfb88978..e51a2c73e 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,13 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../../define.js'; -import rndstr from 'rndstr'; -import config from '@/config/index.js'; -import bcrypt from 'bcryptjs'; -import { Users, UserProfiles } from '@/models/index.js'; -import { sendEmail } from '@/services/send-email.js'; -import { ApiError } from '../../error.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; -import { HOUR } from '@/const.js'; +import { publishMainStream } from "@/services/stream.js"; +import define from "../../define.js"; +import rndstr from "rndstr"; +import config from "@/config/index.js"; +import bcrypt from "bcryptjs"; +import { Users, UserProfiles } from "@/models/index.js"; +import { sendEmail } from "@/services/send-email.js"; +import { ApiError } from "../../error.js"; +import { validateEmailForAccount } from "@/services/validate-email-for-account.js"; +import { HOUR } from "@/const.js"; export const meta = { requireCredential: true, @@ -21,26 +21,26 @@ export const meta = { errors: { incorrectPassword: { - message: 'Incorrect password.', - code: 'INCORRECT_PASSWORD', - id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3', + message: "Incorrect password.", + code: "INCORRECT_PASSWORD", + id: "e54c1d7e-e7d6-4103-86b6-0a95069b4ad3", }, unavailable: { - message: 'Unavailable email address.', - code: 'UNAVAILABLE', - id: 'a2defefb-f220-8849-0af6-17f816099323', + message: "Unavailable email address.", + code: "UNAVAILABLE", + id: "a2defefb-f220-8849-0af6-17f816099323", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - password: { type: 'string' }, - email: { type: 'string', nullable: true }, + password: { type: "string" }, + email: { type: "string", nullable: true }, }, - required: ['password'], + required: ["password"], } as const; // eslint-disable-next-line import/no-default-export @@ -73,10 +73,10 @@ export default define(meta, paramDef, async (ps, user) => { }); // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); + publishMainStream(user.id, "meUpdated", iObj); if (ps.email != null) { - const code = rndstr('a-z0-9', 16); + const code = rndstr("a-z0-9", 16); await UserProfiles.update(user.id, { emailVerifyCode: code, @@ -84,9 +84,12 @@ export default define(meta, paramDef, async (ps, user) => { const link = `${config.url}/verify-email/${code}`; - sendEmail(ps.email, 'Email verification', + sendEmail( + ps.email, + "Email verification", `To verify email, please click this link:
${link}`, - `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 cdc974f5c..f3e13757a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -1,121 +1,136 @@ -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 { 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 { notificationTypes } from '@/types.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { langmap } from '@/misc/langmap.js'; -import { ApiError } from '../../error.js'; -import define from '../../define.js'; +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 { 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 type { User } from "@/models/entities/user.js"; +import type { UserProfile } from "@/models/entities/user-profile.js"; +import { notificationTypes } from "@/types.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; +import { langmap } from "@/misc/langmap.js"; +import { ApiError } from "../../error.js"; +import define from "../../define.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchAvatar: { - message: 'No such avatar file.', - code: 'NO_SUCH_AVATAR', - id: '539f3a45-f215-4f81-a9a8-31293640207f', + message: "No such avatar file.", + code: "NO_SUCH_AVATAR", + id: "539f3a45-f215-4f81-a9a8-31293640207f", }, noSuchBanner: { - message: 'No such banner file.', - code: 'NO_SUCH_BANNER', - id: '0d8f5629-f210-41c2-9433-735831a58595', + message: "No such banner file.", + code: "NO_SUCH_BANNER", + id: "0d8f5629-f210-41c2-9433-735831a58595", }, avatarNotAnImage: { - message: 'The file specified as an avatar is not an image.', - code: 'AVATAR_NOT_AN_IMAGE', - id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191', + message: "The file specified as an avatar is not an image.", + code: "AVATAR_NOT_AN_IMAGE", + id: "f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191", }, bannerNotAnImage: { - message: 'The file specified as a banner is not an image.', - code: 'BANNER_NOT_AN_IMAGE', - id: '75aedb19-2afd-4e6d-87fc-67941256fa60', + message: "The file specified as a banner is not an image.", + code: "BANNER_NOT_AN_IMAGE", + id: "75aedb19-2afd-4e6d-87fc-67941256fa60", }, noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: '8e01b590-7eb9-431b-a239-860e086c408e', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "8e01b590-7eb9-431b-a239-860e086c408e", }, invalidRegexp: { - message: 'Invalid Regular Expression.', - code: 'INVALID_REGEXP', - id: '0d786918-10df-41cd-8f33-8dec7d9a89a5', + message: "Invalid Regular Expression.", + code: "INVALID_REGEXP", + id: "0d786918-10df-41cd-8f33-8dec7d9a89a5", }, }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'MeDetailed', + type: "object", + optional: false, + nullable: false, + ref: "MeDetailed", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { name: { ...Users.nameSchema, nullable: true }, description: { ...Users.descriptionSchema, nullable: true }, location: { ...Users.locationSchema, nullable: true }, birthday: { ...Users.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 }, + 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 }, fields: { - type: 'array', + type: "array", minItems: 0, maxItems: 16, items: { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - value: { type: 'string' }, + name: { type: "string" }, + value: { type: "string" }, }, - required: ['name', 'value'], + required: ["name", "value"], + }, + }, + isLocked: { type: "boolean" }, + isExplorable: { type: "boolean" }, + hideOnlineStatus: { type: "boolean" }, + publicReactions: { type: "boolean" }, + carefulBot: { type: "boolean" }, + autoAcceptFollowed: { type: "boolean" }, + noCrawle: { type: "boolean" }, + isBot: { type: "boolean" }, + isCat: { type: "boolean" }, + showTimelineReplies: { type: "boolean" }, + injectFeaturedNote: { type: "boolean" }, + receiveAnnouncementEmail: { type: "boolean" }, + alwaysMarkNsfw: { type: "boolean" }, + autoSensitive: { type: "boolean" }, + ffVisibility: { type: "string", enum: ["public", "followers", "private"] }, + pinnedPageId: { type: "string", format: "misskey:id", nullable: true }, + mutedWords: { type: "array" }, + mutedInstances: { + type: "array", + items: { + type: "string", + }, + }, + mutingNotificationTypes: { + type: "array", + items: { + type: "string", + enum: notificationTypes, + }, + }, + emailNotificationTypes: { + type: "array", + items: { + type: "string", }, }, - isLocked: { type: 'boolean' }, - isExplorable: { type: 'boolean' }, - hideOnlineStatus: { type: 'boolean' }, - publicReactions: { type: 'boolean' }, - carefulBot: { type: 'boolean' }, - autoAcceptFollowed: { type: 'boolean' }, - noCrawle: { type: 'boolean' }, - isBot: { type: 'boolean' }, - isCat: { type: 'boolean' }, - showTimelineReplies: { type: 'boolean' }, - injectFeaturedNote: { type: 'boolean' }, - receiveAnnouncementEmail: { type: 'boolean' }, - alwaysMarkNsfw: { type: 'boolean' }, - autoSensitive: { type: 'boolean' }, - ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] }, - pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true }, - mutedWords: { type: 'array' }, - mutedInstances: { type: 'array', items: { - type: 'string', - } }, - mutingNotificationTypes: { type: 'array', items: { - type: 'string', enum: notificationTypes, - } }, - emailNotificationTypes: { type: 'array', items: { - type: 'string', - } }, }, } as const; @@ -134,61 +149,83 @@ export default define(meta, paramDef, async (ps, _user, token) => { 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.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); + 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); - } - }); + 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.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 (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 (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 (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) { @@ -197,8 +234,14 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (ps.fields) { profileUpdates.fields = ps.fields - .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') - .map(x => { + .filter( + (x) => + typeof x.name === "string" && + x.name !== "" && + typeof x.value === "string" && + x.value !== "", + ) + .map((x) => { return { name: x.name, value: x.value }; }); } @@ -209,7 +252,10 @@ export default define(meta, paramDef, async (ps, _user, token) => { let tags = [] as string[]; const newName = updates.name === undefined ? user.name : updates.name; - const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; + const newDescription = + profileUpdates.description === undefined + ? profile.description + : profileUpdates.description; if (newName != null) { const tokens = mfm.parseSimple(newName); @@ -219,7 +265,9 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (newDescription != null) { const tokens = mfm.parse(newDescription); emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); - tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); + tags = extractHashtags(tokens!) + .map((tag) => normalizeForSearch(tag)) + .splice(0, 32); } updates.emojis = emojis; @@ -230,7 +278,8 @@ export default define(meta, paramDef, async (ps, _user, token) => { //#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(profileUpdates).length > 0) + await UserProfiles.update(user.id, profileUpdates); const iObj = await Users.pack(user.id, user, { detail: true, @@ -238,8 +287,12 @@ export default define(meta, paramDef, async (ps, _user, token) => { }); // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id })); + publishMainStream(user.id, "meUpdated", iObj); + publishUserEvent( + user.id, + "updateUserProfile", + await UserProfiles.findOneBy({ userId: user.id }), + ); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { 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 1d7e4a16b..715a04f0d 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,30 +1,34 @@ -import define from '../../define.js'; -import { UserGroupInvitations } from '@/models/index.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import define from "../../define.js"; +import { UserGroupInvitations } from "@/models/index.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['account', 'groups'], + tags: ["account", "groups"], requireCredential: true, - kind: 'read:user-groups', + kind: "read:user-groups", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, group: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, }, }, @@ -32,24 +36,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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'); + const query = makePaginationQuery( + UserGroupInvitations.createQueryBuilder("invitation"), + ps.sinceId, + ps.untilId, + ) + .andWhere("invitation.userId = :meId", { meId: user.id }) + .leftJoinAndSelect("invitation.userGroup", "user_group"); - const invitations = await query - .take(ps.limit) - .getMany(); + const invitations = await query.take(ps.limit).getMany(); return await UserGroupInvitations.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 2e2fd00b8..c3ad998c4 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -1,28 +1,32 @@ -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 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"; export const meta = { - tags: ['webhooks'], + tags: ["webhooks"], requireCredential: true, - kind: 'write:account', + kind: "write:account", } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 100 }, - url: { type: 'string', minLength: 1, maxLength: 1024 }, - secret: { type: 'string', minLength: 1, maxLength: 1024 }, - on: { type: 'array', items: { - type: 'string', enum: webhookEventTypes, - } }, + name: { type: "string", minLength: 1, maxLength: 100 }, + url: { type: "string", minLength: 1, maxLength: 1024 }, + secret: { type: "string", minLength: 1, maxLength: 1024 }, + on: { + type: "array", + items: { + type: "string", + enum: webhookEventTypes, + }, + }, }, - required: ['name', 'url', 'secret', 'on'], + required: ["name", "url", "secret", "on"], } as const; // eslint-disable-next-line import/no-default-export @@ -35,9 +39,9 @@ export default define(meta, paramDef, async (ps, user) => { url: ps.url, secret: ps.secret, on: ps.on, - }).then(x => Webhooks.findOneByOrFail(x.identifiers[0])); + }).then((x) => Webhooks.findOneByOrFail(x.identifiers[0])); - publishInternalEvent('webhookCreated', webhook); + 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 2821eaa5f..3530e959e 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts @@ -1,30 +1,30 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; -import { publishInternalEvent } from '@/services/stream.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { Webhooks } from "@/models/index.js"; +import { publishInternalEvent } from "@/services/stream.js"; export const meta = { - tags: ['webhooks'], + tags: ["webhooks"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchWebhook: { - message: 'No such webhook.', - code: 'NO_SUCH_WEBHOOK', - id: 'bae73e5a-5522-4965-ae19-3a8688e71d82', + message: "No such webhook.", + code: "NO_SUCH_WEBHOOK", + id: "bae73e5a-5522-4965-ae19-3a8688e71d82", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - webhookId: { type: 'string', format: 'misskey:id' }, + webhookId: { type: "string", format: "misskey:id" }, }, - required: ['webhookId'], + required: ["webhookId"], } as const; // eslint-disable-next-line import/no-default-export @@ -40,5 +40,5 @@ export default define(meta, paramDef, async (ps, user) => { await Webhooks.delete(webhook.id); - publishInternalEvent('webhookDeleted', webhook); + 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 54e456373..0691a34e1 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -1,16 +1,16 @@ -import define from '../../../define.js'; -import { Webhooks } from '@/models/index.js'; +import define from "../../../define.js"; +import { Webhooks } from "@/models/index.js"; export const meta = { - tags: ['webhooks', 'account'], + tags: ["webhooks", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; 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 02fa1edb5..cb86f7226 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -1,29 +1,29 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { Webhooks } from '@/models/index.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { Webhooks } from "@/models/index.js"; export const meta = { - tags: ['webhooks'], + tags: ["webhooks"], requireCredential: true, - kind: 'read:account', + kind: "read:account", errors: { noSuchWebhook: { - message: 'No such webhook.', - code: 'NO_SUCH_WEBHOOK', - id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098', + message: "No such webhook.", + code: "NO_SUCH_WEBHOOK", + id: "50f614d9-3047-4f7e-90d8-ad6b2d5fb098", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - webhookId: { type: 'string', format: 'misskey:id' }, + webhookId: { type: "string", format: "misskey:id" }, }, - required: ['webhookId'], + required: ["webhookId"], } as const; // eslint-disable-next-line import/no-default-export 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 f87b9753f..525fb9a8b 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts @@ -1,39 +1,42 @@ -import define from '../../../define.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'; +import define from "../../../define.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'], + tags: ["webhooks"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchWebhook: { - message: 'No such webhook.', - code: 'NO_SUCH_WEBHOOK', - id: 'fb0fea69-da18-45b1-828d-bd4fd1612518', + message: "No such webhook.", + code: "NO_SUCH_WEBHOOK", + id: "fb0fea69-da18-45b1-828d-bd4fd1612518", }, }, - } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - webhookId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 100 }, - url: { type: 'string', minLength: 1, maxLength: 1024 }, - secret: { type: 'string', minLength: 1, maxLength: 1024 }, - on: { type: 'array', items: { - type: 'string', enum: webhookEventTypes, - } }, - active: { type: 'boolean' }, + webhookId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 100 }, + url: { type: "string", minLength: 1, maxLength: 1024 }, + secret: { type: "string", minLength: 1, maxLength: 1024 }, + on: { + type: "array", + items: { + type: "string", + enum: webhookEventTypes, + }, + }, + active: { type: "boolean" }, }, - required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'], + required: ["webhookId", "name", "url", "secret", "on", "active"], } as const; // eslint-disable-next-line import/no-default-export @@ -55,5 +58,5 @@ export default define(meta, paramDef, async (ps, user) => { active: ps.active, }); - publishInternalEvent('webhookUpdated', webhook); + publishInternalEvent("webhookUpdated", webhook); }); diff --git a/packages/backend/src/server/api/endpoints/latest-version.ts b/packages/backend/src/server/api/endpoints/latest-version.ts index b319da9e3..343291142 100644 --- a/packages/backend/src/server/api/endpoints/latest-version.ts +++ b/packages/backend/src/server/api/endpoints/latest-version.ts @@ -1,14 +1,14 @@ -import define from '../define.js'; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -16,7 +16,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { let tag_name; - await fetch('https://codeberg.org/api/v1/repos/calckey/calckey/releases?draft=false&pre-release=false&page=1&limit=1') + await fetch( + "https://codeberg.org/api/v1/repos/calckey/calckey/releases?draft=false&pre-release=false&page=1&limit=1", + ) .then((response) => response.json()) .then((data) => { tag_name = data[0].tag_name; diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 241cbbbc1..c8514deae 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -1,31 +1,37 @@ -import { Brackets } from 'typeorm'; -import type { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { MessagingMessages, Mutings, UserGroupJoinings } from '@/models/index.js'; -import define from '../../define.js'; +import { Brackets } from "typeorm"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { + MessagingMessages, + Mutings, + UserGroupJoinings, +} from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['messaging'], + tags: ["messaging"], requireCredential: true, - kind: 'read:messaging', + kind: "read:messaging", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', + type: "object", + optional: false, + nullable: false, + ref: "MessagingMessage", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - group: { type: 'boolean', default: false }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + group: { type: "boolean", default: false }, }, required: [], } as const; @@ -36,9 +42,11 @@ export default define(meta, paramDef, async (ps, user) => { muterId: user.id, }); - const groups = ps.group ? await UserGroupJoinings.findBy({ - userId: user.id, - }).then(xs => xs.map(x => x.userGroupId)) : []; + const groups = ps.group + ? await UserGroupJoinings.findBy({ + userId: user.id, + }).then((xs) => xs.map((x) => x.userGroupId)) + : []; if (ps.group && groups.length === 0) { return []; @@ -48,33 +56,45 @@ export default define(meta, paramDef, async (ps, user) => { 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!); + ? 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'); + const query = MessagingMessages.createQueryBuilder("message").orderBy( + "message.createdAt", + "DESC", + ); if (ps.group) { - query.where('message.groupId IN (:...groups)', { groups: groups }); + query.where("message.groupId IN (:...groups)", { groups: groups }); if (found.length > 0) { - query.andWhere('message.groupId NOT IN (:...found)', { found: found }); + 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'); + 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 }); + 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) }); + 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), + }); } } @@ -87,5 +107,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user))); + 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 dbf1f6c86..6edddb458 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -1,69 +1,80 @@ -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 { Brackets } from 'typeorm'; -import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from '../../common/read-messaging-message.js'; +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 { Brackets } from "typeorm"; +import { + readUserMessagingMessage, + readGroupMessagingMessage, + deliverReadActivity, +} from "../../common/read-messaging-message.js"; export const meta = { - tags: ['messaging'], + tags: ["messaging"], requireCredential: true, - kind: 'read:messaging', + kind: "read:messaging", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', + type: "object", + optional: false, + nullable: false, + ref: "MessagingMessage", }, }, errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '11795c64-40ea-4198-b06e-3c873ed9039d', + message: "No such user.", + code: "NO_SUCH_USER", + id: "11795c64-40ea-4198-b06e-3c873ed9039d", }, noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "c4d9f88c-9270-4632-b032-6ed8cee36f7f", }, groupAccessDenied: { - message: 'You can not read messages of groups that you have not joined.', - code: 'GROUP_ACCESS_DENIED', - id: 'a053a8dd-a491-4718-8f87-50775aad9284', + message: "You can not read messages of groups that you have not joined.", + code: "GROUP_ACCESS_DENIED", + id: "a053a8dd-a491-4718-8f87-50775aad9284", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - markAsRead: { type: 'boolean', default: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + markAsRead: { type: "boolean", default: true }, }, anyOf: [ { properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], }, { properties: { - groupId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, }, - required: ['groupId'], + required: ["groupId"], }, ], } as const; @@ -72,30 +83,46 @@ export const paramDef = { 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); + 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 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)); + readUserMessagingMessage( + user.id, + recipient.id, + messages.filter((m) => m.recipientId === user.id).map((x) => x.id), + ); // リモートユーザーとのメッセージだったら既読配信 if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) { @@ -103,9 +130,13 @@ export default define(meta, paramDef, async (ps, user) => { } } - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateRecipient: 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 }); @@ -124,18 +155,29 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.groupAccessDenied); } - const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) - .andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id }); + 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)); + readGroupMessagingMessage( + user.id, + recipientGroup.id, + messages.map((x) => x.id), + ); } - return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { - populateGroup: false, - }))); + 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 405af5ec1..bb3a616ba 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -1,87 +1,95 @@ -import define from '../../../define.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'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; +import { + MessagingMessages, + DriveFiles, + UserGroups, + UserGroupJoinings, + Blockings, +} from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import { createMessage } from "@/services/messages/create.js"; export const meta = { - tags: ['messaging'], + tags: ["messaging"], requireCredential: true, - kind: 'write:messaging', + kind: "write:messaging", res: { - type: 'object', - optional: false, nullable: false, - ref: 'MessagingMessage', + type: "object", + optional: false, + nullable: false, + ref: "MessagingMessage", }, errors: { recipientIsYourself: { - message: 'You can not send a message to yourself.', - code: 'RECIPIENT_IS_YOURSELF', - id: '17e2ba79-e22a-4cbc-bf91-d327643f4a7e', + message: "You can not send a message to yourself.", + code: "RECIPIENT_IS_YOURSELF", + id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '11795c64-40ea-4198-b06e-3c873ed9039d', + message: "No such user.", + code: "NO_SUCH_USER", + id: "11795c64-40ea-4198-b06e-3c873ed9039d", }, noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "c94e2a5d-06aa-4914-8fa6-6a42e73d6537", }, groupAccessDenied: { - message: 'You can not send messages to groups that you have not joined.', - code: 'GROUP_ACCESS_DENIED', - id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd', + message: "You can not send messages to groups that you have not joined.", + code: "GROUP_ACCESS_DENIED", + id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd", }, noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: '4372b8e2-185d-4146-8749-2f68864a3e5f', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "4372b8e2-185d-4146-8749-2f68864a3e5f", }, contentRequired: { - message: 'Content required. You need to set text or fileId.', - code: 'CONTENT_REQUIRED', - id: '25587321-b0e6-449c-9239-f8925092942c', + message: "Content required. You need to set text or fileId.", + code: "CONTENT_REQUIRED", + id: "25587321-b0e6-449c-9239-f8925092942c", }, youHaveBeenBlocked: { - message: 'You cannot send a message because you have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: 'c15a5199-7422-4968-941a-2a462c478f7d', + message: + "You cannot send a message because you have been blocked by this user.", + code: "YOU_HAVE_BEEN_BLOCKED", + id: "c15a5199-7422-4968-941a-2a462c478f7d", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - text: { type: 'string', nullable: true, maxLength: 3000 }, - fileId: { type: 'string', format: 'misskey:id' }, + text: { type: "string", nullable: true, maxLength: 3000 }, + fileId: { type: "string", format: "misskey:id" }, }, anyOf: [ { properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], }, { properties: { - groupId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, }, - required: ['groupId'], + required: ["groupId"], }, ], } as const; @@ -98,8 +106,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Fetch recipient (user) - recipientUser = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + recipientUser = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -147,5 +156,11 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.contentRequired); } - return await createMessage(user, recipientUser, recipientGroup, ps.text, file); + 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 17d295700..c9c0fda0f 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,15 +1,15 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { deleteMessage } from '@/services/messages/delete.js'; -import { SECOND, HOUR } from '@/const.js'; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { MessagingMessages } from "@/models/index.js"; +import { deleteMessage } from "@/services/messages/delete.js"; +import { SECOND, HOUR } from "@/const.js"; export const meta = { - tags: ['messaging'], + tags: ["messaging"], requireCredential: true, - kind: 'write:messaging', + kind: "write:messaging", limit: { duration: HOUR, @@ -19,19 +19,19 @@ export const meta = { errors: { noSuchMessage: { - message: 'No such message.', - code: 'NO_SUCH_MESSAGE', - id: '54b5b326-7925-42cf-8019-130fda8b56af', + message: "No such message.", + code: "NO_SUCH_MESSAGE", + id: "54b5b326-7925-42cf-8019-130fda8b56af", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - messageId: { type: 'string', format: 'misskey:id' }, + messageId: { type: "string", format: "misskey:id" }, }, - required: ['messageId'], + required: ["messageId"], } as const; // eslint-disable-next-line import/no-default-export 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 db12ae922..ae5d07bc3 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -1,30 +1,33 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { MessagingMessages } from '@/models/index.js'; -import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../common/read-messaging-message.js'; +import define from "../../../define.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'], + tags: ["messaging"], requireCredential: true, - kind: 'write:messaging', + kind: "write:messaging", errors: { noSuchMessage: { - message: 'No such message.', - code: 'NO_SUCH_MESSAGE', - id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14', + message: "No such message.", + code: "NO_SUCH_MESSAGE", + id: "86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - messageId: { type: 'string', format: 'misskey:id' }, + messageId: { type: "string", format: "misskey:id" }, }, - required: ['messageId'], + required: ["messageId"], } as const; // eslint-disable-next-line import/no-default-export @@ -36,13 +39,19 @@ export default define(meta, paramDef, async (ps, user) => { } 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; - }); + 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); + 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; }); } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 3e3288d6d..53238281d 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,327 +1,397 @@ -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 { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import define from '../define.js'; +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 { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js"; +import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { maintainerName: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, maintainerEmail: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, version: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, example: config.version, }, name: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, uri: { - type: 'string', - optional: false, nullable: false, - format: 'url', - example: 'https://calckey.example.com', + type: "string", + optional: false, + nullable: false, + format: "url", + example: "https://calckey.example.com", }, description: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, langs: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, tosUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, repositoryUrl: { - type: 'string', - optional: false, nullable: false, - default: 'https://codeberg.org/calckey/calckey', + type: "string", + optional: false, + nullable: false, + default: "https://codeberg.org/calckey/calckey", }, feedbackUrl: { - type: 'string', - optional: false, nullable: false, - default: 'https://codeberg.org/calckey/calckey/issues', + type: "string", + optional: false, + nullable: false, + default: "https://codeberg.org/calckey/calckey/issues", }, defaultDarkTheme: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, defaultLightTheme: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, disableRegistration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, disableLocalTimeline: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, disableRecommendedTimeline: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, disableGlobalTimeline: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, driveCapacityPerLocalUserMb: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, driveCapacityPerRemoteUserMb: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, cacheRemoteFiles: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, emailRequiredForSignup: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableHcaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hcaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, enableRecaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, recaptchaSiteKey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, swPublickey: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, mascotImageUrl: { - type: 'string', - optional: false, nullable: false, - default: '/assets/ai.png', + type: "string", + optional: false, + nullable: false, + default: "/assets/ai.png", }, bannerUrl: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, errorImageUrl: { - type: 'string', - optional: false, nullable: false, - default: 'https://xn--931a.moe/aiart/yubitun.png', + type: "string", + optional: false, + nullable: false, + default: "https://xn--931a.moe/aiart/yubitun.png", }, iconUrl: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, maxNoteTextLength: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, emojis: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, aliases: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, category: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', + type: "string", + optional: false, + nullable: true, + description: "The local host is represented with `null`.", }, url: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, }, }, }, ads: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { place: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, url: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, imageUrl: { - type: 'string', - optional: false, nullable: false, - format: 'url', + type: "string", + optional: false, + nullable: false, + format: "url", }, }, }, }, requireSetup: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, example: false, }, enableEmail: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableTwitterIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableGithubIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableDiscordIntegration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, enableServiceWorker: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, translatorAvailable: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, proxyAccountName: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, features: { - type: 'object', - optional: true, nullable: false, + type: "object", + optional: true, + nullable: false, properties: { registration: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, localTimeLine: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, recommendedTimeLine: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, globalTimeLine: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, elasticsearch: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hcaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, recaptcha: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, objectStorage: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, twitter: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, github: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, discord: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, serviceWorker: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, miauth: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, default: true, }, }, }, secureMode: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, default: false, }, privateMode: { - type: 'boolean', - optional: true, nullable: false, + type: "boolean", + optional: true, + nullable: false, default: false, }, defaultReaction: { - type: 'string', - optional: 'false', nullable: false, - default: '⭐', + type: "string", + optional: "false", + nullable: false, + default: "⭐", }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - detail: { type: 'boolean', default: true }, + detail: { type: "boolean", default: true }, }, required: [], } as const; @@ -335,12 +405,12 @@ export default define(meta, paramDef, async (ps, me) => { host: IsNull(), }, order: { - category: 'ASC', - name: 'ASC', + category: "ASC", + name: "ASC", }, cache: { - id: 'meta_emojis', - milliseconds: 3600000, // 1 hour + id: "meta_emojis", + milliseconds: 3600000, // 1 hour }, }); @@ -390,13 +460,16 @@ export default define(meta, paramDef, async (ps, me) => { emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis), defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, - ads: instance.privateMode && !me ? [] : ads.map(ad => ({ - id: ad.id, - url: ad.url, - place: ad.place, - ratio: ad.ratio, - imageUrl: ad.imageUrl, - })), + ads: + instance.privateMode && !me + ? [] + : ads.map((ad) => ({ + id: ad.id, + url: ad.url, + place: ad.place, + ratio: ad.ratio, + imageUrl: ad.imageUrl, + })), enableEmail: instance.enableEmail, enableTwitterIntegration: instance.enableTwitterIntegration, @@ -408,19 +481,25 @@ export default define(meta, paramDef, async (ps, me) => { translatorAvailable: instance.deeplAuthKey != null, defaultReaction: instance.defaultReaction, - ...(ps.detail ? { - pinnedPages: instance.privateMode && !me ? [] : instance.pinnedPages, - pinnedClipId: instance.privateMode && !me ? [] : instance.pinnedClipId, - cacheRemoteFiles: instance.cacheRemoteFiles, - requireSetup: (await Users.countBy({ - host: IsNull(), - })) === 0, - } : {}), + ...(ps.detail + ? { + pinnedPages: instance.privateMode && !me ? [] : instance.pinnedPages, + pinnedClipId: + instance.privateMode && !me ? [] : instance.pinnedClipId, + cacheRemoteFiles: instance.cacheRemoteFiles, + requireSetup: + (await Users.countBy({ + host: IsNull(), + })) === 0, + } + : {}), }; if (ps.detail) { if (!instance.privateMode || me) { - const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null; + const proxyAccount = instance.proxyAccountId + ? await Users.pack(instance.proxyAccountId).catch(() => null) + : null; response.proxyAccountName = proxyAccount ? proxyAccount.username : null; } 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 73ecdaeb0..7c12b596c 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -1,39 +1,45 @@ -import define from '../../define.js'; -import { AccessTokens } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { secureRndstr } from '@/misc/secure-rndstr.js'; +import define from "../../define.js"; +import { AccessTokens } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { secureRndstr } from "@/misc/secure-rndstr.js"; export const meta = { - tags: ['auth'], + tags: ["auth"], requireCredential: true, secure: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { token: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - session: { type: 'string', nullable: true }, - name: { type: 'string', nullable: true }, - description: { type: 'string', nullable: true }, - iconUrl: { type: 'string', nullable: true }, - permission: { type: 'array', uniqueItems: true, items: { - type: 'string', - } }, + session: { type: "string", nullable: true }, + name: { type: "string", nullable: true }, + description: { type: "string", nullable: true }, + iconUrl: { type: "string", nullable: true }, + permission: { + type: "array", + uniqueItems: true, + items: { + type: "string", + }, + }, }, - required: ['session', 'permission'], + required: ["session", "permission"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 7e857e673..c43864164 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -1,50 +1,51 @@ -import define from '../../define.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 define from "../../define.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 type { Muting } from "@/models/entities/muting.js"; +import { publishUserEvent } from "@/services/stream.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'write:mutes', + kind: "write:mutes", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '6fef56f3-e765-4957-88e5-c6f65329b8a5', + message: "No such user.", + code: "NO_SUCH_USER", + id: "6fef56f3-e765-4957-88e5-c6f65329b8a5", }, muteeIsYourself: { - message: 'Mutee is yourself.', - code: 'MUTEE_IS_YOURSELF', - id: 'a4619cb2-5f23-484b-9301-94c903074e10', + message: "Mutee is yourself.", + code: "MUTEE_IS_YOURSELF", + id: "a4619cb2-5f23-484b-9301-94c903074e10", }, alreadyMuting: { - message: 'You are already muting that user.', - code: 'ALREADY_MUTING', - id: '7e7359cb-160c-4956-b08f-4d1c653cd007', + message: "You are already muting that user.", + code: "ALREADY_MUTING", + id: "7e7359cb-160c-4956-b08f-4d1c653cd007", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, expiresAt: { - type: 'integer', + type: "integer", nullable: true, - description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.', + description: + "A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.", }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -57,8 +58,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const mutee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -85,7 +87,7 @@ export default define(meta, paramDef, async (ps, user) => { muteeId: mutee.id, } as Muting); - publishUserEvent(user.id, 'mute', mutee); + publishUserEvent(user.id, "mute", mutee); NoteWatchings.delete({ userId: muter.id, diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 0b173dbe2..00c4853c1 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -1,43 +1,43 @@ -import define from '../../define.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 define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getUser } from "../../common/getters.js"; +import { Mutings } from "@/models/index.js"; +import { publishUserEvent } from "@/services/stream.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'write:mutes', + kind: "write:mutes", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef', + message: "No such user.", + code: "NO_SUCH_USER", + id: "b851d00b-8ab1-4a56-8b1b-e24187cb48ef", }, muteeIsYourself: { - message: 'Mutee is yourself.', - code: 'MUTEE_IS_YOURSELF', - id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9', + message: "Mutee is yourself.", + code: "MUTEE_IS_YOURSELF", + id: "f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9", }, notMuting: { - message: 'You are not muting that user.', - code: 'NOT_MUTING', - id: '5467d020-daa9-4553-81e1-135c0c35a96d', + message: "You are not muting that user.", + code: "NOT_MUTING", + id: "5467d020-daa9-4553-81e1-135c0c35a96d", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -50,8 +50,9 @@ export default define(meta, paramDef, async (ps, user) => { } // Get mutee - const mutee = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + const mutee = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -70,5 +71,5 @@ export default define(meta, paramDef, async (ps, user) => { id: exist.id, }); - publishUserEvent(user.id, 'unmute', mutee); + publishUserEvent(user.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 31283cf4c..e5413c2ae 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -1,43 +1,46 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Mutings } from '@/models/index.js'; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; +import { Mutings } from "@/models/index.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - kind: 'read:mutes', + kind: "read:mutes", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Muting', + type: "object", + optional: false, + nullable: false, + ref: "Muting", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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 query = makePaginationQuery( + Mutings.createQueryBuilder("muting"), + ps.sinceId, + ps.untilId, + ).andWhere("muting.muterId = :meId", { meId: me.id }); - const mutings = await query - .take(ps.limit) - .getMany(); + const mutings = await query.take(ps.limit).getMany(); return await Mutings.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 85b75c15d..356b3cde0 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -1,27 +1,29 @@ -import define from '../../define.js'; -import { Apps } from '@/models/index.js'; +import define from "../../define.js"; +import { Apps } from "@/models/index.js"; export const meta = { - tags: ['account', 'app'], + tags: ["account", "app"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'App', + type: "object", + optional: false, + nullable: false, + ref: "App", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, }, required: [], } as const; @@ -38,7 +40,11 @@ export default define(meta, paramDef, async (ps, user) => { skip: ps.offset, }); - return await Promise.all(apps.map(app => Apps.pack(app, user, { - detail: true, - }))); + return await Promise.all( + apps.map((app) => + Apps.pack(app, user, { + detail: true, + }), + ), + ); }); diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index fc2bc3741..4df5f8985 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,72 +1,84 @@ -import { Notes } from '@/models/index.js'; -import define from '../define.js'; -import { makePaginationQuery } from '../common/make-pagination-query.js'; +import { Notes } from "@/models/index.js"; +import define from "../define.js"; +import { makePaginationQuery } from "../common/make-pagination-query.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - local: { type: 'boolean', default: false }, - reply: { type: 'boolean' }, - renote: { type: 'boolean' }, - withFiles: { type: 'boolean' }, - poll: { type: 'boolean' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + local: { type: "boolean", default: false }, + reply: { type: "boolean" }, + renote: { type: "boolean" }, + withFiles: { type: "boolean" }, + poll: { type: "boolean" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, required: [], } 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'); + 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"); if (ps.local) { - query.andWhere('note.userHost IS NULL'); + query.andWhere("note.userHost IS NULL"); } if (ps.reply !== undefined) { - query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); + 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'); + 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 = \'{}\''); + query.andWhere( + ps.withFiles ? "note.fileIds != '{}'" : "note.fileIds = '{}'", + ); } if (ps.poll !== undefined) { - query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); + query.andWhere(ps.poll ? "note.hasPoll = TRUE" : "note.hasPoll = FALSE"); } // TODO diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 72282b3de..513b37fc3 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,46 +1,55 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, }; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } 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.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))', { noteId: ps.noteId, depth: ps.depth, limit: ps.limit }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('user.avatar', 'avatar') - .leftJoinAndSelect('user.banner', 'banner'); + const query = makePaginationQuery( + Notes.createQueryBuilder("note"), + ps.sinceId, + ps.untilId, + ) + .andWhere( + "note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))", + { noteId: ps.noteId, depth: ps.depth, limit: ps.limit }, + ) + .innerJoinAndSelect("note.user", "user") + .leftJoinAndSelect("user.avatar", "avatar") + .leftJoinAndSelect("user.banner", "banner"); generateVisibilityQuery(query, user); if (user) { diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 514386d73..cdc3a388a 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,46 +1,49 @@ -import { In } from 'typeorm'; -import { ClipNotes, Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; -import { ApiError } from '../../error.js'; +import { In } from "typeorm"; +import { ClipNotes, Clips } from "@/models/index.js"; +import define from "../../define.js"; +import { getNote } from "../../common/getters.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['clips', 'notes'], + tags: ["clips", "notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "47db1a1c-b0af-458d-8fb4-986e4efafe1e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const note = await getNote(ps.noteId, me).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, me).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); @@ -49,9 +52,9 @@ export default define(meta, paramDef, async (ps, me) => { }); const clips = await Clips.findBy({ - id: In(clipNotes.map(x => x.clipId)), + id: In(clipNotes.map((x) => x.clipId)), isPublic: true, }); - return await Promise.all(clips.map(x => Clips.pack(x))); + return await Promise.all(clips.map((x) => Clips.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 fa9b58848..7729db7db 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,48 +1,51 @@ -import { Note } from '@/models/entities/note.js'; -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import type { Note } from "@/models/entities/note.js"; +import { Notes } from "@/models/index.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getNote } from "../../common/getters.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "e1035875-9551-45ec-afa8-1ded1fcb53c8", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + noteId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); @@ -51,8 +54,8 @@ export default define(meta, paramDef, async (ps, user) => { async function get(id: any) { i++; - const p = await getNote(id, user).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') return null; + const p = await getNote(id, user).catch((e) => { + if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") return null; throw e; }); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 1dc8b42b2..d7649d4be 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,19 +1,25 @@ -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 { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { noteVisibilities } from '../../../../types.js'; -import { ApiError } from '../../error.js'; -import define from '../../define.js'; -import { HOUR } from '@/const.js'; -import { getNote } from '../../common/getters.js'; +import { In } from "typeorm"; +import create from "@/services/note/create.js"; +import type { User } from "@/models/entities/user.js"; +import { + Users, + DriveFiles, + Notes, + Channels, + Blockings, +} from "@/models/index.js"; +import type { DriveFile } from "@/models/entities/drive-file.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 { noteVisibilities } from "../../../../types.js"; +import { ApiError } from "../../error.js"; +import define from "../../define.js"; +import { HOUR } from "@/const.js"; +import { getNote } from "../../common/getters.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, @@ -22,154 +28,167 @@ export const meta = { max: 300, }, - kind: 'write:notes', + kind: "write:notes", res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { createdNote: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, }, errors: { noSuchRenoteTarget: { - message: 'No such renote target.', - code: 'NO_SUCH_RENOTE_TARGET', - id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4', + message: "No such renote target.", + code: "NO_SUCH_RENOTE_TARGET", + id: "b5c90186-4ab0-49c8-9bba-a1f76c282ba4", }, cannotReRenote: { - message: 'You can not Renote a pure Renote.', - code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE', - id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a', + message: "You can not Renote a pure Renote.", + code: "CANNOT_RENOTE_TO_A_PURE_RENOTE", + id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a", }, noSuchReplyTarget: { - message: 'No such reply target.', - code: 'NO_SUCH_REPLY_TARGET', - id: '749ee0f6-d3da-459a-bf02-282e2da4292c', + message: "No such reply target.", + code: "NO_SUCH_REPLY_TARGET", + id: "749ee0f6-d3da-459a-bf02-282e2da4292c", }, cannotReplyToPureRenote: { - message: 'You can not reply to a pure Renote.', - code: 'CANNOT_REPLY_TO_A_PURE_RENOTE', - id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', + message: "You can not reply to a pure Renote.", + code: "CANNOT_REPLY_TO_A_PURE_RENOTE", + id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15", }, cannotCreateAlreadyExpiredPoll: { - message: 'Poll is already expired.', - code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', - id: '04da457d-b083-4055-9082-955525eda5a5', + message: "Poll is already expired.", + code: "CANNOT_CREATE_ALREADY_EXPIRED_POLL", + id: "04da457d-b083-4055-9082-955525eda5a5", }, noSuchChannel: { - message: 'No such channel.', - code: 'NO_SUCH_CHANNEL', - id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb', + message: "No such channel.", + code: "NO_SUCH_CHANNEL", + id: "b1653923-5453-4edc-b786-7c4f39bb0bbb", }, youHaveBeenBlocked: { - message: 'You have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', + message: "You have been blocked by this user.", + code: "YOU_HAVE_BEEN_BLOCKED", + id: "b390d7e1-8a5e-46ed-b625-06271cafd3d3", }, accountLocked: { - message: 'You migrated. Your account is now locked.', - code: 'ACCOUNT_LOCKED', - id: 'd390d7e1-8a5e-46ed-b625-06271cafd3d3', + message: "You migrated. Your account is now locked.", + code: "ACCOUNT_LOCKED", + id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - visibility: { type: 'string', enum: noteVisibilities, default: 'public' }, - visibleUserIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, - cw: { type: 'string', nullable: true, maxLength: 100 }, - localOnly: { type: 'boolean', default: false }, - noExtractMentions: { type: 'boolean', default: false }, - noExtractHashtags: { type: 'boolean', default: false }, - noExtractEmojis: { type: 'boolean', default: false }, + visibility: { type: "string", enum: noteVisibilities, default: "public" }, + visibleUserIds: { + type: "array", + uniqueItems: true, + items: { + type: "string", + format: "misskey:id", + }, + }, + text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, + cw: { type: "string", nullable: true, maxLength: 100 }, + localOnly: { type: "boolean", default: false }, + noExtractMentions: { type: "boolean", default: false }, + noExtractHashtags: { type: "boolean", default: false }, + noExtractEmojis: { type: "boolean", default: false }, fileIds: { - type: 'array', + type: "array", uniqueItems: true, minItems: 1, maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, + items: { type: "string", format: "misskey:id" }, }, mediaIds: { deprecated: true, - description: 'Use `fileIds` instead. If both are specified, this property is discarded.', - type: 'array', + description: + "Use `fileIds` instead. If both are specified, this property is discarded.", + type: "array", uniqueItems: true, minItems: 1, maxItems: 16, - items: { type: 'string', format: 'misskey:id' }, + items: { type: "string", format: "misskey:id" }, }, - replyId: { type: 'string', format: 'misskey:id', nullable: true }, - renoteId: { type: 'string', format: 'misskey:id', nullable: true }, - channelId: { type: 'string', format: 'misskey:id', nullable: true }, + replyId: { type: "string", format: "misskey:id", nullable: true }, + renoteId: { type: "string", format: "misskey:id", nullable: true }, + channelId: { type: "string", format: "misskey:id", nullable: true }, poll: { - type: 'object', + type: "object", nullable: true, properties: { choices: { - type: 'array', + type: "array", uniqueItems: true, minItems: 2, maxItems: 10, - items: { type: 'string', minLength: 1, maxLength: 50 }, + items: { type: "string", minLength: 1, maxLength: 50 }, }, - multiple: { type: 'boolean', default: false }, - expiresAt: { type: 'integer', nullable: true }, - expiredAfter: { type: 'integer', nullable: true, minimum: 1 }, + multiple: { type: "boolean", default: false }, + expiresAt: { type: "integer", nullable: true }, + expiredAfter: { type: "integer", nullable: true, minimum: 1 }, }, - required: ['choices'], + required: ["choices"], }, }, anyOf: [ { // (re)note with text, files and poll are optional properties: { - text: { type: 'string', minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, + text: { + type: "string", + minLength: 1, + maxLength: MAX_NOTE_TEXT_LENGTH, + nullable: false, + }, }, - required: ['text'], + required: ["text"], }, { // (re)note with files, text and poll are optional - required: ['fileIds'], + required: ["fileIds"], }, { // (re)note with files, text and poll are optional - required: ['mediaIds'], + required: ["mediaIds"], }, { // (re)note with poll, text and files are optional properties: { - poll: { type: 'object', nullable: false }, + poll: { type: "object", nullable: false }, }, - required: ['poll'], + required: ["poll"], }, { // pure renote - required: ['renoteId'], + required: ["renoteId"], }, ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - if(user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); + if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); let visibleUsers: User[] = []; if (ps.visibleUserIds) { visibleUsers = await Users.findBy({ @@ -178,10 +197,11 @@ export default define(meta, paramDef, async (ps, user) => { } let files: DriveFile[] = []; - const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + 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)', { + files = await DriveFiles.createQueryBuilder("file") + .where("file.userId = :userId AND file.id IN (:...fileIds)", { userId: user.id, fileIds, }) @@ -193,8 +213,9 @@ export default define(meta, paramDef, async (ps, user) => { let renote: Note | null = null; if (ps.renoteId != null) { // Fetch renote to note - renote = await getNote(ps.renoteId, user).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchRenoteTarget); + renote = await getNote(ps.renoteId, user).catch((e) => { + if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchRenoteTarget); throw e; }); @@ -217,8 +238,9 @@ export default define(meta, paramDef, async (ps, user) => { let reply: Note | null = null; if (ps.replyId != null) { // Fetch reply - reply = await getNote(ps.replyId, user).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchReplyTarget); + reply = await getNote(ps.replyId, user).catch((e) => { + if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchReplyTarget); throw e; }); @@ -239,11 +261,11 @@ export default define(meta, paramDef, async (ps, user) => { } if (ps.poll) { - if (typeof ps.poll.expiresAt === 'number') { + 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') { + } else if (typeof ps.poll.expiredAfter === "number") { ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; } } @@ -261,11 +283,13 @@ export default define(meta, paramDef, async (ps, user) => { 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, + poll: ps.poll + ? { + choices: ps.poll.choices, + multiple: ps.poll.multiple, + expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } + : undefined, text: ps.text || undefined, reply, renote, diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 34d23448e..058093d3d 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,16 +1,16 @@ -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 { ApiError } from '../../error.js'; -import { SECOND, HOUR } from '@/const.js'; +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 { ApiError } from "../../error.js"; +import { SECOND, HOUR } from "@/const.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, - kind: 'write:notes', + kind: "write:notes", limit: { duration: HOUR, @@ -20,35 +20,36 @@ export const meta = { errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '490be23f-8c1f-4796-819f-94cb4f9d1630', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "490be23f-8c1f-4796-819f-94cb4f9d1630", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "fe8d7103-0ea8-4ec3-814d-f8b401dc69e9", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); - if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { + if (!(user.isAdmin || user.isModerator ) && note.userId !== user.id) { throw new ApiError(meta.errors.accessDenied); } 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 b5dd88a4e..f6e79746a 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,44 +1,45 @@ -import { NoteFavorites } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; +import { NoteFavorites } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getNote } from "../../../common/getters.js"; export const meta = { - tags: ['notes', 'favorites'], + tags: ["notes", "favorites"], requireCredential: true, - kind: 'write:favorites', + kind: "write:favorites", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '6dd26674-e060-4816-909a-45ba3f4da458', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "6dd26674-e060-4816-909a-45ba3f4da458", }, alreadyFavorited: { - message: 'The note has already been marked as a favorite.', - code: 'ALREADY_FAVORITED', - id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6', + message: "The note has already been marked as a favorite.", + code: "ALREADY_FAVORITED", + id: "a402c12b-34dd-41d2-97d8-4d2ffd96a1a6", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); 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 3f4d39254..1cb42c0bf 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,43 +1,44 @@ -import { NoteFavorites } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; +import { NoteFavorites } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getNote } from "../../../common/getters.js"; export const meta = { - tags: ['notes', 'favorites'], + tags: ["notes", "favorites"], requireCredential: true, - kind: 'write:favorites', + kind: "write:favorites", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '80848a2c-398f-4343-baa9-df1d57696c56', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "80848a2c-398f-4343-baa9-df1d57696c56", }, notFavorited: { - message: 'You have not marked that note a favorite.', - code: 'NOT_FAVORITED', - id: 'b625fc69-635e-45e9-86f4-dbefbef35af5', + message: "You have not marked that note a favorite.", + code: "NOT_FAVORITED", + id: "b625fc69-635e-45e9-86f4-dbefbef35af5", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 0e4a454d7..ea74a7466 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,30 +1,32 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, }, required: [], } as const; @@ -34,33 +36,32 @@ export default define(meta, paramDef, async (ps, user) => { const max = 30; const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - const query = Notes.createQueryBuilder('note') - .addSelect('note.score') - .where('note.userHost IS NULL') - .andWhere('note.score > 0') - .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) - .andWhere('note.visibility = \'public\'') - .innerJoinAndSelect('note.user', 'user') - .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 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"); if (user) generateMutedUserQuery(query, user); if (user) generateBlockedUserQuery(query, user); - let notes = await query - .orderBy('note.score', 'DESC') - .take(max) - .getMany(); + let notes = await query.orderBy("note.score", "DESC").take(max).getMany(); - notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + notes.sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); notes = notes.slice(ps.offset, ps.offset + ps.limit); 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 6a468f198..55fe29850 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,50 +1,52 @@ -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 { 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'; +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 { 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'], + tags: ["notes"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { gtlDisabled: { - message: 'Global timeline has been disabled.', - code: 'GTL_DISABLED', - id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b', + message: "Global timeline has been disabled.", + code: "GTL_DISABLED", + id: "0332fc13-6ab2-4427-ae80-a9fadffd1a6b", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, required: [], } as const; @@ -53,27 +55,32 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); if (m.disableGlobalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { + if (user == null || (!(user.isAdmin || user.isModerator))) { throw new ApiError(meta.errors.gtlDisabled); } } //#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'); + 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"); generateRepliesQuery(query, user); if (user) { @@ -83,7 +90,7 @@ export default define(meta, paramDef, async (ps, user) => { } if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } //#endregion 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 2dc98c4c9..f2a4de352 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,56 +1,58 @@ -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 { 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'; +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 { 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'], + tags: ["notes"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { stlDisabled: { - message: 'Hybrid timeline has been disabled.', - code: 'STL_DISABLED', - id: '620763f4-f621-4533-ab33-0577a1a3c342', + message: "Hybrid timeline has been disabled.", + code: "STL_DISABLED", + id: "620763f4-f621-4533-ab33-0577a1a3c342", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, - includeMyRenotes: { type: 'boolean', default: true }, - includeRenotedMyNotes: { type: 'boolean', default: true }, - includeLocalRenotes: { type: 'boolean', default: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, + includeMyRenotes: { type: "boolean", default: true }, + includeRenotedMyNotes: { type: "boolean", default: true }, + includeLocalRenotes: { type: "boolean", default: true }, withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, }, required: [], @@ -59,32 +61,41 @@ export const paramDef = { // 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)) { + if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { throw new ApiError(meta.errors.stlDisabled); } //#region Construct query - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: user.id }); + const 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') + 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); @@ -95,37 +106,49 @@ export default define(meta, paramDef, async (ps, 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)'); - })); + 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)'); - })); + 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)'); - })); + 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 != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } //#endregion 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 3a5c458a0..93b3ea1c9 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,57 +1,62 @@ -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 { 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'; +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 { 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'], + tags: ["notes"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { ltlDisabled: { - message: 'Local timeline has been disabled.', - code: 'LTL_DISABLED', - id: '45a6eb02-7695-4393-b023-dd3be9aaaefd', + message: "Local timeline has been disabled.", + code: "LTL_DISABLED", + id: "45a6eb02-7695-4393-b023-dd3be9aaaefd", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, - fileType: { type: 'array', items: { - type: 'string', - } }, - excludeNsfw: { type: 'boolean', default: false }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + fileType: { + type: "array", + items: { + type: "string", + }, + }, + excludeNsfw: { type: "boolean", default: false }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, required: [], } as const; @@ -60,26 +65,31 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); if (m.disableLocalTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { + if (user == null || (!(user.isAdmin || user.isModerator))) { throw new ApiError(meta.errors.ltlDisabled); } } //#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'); + 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"); generateChannelQuery(query, user); generateRepliesQuery(query, user); @@ -89,21 +99,27 @@ export default define(meta, paramDef, async (ps, user) => { if (user) generateBlockedUserQuery(query, user); if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } 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 }); - } - })); + 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)'); + 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 diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 9b4154452..d329faf89 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,63 +1,72 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - following: { type: 'boolean', default: false }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - visibility: { type: 'string' }, + following: { type: "boolean", default: false }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + visibility: { type: "string" }, }, required: [], } 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 }); + const followingQuery = Followings.createQueryBuilder("following") + .select("following.followeeId") + .where("following.followerId = :followerId", { followerId: user.id }); - const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where(`'{"${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'); + 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"); generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); @@ -65,11 +74,16 @@ export default define(meta, paramDef, async (ps, user) => { generateBlockedUserQuery(query, user); if (ps.visibility) { - query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); + query.andWhere("note.visibility = :visibility", { + visibility: ps.visibility, + }); } if (ps.following) { - query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }); + query.andWhere( + `((note.userId IN (${followingQuery.getQuery()})) OR (note.userId = :meId))`, + { meId: user.id }, + ); query.setParameters(followingQuery.getParameters()); } 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 5a04d68f3..ff452f867 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,67 +1,70 @@ -import { Brackets, In } from 'typeorm'; -import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js'; -import define from '../../../define.js'; +import { Brackets, In } from "typeorm"; +import { Polls, Mutings, Notes, PollVotes } from "@/models/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, }, required: [], } 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() }); - })); + 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 }); + const votedQuery = PollVotes.createQueryBuilder("vote") + .select("vote.noteId") + .where("vote.userId = :meId", { meId: user.id }); - query - .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); + 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 }); + const mutingQuery = Mutings.createQueryBuilder("muting") + .select("muting.muteeId") + .where("muting.muterId = :muterId", { muterId: user.id }); - query - .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); + query.andWhere(`poll.userId NOT IN (${mutingQuery.getQuery()})`); query.setParameters(mutingQuery.getParameters()); //#endregion const polls = await query - .orderBy('poll.noteId', 'DESC') + .orderBy("poll.noteId", "DESC") .take(ps.limit) .skip(ps.offset) .getMany(); @@ -70,10 +73,10 @@ export default define(meta, paramDef, async (ps, user) => { const notes = await Notes.find({ where: { - id: In(polls.map(poll => poll.noteId)), + id: In(polls.map((poll) => poll.noteId)), }, order: { - createdAt: 'DESC', + createdAt: "DESC", }, }); 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 6dd5ddf9e..fe3dfe465 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,70 +1,77 @@ -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 { ApiError } from '../../../error.js'; -import define from '../../../define.js'; +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 type { IRemoteUser } from "@/models/entities/user.js"; +import { genId } from "@/misc/gen-id.js"; +import { getNote } from "../../../common/getters.js"; +import { ApiError } from "../../../error.js"; +import define from "../../../define.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, - kind: 'write:votes', + kind: "write:votes", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'ecafbd2e-c283-4d6d-aecb-1a0a33b75396', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "ecafbd2e-c283-4d6d-aecb-1a0a33b75396", }, noPoll: { - message: 'The note does not attach a poll.', - code: 'NO_POLL', - id: '5f979967-52d9-4314-a911-1c673727f92f', + message: "The note does not attach a poll.", + code: "NO_POLL", + id: "5f979967-52d9-4314-a911-1c673727f92f", }, invalidChoice: { - message: 'Choice ID is invalid.', - code: 'INVALID_CHOICE', - id: 'e0cc9a04-f2e8-41e4-a5f1-4127293260cc', + message: "Choice ID is invalid.", + code: "INVALID_CHOICE", + id: "e0cc9a04-f2e8-41e4-a5f1-4127293260cc", }, alreadyVoted: { - message: 'You have already voted.', - code: 'ALREADY_VOTED', - id: '0963fc77-efac-419b-9424-b391608dc6d8', + message: "You have already voted.", + code: "ALREADY_VOTED", + id: "0963fc77-efac-419b-9424-b391608dc6d8", }, alreadyExpired: { - message: 'The poll is already expired.', - code: 'ALREADY_EXPIRED', - id: '1022a357-b085-4054-9083-8f8de358337e', + message: "The poll is already expired.", + code: "ALREADY_EXPIRED", + id: "1022a357-b085-4054-9083-8f8de358337e", }, youHaveBeenBlocked: { - message: 'You cannot vote this poll because you have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: '85a5377e-b1e9-4617-b0b9-5bea73331e49', + message: + "You cannot vote this poll because you have been blocked by this user.", + code: "YOU_HAVE_BEEN_BLOCKED", + id: "85a5377e-b1e9-4617-b0b9-5bea73331e49", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - choice: { type: 'integer' }, + noteId: { type: "string", format: "misskey:id" }, + choice: { type: "integer" }, }, - required: ['noteId', 'choice'], + required: ["noteId", "choice"], } as const; // eslint-disable-next-line import/no-default-export @@ -72,8 +79,9 @@ export default define(meta, paramDef, async (ps, user) => { const createdAt = new Date(); // Get votee - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); @@ -110,7 +118,7 @@ export default define(meta, paramDef, async (ps, user) => { if (exist.length) { if (poll.multiple) { - if (exist.some(x => x.choice === ps.choice)) { + if (exist.some((x) => x.choice === ps.choice)) { throw new ApiError(meta.errors.alreadyVoted); } } else { @@ -125,19 +133,21 @@ export default define(meta, paramDef, async (ps, user) => { noteId: note.id, userId: user.id, choice: ps.choice, - }).then(x => PollVotes.findOneByOrFail(x.identifiers[0])); + }).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}'`); + await Polls.query( + `UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`, + ); - publishNoteStream(note.id, 'pollVoted', { + publishNoteStream(note.id, "pollVoted", { choice: ps.choice, userId: user.id, }); // Notify - createNotification(note.userId, 'pollVote', { + createNotification(note.userId, "pollVote", { notifierId: user.id, noteId: note.id, choice: ps.choice, @@ -147,9 +157,9 @@ export default define(meta, paramDef, async (ps, user) => { NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), - }).then(watchers => { + }).then((watchers) => { for (const watcher of watchers) { - createNotification(watcher.userId, 'pollVote', { + createNotification(watcher.userId, "pollVote", { notifierId: user.id, noteId: note.id, choice: ps.choice, @@ -159,9 +169,15 @@ export default define(meta, paramDef, async (ps, user) => { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser; + const pollOwner = (await Users.findOneByOrFail({ + id: note.userId, + })) as IRemoteUser; - deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); + deliver( + user, + renderActivity(await renderVote(user, vote, note, poll, pollOwner)), + pollOwner.inbox, + ); } // リモートフォロワーにUpdate配信 diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 00a89b3f2..8500e1061 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,12 +1,13 @@ -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 { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import type { FindOptionsWhere } from "typeorm"; +import { DeepPartial } from "typeorm"; +import { NoteReactions } from "@/models/index.js"; +import type { NoteReaction } from "@/models/entities/note-reaction.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getNote } from "../../common/getters.js"; export const meta = { - tags: ['notes', 'reactions'], + tags: ["notes", "reactions"], requireCredential: false, requireCredentialPrivateMode: true, @@ -15,42 +16,45 @@ export const meta = { cacheSec: 60, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'NoteReaction', + type: "object", + optional: false, + nullable: false, + ref: "NoteReaction", }, }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '263fff3d-d0e1-4af4-bea7-8408059b451a', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "263fff3d-d0e1-4af4-bea7-8408059b451a", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - type: { type: 'string', nullable: true }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, + type: { type: "string", nullable: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // check note visibility - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); @@ -61,8 +65,10 @@ export default define(meta, paramDef, async (ps, user) => { if (ps.type) { // ローカルリアクションはホスト名が . とされているが // DB 上ではそうではないので、必要に応じて変換 - const suffix = '@.:'; - const type = ps.type.endsWith(suffix) ? ps.type.slice(0, ps.type.length - suffix.length) + ':' : ps.type; + const suffix = "@.:"; + const type = ps.type.endsWith(suffix) + ? `${ps.type.slice(0, ps.type.length - suffix.length)}:` + : ps.type; query.reaction = type; } @@ -73,7 +79,7 @@ export default define(meta, paramDef, async (ps, user) => { order: { id: -1, }, - relations: ['user', 'user.avatar', 'user.banner', 'note'], + relations: ["user", "user.avatar", "user.banner", "note"], }); return await NoteReactions.packMany(reactions, user); 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 4c767b489..19b11c235 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -1,60 +1,64 @@ -import createReaction from '@/services/note/reaction/create.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; +import createReaction from "@/services/note/reaction/create.js"; +import define from "../../../define.js"; +import { getNote } from "../../../common/getters.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['reactions', 'notes'], + tags: ["reactions", "notes"], requireCredential: true, - kind: 'write:reactions', + kind: "write:reactions", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '033d0620-5bfe-4027-965d-980b0c85a3ea', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "033d0620-5bfe-4027-965d-980b0c85a3ea", }, alreadyReacted: { - message: 'You are already reacting to that note.', - code: 'ALREADY_REACTED', - id: '71efcf98-86d6-4e2b-b2ad-9d032369366b', + message: "You are already reacting to that note.", + code: "ALREADY_REACTED", + id: "71efcf98-86d6-4e2b-b2ad-9d032369366b", }, youHaveBeenBlocked: { - message: 'You cannot react this note because you have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: '20ef5475-9f38-4e4c-bd33-de6d979498ec', + message: + "You cannot react this note because you have been blocked by this user.", + code: "YOU_HAVE_BEEN_BLOCKED", + id: "20ef5475-9f38-4e4c-bd33-de6d979498ec", }, accountLocked: { - message: 'You migrated. Your account is now locked.', - code: 'ACCOUNT_LOCKED', - id: 'd390d7e1-8a5e-46ed-b625-06271cafd3d3', + message: "You migrated. Your account is now locked.", + code: "ACCOUNT_LOCKED", + id: "d390d7e1-8a5e-46ed-b625-06271cafd3d3", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - reaction: { type: 'string' }, + noteId: { type: "string", format: "misskey:id" }, + reaction: { type: "string" }, }, - required: ['noteId', 'reaction'], + required: ["noteId", "reaction"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - if(user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); - 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); + 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; 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 c25d88d1b..b7df7bd54 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,15 +1,15 @@ -import deleteReaction from '@/services/note/reaction/delete.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; -import { SECOND, HOUR } from '@/const.js'; +import deleteReaction from "@/services/note/reaction/delete.js"; +import define from "../../../define.js"; +import { getNote } from "../../../common/getters.js"; +import { ApiError } from "../../../error.js"; +import { SECOND, HOUR } from "@/const.js"; export const meta = { - tags: ['reactions', 'notes'], + tags: ["reactions", "notes"], requireCredential: true, - kind: 'write:reactions', + kind: "write:reactions", limit: { duration: HOUR, @@ -19,35 +19,37 @@ export const meta = { errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '764d9fce-f9f2-4a0e-92b1-6ceac9a7ad37', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "764d9fce-f9f2-4a0e-92b1-6ceac9a7ad37", }, notReacted: { - message: 'You are not reacting to that note.', - code: 'NOT_REACTED', - id: '92f4426d-4196-4125-aa5b-02943e2ec8fc', + message: "You are not reacting to that note.", + code: "NOT_REACTED", + id: "92f4426d-4196-4125-aa5b-02943e2ec8fc", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); - await deleteReaction(user, note).catch(e => { - if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); + await deleteReaction(user, note).catch((e) => { + if (e.id === "60527ec9-b4cb-4a88-a6bd-32d3ad26817d") + throw new ApiError(meta.errors.notReacted); throw e; }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts index ae2d58274..c8dccf68d 100644 --- a/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/recommended-timeline.ts @@ -1,57 +1,62 @@ -import { Brackets } from 'typeorm'; -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 { 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'; +import { Brackets } from "typeorm"; +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 { 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'], + tags: ["notes"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { rtlDisabled: { - message: 'Recommended timeline has been disabled.', - code: 'RTL_DISABLED', - id: '45a6eb02-7695-4393-b023-dd3be9aaaefe', + message: "Recommended timeline has been disabled.", + code: "RTL_DISABLED", + id: "45a6eb02-7695-4393-b023-dd3be9aaaefe", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, - fileType: { type: 'array', items: { - type: 'string', - } }, - excludeNsfw: { type: 'boolean', default: false }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + fileType: { + type: "array", + items: { + type: "string", + }, + }, + excludeNsfw: { type: "boolean", default: false }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, required: [], } as const; @@ -60,27 +65,34 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const m = await fetchMeta(); if (m.disableRecommendedTimeline) { - if (user == null || (!user.isAdmin && !user.isModerator)) { + if (user == null || (!(user.isAdmin || user.isModerator))) { throw new ApiError(meta.errors.rtlDisabled); } } //#region Construct query - const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere(`(note.userHost = ANY ('{"${m.recommendedInstances.join('","')}"}'))`) - .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'); + const query = makePaginationQuery( + Notes.createQueryBuilder("note"), + ps.sinceId, + ps.untilId, + ps.sinceDate, + ps.untilDate, + ) + .andWhere( + `(note.userHost = ANY ('{"${m.recommendedInstances.join('","')}"}'))`, + ) + .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"); generateChannelQuery(query, user); generateRepliesQuery(query, user); @@ -90,21 +102,27 @@ export default define(meta, paramDef, async (ps, user) => { if (user) generateBlockedUserQuery(query, user); if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } 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 }); - } - })); + 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)'); + 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 diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 2f662f355..add524d69 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,68 +1,75 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.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 { Notes } from "@/models/index.js"; +import define from "../../define.js"; +import { getNote } from "../../common/getters.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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '12908022-2e21-46cd-ba6a-3edaf6093f46', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "12908022-2e21-46cd-ba6a-3edaf6093f46", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); - 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'); + 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"); generateVisibilityQuery(query, user); if (user) generateMutedUserQuery(query, user); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index b05ef5914..86a6d0113 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,53 +1,59 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + noteId: { type: "string", format: "misskey:id" }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, - required: ['noteId'], + required: ["noteId"], } 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'); + 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); 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 99670ae2f..926d082a4 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,59 +1,62 @@ -import { Brackets } from 'typeorm'; -import { Notes } 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 { Brackets } from "typeorm"; +import { Notes } 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"; export const meta = { - tags: ['notes', 'hashtags'], + tags: ["notes", "hashtags"], requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - reply: { type: 'boolean', nullable: true, default: null }, - renote: { type: 'boolean', nullable: true, default: null }, + reply: { type: "boolean", nullable: true, default: null }, + renote: { type: "boolean", nullable: true, default: null }, withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, - poll: { type: 'boolean', nullable: true, default: null }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + poll: { type: "boolean", nullable: true, default: null }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, anyOf: [ { properties: { - tag: { type: 'string', minLength: 1 }, + tag: { type: "string", minLength: 1 }, }, - required: ['tag'], + required: ["tag"], }, { properties: { query: { - type: 'array', - description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.', + type: "array", + description: + "The outer arrays are chained with OR, the inner arrays are chained with AND.", items: { - type: 'array', + type: "array", items: { - type: 'string', + type: "string", minLength: 1, }, minItems: 1, @@ -61,25 +64,29 @@ export const paramDef = { minItems: 1, }, }, - required: ['query'], + required: ["query"], }, ], } 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'); + 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"); generateVisibilityQuery(query, me); if (me) generateMutedUserQuery(query, me); @@ -87,50 +94,54 @@ export default define(meta, paramDef, async (ps, me) => { try { if (ps.tag) { - if (!safeForSql(ps.tag)) throw new Error('Injection'); + if (!safeForSql(ps.tag)) throw new Error("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 new Error('Injection'); - qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); - } - })); - } - })); + 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 new Error("Injection"); + qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + } + }), + ); + } + }), + ); } } catch (e) { - if (e.message === 'Injection') return []; + if (e.message === "Injection") return []; throw e; } if (ps.reply != null) { if (ps.reply) { - query.andWhere('note.replyId IS NOT NULL'); + query.andWhere("note.replyId IS NOT NULL"); } else { - query.andWhere('note.replyId IS NULL'); + query.andWhere("note.replyId IS NULL"); } } if (ps.renote != null) { if (ps.renote) { - query.andWhere('note.renoteId IS NOT NULL'); + query.andWhere("note.renoteId IS NOT NULL"); } else { - query.andWhere('note.renoteId IS NULL'); + query.andWhere("note.renoteId IS NULL"); } } if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } if (ps.poll != null) { if (ps.poll) { - query.andWhere('note.hasPoll = TRUE'); + query.andWhere("note.hasPoll = TRUE"); } else { - query.andWhere('note.hasPoll = FALSE'); + query.andWhere("note.hasPoll = FALSE"); } } diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index cf3de47a3..ae39b33ec 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,76 +1,93 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, - errors: { - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - query: { type: 'string' }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + query: { type: "string" }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, host: { - type: 'string', + type: "string", nullable: true, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", + }, + userId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, + }, + channelId: { + type: "string", + format: "misskey:id", + nullable: true, + default: null, }, - userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, - channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, }, - required: ['query'], + 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); + const query = makePaginationQuery( + Notes.createQueryBuilder("note"), + ps.sinceId, + ps.untilId, + ); if (ps.userId) { - query.andWhere('note.userId = :userId', { userId: 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.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'); + .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); @@ -80,47 +97,67 @@ export default define(meta, paramDef, async (ps, me) => { 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', + const userQuery = + ps.userId != null + ? [ + { + term: { + userId: ps.userId, + }, }, - }, - }, - }] : ps.host !== undefined ? [{ - term: { - userHost: ps.host, - }, - }] : [] - : []; + ] + : []; + + const hostQuery = + ps.userId == null + ? ps.host === null + ? [ + { + bool: { + must_not: { + exists: { + field: "userHost", + }, + }, + }, + }, + ] + : ps.host !== undefined + ? [ + { + term: { + userHost: ps.host, + }, + }, + ] + : [] + : []; const result = await es.search({ - index: config.elasticsearch.index || 'misskey_note', + 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', + must: [ + { + simple_query_string: { + fields: ["text"], + query: ps.query.toLowerCase(), + default_operator: "and", + }, }, - }, ...hostQuery, ...userQuery], + ...hostQuery, + ...userQuery, + ], }, }, - sort: [{ - _doc: 'desc', - }], + sort: [ + { + _doc: "desc", + }, + ], }, }); diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 83a39a855..b2785d2a8 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,49 +1,52 @@ -import { Notes } from '@/models/index.js'; -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; -import { ApiError } from '../../error.js'; +import { Notes } from "@/models/index.js"; +import define from "../../define.js"; +import { getNote } from "../../common/getters.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "24fcbfc6-2e37-42b6-8388-c29b3861a08d", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); return await Notes.pack(note, user, { // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774) detail: true, - }).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + }).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 67579b2a6..b8c6bd694 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,38 +1,47 @@ -import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; -import { getNote } from '../../common/getters.js'; -import define from '../../define.js'; +import { + NoteFavorites, + Notes, + NoteThreadMutings, + NoteWatchings, +} from "@/models/index.js"; +import { getNote } from "../../common/getters.js"; +import define from "../../define.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { isFavorited: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isWatching: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isMutedThread: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + required: ["noteId"], } as const; // eslint-disable-next-line import/no-default-export 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 4154b5dc5..2ae0b6bf4 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,47 +1,51 @@ -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 { ApiError } from '../../../error.js'; +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 { ApiError } from "../../../error.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "5ff67ada-ed3b-2e71-8e87-a1a421e177d2", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); const mutedNotes = await Notes.find({ - where: [{ - id: note.threadId || note.id, - }, { - threadId: note.threadId || note.id, - }], + where: [ + { + id: note.threadId || note.id, + }, + { + threadId: note.threadId || note.id, + }, + ], }); await readNote(user.id, mutedNotes); 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 cbc0e5ce5..84a362cfb 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,36 +1,37 @@ -import { NoteThreadMutings } from '@/models/index.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; +import { NoteThreadMutings } from "@/models/index.js"; +import define from "../../../define.js"; +import { getNote } from "../../../common/getters.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "bddd57ac-ceb3-b29d-4334-86ea5fae481a", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 22f492517..6023cd573 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,46 +1,48 @@ -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 { 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"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, - includeMyRenotes: { type: 'boolean', default: true }, - includeRenotedMyNotes: { type: 'boolean', default: true }, - includeLocalRenotes: { type: 'boolean', default: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, + includeMyRenotes: { type: "boolean", default: true }, + includeRenotedMyNotes: { type: "boolean", default: true }, + includeLocalRenotes: { type: "boolean", default: true }, withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, }, required: [], @@ -48,35 +50,44 @@ export const paramDef = { // 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; + 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 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') + 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); @@ -87,37 +98,49 @@ export default define(meta, paramDef, async (ps, 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)'); - })); + 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)'); - })); + 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)'); - })); + 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 != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } //#endregion diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index a01dcfa48..d8556a43c 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,46 +1,48 @@ -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 { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; -import define from '../../define.js'; +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 { ApiError } from "../../error.js"; +import { getNote } from "../../common/getters.js"; +import define from "../../define.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, }, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "bea9b03f-36e0-49c5-a4db-627a029f8971", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, - targetLang: { type: 'string' }, + noteId: { type: "string", format: "misskey:id" }, + targetLang: { type: "string" }, }, - required: ['noteId', 'targetLang'], + required: ["noteId", "targetLang"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); @@ -55,21 +57,23 @@ export default define(meta, paramDef, async (ps, user) => { } let targetLang = ps.targetLang; - if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; + 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); + 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 endpoint = instance.deeplIsPro + ? "https://api.deepl.com/v2/translate" + : "https://api-free.deepl.com/v2/translate"; const res = await fetch(endpoint, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': config.userAgent, - Accept: 'application/json, */*', + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": config.userAgent, + Accept: "application/json, */*", }, body: params, // TODO diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 1089a9e37..7f3b48753 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,16 +1,16 @@ -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 { ApiError } from '../../error.js'; -import { SECOND, HOUR } from '@/const.js'; +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 { ApiError } from "../../error.js"; +import { SECOND, HOUR } from "@/const.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, - kind: 'write:notes', + kind: "write:notes", limit: { duration: HOUR, @@ -20,25 +20,26 @@ export const meta = { errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'efd4a259-2442-496b-8dd7-b255aa1a160f', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "efd4a259-2442-496b-8dd7-b255aa1a160f", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); 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 e603a8f62..037ae2320 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,54 +1,56 @@ -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 { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; +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 { 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'], + tags: ["notes", "lists"], requireCredential: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, - includeMyRenotes: { type: 'boolean', default: true }, - includeRenotedMyNotes: { type: 'boolean', default: true }, - includeLocalRenotes: { type: 'boolean', default: true }, + listId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, + includeMyRenotes: { type: "boolean", default: true }, + includeRenotedMyNotes: { type: "boolean", default: true }, + includeLocalRenotes: { type: "boolean", default: true }, withFiles: { - type: 'boolean', + type: "boolean", default: false, - description: 'Only show notes that have attached files.', + description: "Only show notes that have attached files.", }, }, - required: ['listId'], + required: ["listId"], } as const; // eslint-disable-next-line import/no-default-export @@ -63,55 +65,77 @@ export default define(meta, paramDef, async (ps, user) => { } //#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 }); + 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)'); - })); + 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)'); - })); + 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)'); - })); + 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 != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } //#endregion diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 6025799fa..993f9afec 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -1,36 +1,37 @@ -import watch from '@/services/note/watch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; +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'], + tags: ["notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "ea0e37a6-90a3-4f58-ba6b-c328ca206fc7", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 7021c7970..9fe51a5e5 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -1,36 +1,37 @@ -import unwatch from '@/services/note/unwatch.js'; -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; +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'], + tags: ["notes"], requireCredential: true, - kind: 'write:account', + kind: "write:account", errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: '09b3695c-f72c-4731-a428-7cff825fc82e', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "09b3695c-f72c-4731-a428-7cff825fc82e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index 80d513d8d..e53c41067 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -1,30 +1,29 @@ -import { createNotification } from '@/services/create-notification.js'; -import define from '../../define.js'; +import { createNotification } from "@/services/create-notification.js"; +import define from "../../define.js"; export const meta = { - tags: ['notifications'], + tags: ["notifications"], requireCredential: true, - kind: 'write:notifications', + kind: "write:notifications", - errors: { - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - body: { type: 'string' }, - header: { type: 'string', nullable: true }, - icon: { type: 'string', nullable: true }, + body: { type: "string" }, + header: { type: "string", nullable: true }, + icon: { type: "string", nullable: true }, }, - required: ['body'], + required: ["body"], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user, token) => { - createNotification(user.id, 'app', { + createNotification(user.id, "app", { appAccessTokenId: token ? token.id : null, customBody: ps.body, customHeader: ps.header, 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 d169afbb3..e1cc895a6 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,18 +1,18 @@ -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 { publishMainStream } from "@/services/stream.js"; +import { pushNotification } from "@/services/push-notification.js"; +import { Notifications } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['notifications', 'account'], + tags: ["notifications", "account"], requireCredential: true, - kind: 'write:notifications', + kind: "write:notifications", } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -20,14 +20,17 @@ export const paramDef = { // 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, - }); + await Notifications.update( + { + notifieeId: user.id, + isRead: false, + }, + { + isRead: true, + }, + ); // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user.id, 'readAllNotifications'); - pushNotification(user.id, 'readAllNotifications', undefined); + publishMainStream(user.id, "readAllNotifications"); + pushNotification(user.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 7bce525a5..f1bc86fd9 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -1,20 +1,20 @@ -import define from '../../define.js'; -import { readNotification } from '../../common/read-notification.js'; +import define from "../../define.js"; +import { readNotification } from "../../common/read-notification.js"; export const meta = { - tags: ['notifications', 'account'], + tags: ["notifications", "account"], requireCredential: true, - kind: 'write:notifications', + kind: "write:notifications", - description: 'Mark a notification as read.', + description: "Mark a notification as read.", errors: { noSuchNotification: { - message: 'No such notification.', - code: 'NO_SUCH_NOTIFICATION', - id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e', + message: "No such notification.", + code: "NO_SUCH_NOTIFICATION", + id: "efa929d5-05b5-47d1-beec-e6a4dbed011e", }, }, } as const; @@ -22,28 +22,29 @@ export const meta = { export const paramDef = { oneOf: [ { - type: 'object', + type: "object", properties: { - notificationId: { type: 'string', format: 'misskey:id' }, + notificationId: { type: "string", format: "misskey:id" }, }, - required: ['notificationId'], + required: ["notificationId"], }, { - type: 'object', + type: "object", properties: { notificationIds: { - type: 'array', - items: { type: 'string', format: 'misskey:id' }, + type: "array", + items: { type: "string", format: "misskey:id" }, maxItems: 100, }, }, - required: ['notificationIds'], + required: ["notificationIds"], }, ], } 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]); + if ("notificationId" in ps) + return readNotification(user.id, [ps.notificationId]); return readNotification(user.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 6dd3ede85..126c79f4f 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,7 +1,7 @@ -import { publishMainStream } from '@/services/stream.js'; -import { Users, Pages } from '@/models/index.js'; -import define from '../define.js'; -import { ApiError } from '../error.js'; +import { publishMainStream } from "@/services/stream.js"; +import { Users, Pages } from "@/models/index.js"; +import define from "../define.js"; +import { ApiError } from "../error.js"; export const meta = { requireCredential: true, @@ -9,21 +9,21 @@ export const meta = { errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: '4a13ad31-6729-46b4-b9af-e86b265c2e74', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "4a13ad31-6729-46b4-b9af-e86b265c2e74", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - pageId: { type: 'string', format: 'misskey:id' }, - event: { type: 'string' }, + pageId: { type: "string", format: "misskey:id" }, + event: { type: "string" }, var: {}, }, - required: ['pageId', 'event'], + required: ["pageId", "event"], } as const; // eslint-disable-next-line import/no-default-export @@ -33,13 +33,17 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchPage); } - publishMainStream(page.userId, 'pageEvent', { + 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, - }), + user: await Users.pack( + user.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 063faf6ec..1099e4d88 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,16 +1,16 @@ -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 { ApiError } from '../../error.js'; -import { HOUR } from '@/const.js'; +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 { ApiError } from "../../error.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: true, - kind: 'write:pages', + kind: "write:pages", limit: { duration: HOUR, @@ -18,45 +18,62 @@ export const meta = { }, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, errors: { noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'b7b97489-0f66-4b12-a5ff-b21bd63f6e1c', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "b7b97489-0f66-4b12-a5ff-b21bd63f6e1c", }, nameAlreadyExists: { - message: 'Specified name already exists.', - code: 'NAME_ALREADY_EXISTS', - id: '4650348e-301c-499a-83c9-6aa988c66bc1', + message: "Specified name already exists.", + code: "NAME_ALREADY_EXISTS", + id: "4650348e-301c-499a-83c9-6aa988c66bc1", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, - summary: { type: 'string', nullable: true }, - content: { type: 'array', items: { - type: 'object', additionalProperties: true, - } }, - variables: { type: 'array', items: { - type: 'object', additionalProperties: true, - } }, - script: { type: 'string' }, - eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true }, - font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' }, - alignCenter: { type: 'boolean', default: false }, - isPublic: { type: 'boolean', default: true }, - hideTitleWhenPinned: { type: 'boolean', default: false }, + title: { type: "string" }, + name: { type: "string", minLength: 1 }, + summary: { type: "string", nullable: true }, + content: { + type: "array", + items: { + type: "object", + additionalProperties: true, + }, + }, + variables: { + type: "array", + items: { + type: "object", + additionalProperties: true, + }, + }, + script: { type: "string" }, + eyeCatchingImageId: { + type: "string", + format: "misskey:id", + nullable: true, + }, + font: { + type: "string", + enum: ["serif", "sans-serif"], + default: "sans-serif", + }, + alignCenter: { type: "boolean", default: false }, + isPublic: { type: "boolean", default: true }, + hideTitleWhenPinned: { type: "boolean", default: false }, }, - required: ['title', 'name', 'content', 'variables', 'script'], + required: ["title", "name", "content", "variables", "script"], } as const; // eslint-disable-next-line import/no-default-export @@ -76,30 +93,32 @@ export default define(meta, paramDef, async (ps, user) => { await Pages.findBy({ userId: user.id, name: ps.name, - }).then(result => { + }).then((result) => { if (result.length > 0) { throw new ApiError(meta.errors.nameAlreadyExists); } }); - 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, - isPublic: ps.isPublic, - })).then(x => Pages.findOneByOrFail(x.identifiers[0])); + 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, + isPublic: ps.isPublic, + }), + ).then((x) => Pages.findOneByOrFail(x.identifiers[0])); return await Pages.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 a7708e658..e68da8e35 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,35 +1,35 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { Pages } from "@/models/index.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: true, - kind: 'write:pages', + kind: "write:pages", errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: 'eb0c6e1d-d519-4764-9486-52a7e1c6392a', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "eb0c6e1d-d519-4764-9486-52a7e1c6392a", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '8b741b3e-2c22-44b3-a15f-29949aa1601e', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "8b741b3e-2c22-44b3-a15f-29949aa1601e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - pageId: { type: 'string', format: 'misskey:id' }, + pageId: { type: "string", format: "misskey:id" }, }, - required: ['pageId'], + required: ["pageId"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 75580778b..565ba5b2e 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,35 +1,37 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; +import { Pages } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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'); + const query = Pages.createQueryBuilder("page") + .where("page.visibility = 'public'") + .andWhere("page.likedCount > 0") + .orderBy("page.likedCount", "DESC"); const pages = await query.take(10).getMany(); diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index b4aab40d3..eb61b8b26 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,36 +1,36 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { Pages, PageLikes } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: true, - kind: 'write:page-likes', + kind: "write:page-likes", errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "cc98a8a2-0dc3-4123-b198-62c71df18ed3", }, alreadyLiked: { - message: 'The page has already been liked.', - code: 'ALREADY_LIKED', - id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', + message: "The page has already been liked.", + code: "ALREADY_LIKED", + id: "cc98a8a2-0dc3-4123-b198-62c71df18ed3", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - pageId: { type: 'string', format: 'misskey:id' }, + pageId: { type: "string", format: "misskey:id" }, }, - required: ['pageId'], + required: ["pageId"], } as const; // eslint-disable-next-line import/no-default-export @@ -58,5 +58,5 @@ export default define(meta, paramDef, async (ps, user) => { userId: user.id, }); - Pages.increment({ id: page.id }, 'likedCount', 1); + Pages.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 384975af7..9a079d865 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,45 +1,46 @@ -import { IsNull } from 'typeorm'; -import { Pages, Users } from '@/models/index.js'; -import { Page } from '@/models/entities/page.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { IsNull } from "typeorm"; +import { Pages, Users } from "@/models/index.js"; +import type { Page } from "@/models/entities/page.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: '222120c0-3ead-4528-811b-b96f233388d7', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "222120c0-3ead-4528-811b-b96f233388d7", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", anyOf: [ { properties: { - pageId: { type: 'string', format: 'misskey:id' }, + pageId: { type: "string", format: "misskey:id" }, }, - required: ['pageId'], + required: ["pageId"], }, { properties: { - name: { type: 'string' }, - username: { type: 'string' }, + name: { type: "string" }, + username: { type: "string" }, }, - required: ['name', 'username'], + required: ["name", "username"], }, ], } as const; @@ -67,7 +68,7 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchPage); } - if (!page.isPublic && (user == null || (page.userId !== user.id))) { + if (!page.isPublic && (user == null || page.userId !== user.id)) { throw new ApiError(meta.errors.noSuchPage); } diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 6b3a2bec1..7c3760221 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,35 +1,35 @@ -import { Pages, PageLikes } from '@/models/index.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { Pages, PageLikes } from "@/models/index.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: true, - kind: 'write:page-likes', + kind: "write:page-likes", errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: 'a0d41e20-1993-40bd-890e-f6e560ae648e', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "a0d41e20-1993-40bd-890e-f6e560ae648e", }, notLiked: { - message: 'You have not liked that page.', - code: 'NOT_LIKED', - id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee', + message: "You have not liked that page.", + code: "NOT_LIKED", + id: "f5e586b0-ce93-4050-b0e3-7f31af5259ee", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - pageId: { type: 'string', format: 'misskey:id' }, + pageId: { type: "string", format: "misskey:id" }, }, - required: ['pageId'], + required: ["pageId"], } as const; // eslint-disable-next-line import/no-default-export @@ -51,5 +51,5 @@ export default define(meta, paramDef, async (ps, user) => { // Delete like await PageLikes.delete(exist.id); - Pages.decrement({ id: page.id }, 'likedCount', 1); + Pages.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 585e9e73e..a746a3e2a 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,15 +1,15 @@ -import { Not } from 'typeorm'; -import { Pages, DriveFiles } from '@/models/index.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { HOUR } from '@/const.js'; +import { Not } from "typeorm"; +import { Pages, DriveFiles } from "@/models/index.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['pages'], + tags: ["pages"], requireCredential: true, - kind: 'write:pages', + kind: "write:pages", limit: { duration: HOUR, @@ -18,51 +18,63 @@ export const meta = { errors: { noSuchPage: { - message: 'No such page.', - code: 'NO_SUCH_PAGE', - id: '21149b9e-3616-4778-9592-c4ce89f5a864', + message: "No such page.", + code: "NO_SUCH_PAGE", + id: "21149b9e-3616-4778-9592-c4ce89f5a864", }, accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '3c15cd52-3b4b-4274-967d-6456fc4f792b', + message: "Access denied.", + code: "ACCESS_DENIED", + id: "3c15cd52-3b4b-4274-967d-6456fc4f792b", }, noSuchFile: { - message: 'No such file.', - code: 'NO_SUCH_FILE', - id: 'cfc23c7c-3887-490e-af30-0ed576703c82', + message: "No such file.", + code: "NO_SUCH_FILE", + id: "cfc23c7c-3887-490e-af30-0ed576703c82", }, nameAlreadyExists: { - message: 'Specified name already exists.', - code: 'NAME_ALREADY_EXISTS', - id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab', + message: "Specified name already exists.", + code: "NAME_ALREADY_EXISTS", + id: "2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - pageId: { type: 'string', format: 'misskey:id' }, - title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, - summary: { type: 'string', nullable: true }, - content: { type: 'array', items: { - type: 'object', additionalProperties: true, - } }, - variables: { type: 'array', items: { - type: 'object', additionalProperties: true, - } }, - script: { type: 'string' }, - eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true }, - font: { type: 'string', enum: ['serif', 'sans-serif'] }, - alignCenter: { type: 'boolean' }, - hideTitleWhenPinned: { type: 'boolean' }, - isPublic: { type: 'boolean' }, + pageId: { type: "string", format: "misskey:id" }, + title: { type: "string" }, + name: { type: "string", minLength: 1 }, + summary: { type: "string", nullable: true }, + content: { + type: "array", + items: { + type: "object", + additionalProperties: true, + }, + }, + variables: { + type: "array", + items: { + type: "object", + additionalProperties: true, + }, + }, + script: { type: "string" }, + eyeCatchingImageId: { + type: "string", + format: "misskey:id", + nullable: true, + }, + font: { type: "string", enum: ["serif", "sans-serif"] }, + alignCenter: { type: "boolean" }, + hideTitleWhenPinned: { type: "boolean" }, + isPublic: { type: "boolean" }, }, - required: ['pageId', 'title', 'name', 'content', 'variables', 'script'], + required: ["pageId", "title", "name", "content", "variables", "script"], } as const; // eslint-disable-next-line import/no-default-export @@ -91,7 +103,7 @@ export default define(meta, paramDef, async (ps, user) => { id: Not(ps.pageId), userId: user.id, name: ps.name, - }).then(result => { + }).then((result) => { if (result.length > 0) { throw new ApiError(meta.errors.nameAlreadyExists); } @@ -106,12 +118,17 @@ export default define(meta, paramDef, async (ps, user) => { variables: ps.variables, script: ps.script, isPublic: ps.isPublic, - alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, - hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, + 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 + eyeCatchingImageId: + ps.eyeCatchingImageId === null + ? null + : ps.eyeCatchingImageId === undefined ? page.eyeCatchingImageId : eyeCatchingImage!.id, }); diff --git a/packages/backend/src/server/api/endpoints/patrons.ts b/packages/backend/src/server/api/endpoints/patrons.ts index bc1fcc5c9..7a7ee25ac 100644 --- a/packages/backend/src/server/api/endpoints/patrons.ts +++ b/packages/backend/src/server/api/endpoints/patrons.ts @@ -1,15 +1,15 @@ -import define from '../define.js'; +import define from "../define.js"; export const meta = { - tags: ['meta'], - description: 'Get list of Calckey patrons from Codeberg', + tags: ["meta"], + description: "Get list of Calckey patrons from Codeberg", requireCredential: false, requireCredentialPrivateMode: false, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -17,10 +17,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { let patrons; - await fetch('https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json') + await fetch( + "https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json", + ) .then((response) => response.json()) .then((data) => { - patrons = data['patrons']; + patrons = data["patrons"]; }); return patrons; diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index 2891a0860..282c429f5 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,24 +1,26 @@ -import define from '../define.js'; +import define from "../define.js"; export const meta = { requireCredential: false, - tags: ['meta'], + tags: ["meta"], res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { pong: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 251482648..7a9d3b404 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,29 +1,31 @@ -import { IsNull } from 'typeorm'; -import { Users } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import * as Acct from '@/misc/acct.js'; -import type { User } from '@/models/entities/user.js'; -import define from '../define.js'; +import { IsNull } from "typeorm"; +import { Users } from "@/models/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import * as Acct from "@/misc/acct.js"; +import type { User } from "@/models/entities/user.js"; +import define from "../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: false, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -32,10 +34,20 @@ export const paramDef = { export default define(meta, paramDef, async (ps, me) => { const meta = await fetchMeta(); - const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => Users.findOneBy({ - usernameLower: acct.username.toLowerCase(), - host: acct.host ?? IsNull(), - }))); + const users = await Promise.all( + meta.pinnedUsers + .map((acct) => Acct.parse(acct)) + .map((acct) => + Users.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + }), + ), + ); - return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); + return await Users.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 7c37fcbf7..e80e447a1 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,35 +1,36 @@ -import { PromoReads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; -import { getNote } from '../../common/getters.js'; +import { PromoReads } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import define from "../../define.js"; +import { ApiError } from "../../error.js"; +import { getNote } from "../../common/getters.js"; export const meta = { - tags: ['notes'], + tags: ["notes"], requireCredential: true, errors: { noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932', + message: "No such note.", + code: "NO_SUCH_NOTE", + id: "d785b897-fcd3-4fe9-8fc3-b85c26e6c932", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - noteId: { type: 'string', format: 'misskey:id' }, + noteId: { type: "string", format: "misskey:id" }, }, - required: ['noteId'], + 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, user).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + const note = await getNote(ps.noteId, user).catch((err) => { + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") + throw new ApiError(meta.errors.noSuchNote); throw err; }); diff --git a/packages/backend/src/server/api/endpoints/recommended-instances.ts b/packages/backend/src/server/api/endpoints/recommended-instances.ts index 844177b1d..ea3483036 100644 --- a/packages/backend/src/server/api/endpoints/recommended-instances.ts +++ b/packages/backend/src/server/api/endpoints/recommended-instances.ts @@ -1,25 +1,27 @@ // import { IsNull } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import define from '../define.js'; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import define from "../define.js"; export const meta = { - tags: ['meta'], + tags: ["meta"], requireCredential: false, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'string', - optional: false, nullable: false, + type: "string", + optional: false, + nullable: false, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -27,6 +29,6 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { const meta = await fetchMeta(); - const instances = await Promise.all(meta.recommendedInstances.map(x => x)); + const instances = await Promise.all(meta.recommendedInstances.map((x) => x)); return instances; }); diff --git a/packages/backend/src/server/api/endpoints/release.ts b/packages/backend/src/server/api/endpoints/release.ts index fcdcfbe5b..175336914 100644 --- a/packages/backend/src/server/api/endpoints/release.ts +++ b/packages/backend/src/server/api/endpoints/release.ts @@ -1,15 +1,15 @@ -import define from '../define.js'; +import define from "../define.js"; export const meta = { - tags: ['meta'], - description: 'Get release notes from Codeberg', + tags: ["meta"], + description: "Get release notes from Codeberg", requireCredential: false, requireCredentialPrivateMode: false, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -17,8 +17,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async () => { let release; - - await fetch('https://codeberg.org/calckey/calckey/raw/branch/develop/release.json') + + await fetch( + "https://codeberg.org/calckey/calckey/raw/branch/develop/release.json", + ) .then((response) => response.json()) .then((data) => { release = data; 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 ddf193903..379ac2b5b 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,38 +1,36 @@ -import rndstr from 'rndstr'; -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 { ApiError } from '../error.js'; -import define from '../define.js'; -import { HOUR } from '@/const.js'; +import rndstr from "rndstr"; +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 { ApiError } from "../error.js"; +import define from "../define.js"; +import { HOUR } from "@/const.js"; export const meta = { - tags: ['reset password'], + tags: ["reset password"], requireCredential: false, - description: 'Request a users password to be reset.', + description: "Request a users password to be reset.", limit: { duration: HOUR, max: 3, }, - errors: { - - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - username: { type: 'string' }, - email: { type: 'string' }, + username: { type: "string" }, + email: { type: "string" }, }, - required: ['username', 'email'], + required: ["username", "email"], } as const; // eslint-disable-next-line import/no-default-export @@ -59,7 +57,7 @@ export default define(meta, paramDef, async (ps) => { return; } - const token = rndstr('a-z0-9', 64); + const token = rndstr("a-z0-9", 64); await PasswordResetRequests.insert({ id: genId(), @@ -70,7 +68,10 @@ export default define(meta, paramDef, async (ps) => { const link = `${config.url}/reset-password/${token}`; - sendEmail(ps.email, 'Password reset requested', + sendEmail( + ps.email, + "Password reset requested", `To reset password, please click this link:
${link}`, - `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 cf5710f82..583a54e72 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -1,30 +1,30 @@ -import { resetDb } from '@/db/postgre.js'; -import define from '../define.js'; -import { ApiError } from '../error.js'; +import { resetDb } from "@/db/postgre.js"; +import define from "../define.js"; +import { ApiError } from "../error.js"; export const meta = { - tags: ['non-productive'], + tags: ["non-productive"], requireCredential: false, - description: 'Only available when running with NODE_ENV=testing. Reset the database and flush Redis.', + description: + "Only available when running with NODE_ENV=testing. Reset the database and flush Redis.", - errors: { - - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } 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 new Error('NODE_ENV is not a test'); + if (process.env.NODE_ENV !== "test") + throw new Error("NODE_ENV is not a test"); await resetDb(); - await new Promise(resolve => setTimeout(resolve, 1000)); + 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 797169c2c..c78c11ee7 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,28 +1,26 @@ -import bcrypt from 'bcryptjs'; -import { publishMainStream } from '@/services/stream.js'; -import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; -import define from '../define.js'; -import { ApiError } from '../error.js'; +import bcrypt from "bcryptjs"; +import { publishMainStream } from "@/services/stream.js"; +import { Users, UserProfiles, PasswordResetRequests } from "@/models/index.js"; +import define from "../define.js"; +import { ApiError } from "../error.js"; export const meta = { - tags: ['reset password'], + tags: ["reset password"], requireCredential: false, - description: 'Complete the password reset that was previously requested.', + description: "Complete the password reset that was previously requested.", - errors: { - - }, + errors: {}, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - token: { type: 'string' }, - password: { type: 'string' }, + token: { type: "string" }, + password: { type: "string" }, }, - required: ['token', 'password'], + required: ["token", "password"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index fdfbc8a6f..32db34e70 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -1,16 +1,16 @@ -import * as os from 'node:os'; -import si from 'systeminformation'; -import define from '../define.js'; +import * as os from "node:os"; +import si from "systeminformation"; +import define from "../define.js"; export const meta = { requireCredential: false, requireCredentialPrivateMode: true, - tags: ['meta'], + tags: ["meta"], } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index 0f2fb1f41..a7a08dae4 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,52 +1,60 @@ -import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; -import define from '../define.js'; -import { } from '@/services/chart/index.js'; -import { IsNull } from 'typeorm'; +import { Instances, NoteReactions, Notes, Users } from "@/models/index.js"; +import define from "../define.js"; +import {} from "@/services/chart/index.js"; +import { IsNull } from "typeorm"; export const meta = { requireCredential: false, requireCredentialPrivateMode: true, - tags: ['meta'], + tags: ["meta"], res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { notesCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, originalNotesCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, usersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, originalUsersCount: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, instances: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, driveUsageLocal: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, driveUsageRemote: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 437f8874f..3ad713490 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,40 +1,43 @@ -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 { fetchMeta } from "@/misc/fetch-meta.js"; +import { genId } from "@/misc/gen-id.js"; +import { SwSubscriptions } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - description: 'Register to receive push notifications.', + description: "Register to receive push notifications.", res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { state: { - type: 'string', - optional: true, nullable: false, - enum: ['already-subscribed', 'subscribed'], + type: "string", + optional: true, + nullable: false, + enum: ["already-subscribed", "subscribed"], }, key: { - type: 'string', - optional: false, nullable: true, + type: "string", + optional: false, + nullable: true, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - endpoint: { type: 'string' }, - auth: { type: 'string' }, - publickey: { type: 'string' }, + endpoint: { type: "string" }, + auth: { type: "string" }, + publickey: { type: "string" }, }, - required: ['endpoint', 'auth', 'publickey'], + required: ["endpoint", "auth", "publickey"], } as const; // eslint-disable-next-line import/no-default-export @@ -51,7 +54,7 @@ export default define(meta, paramDef, async (ps, user) => { if (exist != null) { return { - state: 'already-subscribed' as const, + state: "already-subscribed" as const, key: instance.swPublicKey, }; } @@ -66,7 +69,7 @@ export default define(meta, paramDef, async (ps, user) => { }); return { - state: 'subscribed' as const, + 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 c19e06b87..9335e43f4 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,20 +1,20 @@ -import { SwSubscriptions } from '@/models/index.js'; -import define from '../../define.js'; +import { SwSubscriptions } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['account'], + tags: ["account"], requireCredential: true, - description: 'Unregister from receiving push notifications.', + description: "Unregister from receiving push notifications.", } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - endpoint: { type: 'string' }, + endpoint: { type: "string" }, }, - required: ['endpoint'], + required: ["endpoint"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 9949237a7..9c173296a 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,23 +1,23 @@ -import define from '../define.js'; +import define from "../define.js"; export const meta = { - tags: ['non-productive'], + tags: ["non-productive"], - description: 'Endpoint for testing input validation.', + description: "Endpoint for testing input validation.", requireCredential: false, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - required: { type: 'boolean' }, - string: { type: 'string' }, - default: { type: 'string', default: 'hello' }, - nullableDefault: { type: 'string', nullable: true, default: 'hello' }, - id: { type: 'string', format: 'misskey:id' }, + required: { type: "boolean" }, + string: { type: "string" }, + default: { type: "string", default: "hello" }, + nullableDefault: { type: "string", nullable: true, default: "hello" }, + id: { type: "string", format: "misskey:id" }, }, - required: ['required'], + required: ["required"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 3e41aeaed..cee4dcb70 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,30 +1,32 @@ -import { IsNull } from 'typeorm'; -import { Users, UsedUsernames } from '@/models/index.js'; -import define from '../../define.js'; +import { IsNull } from "typeorm"; +import { Users, UsedUsernames } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { available: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { username: Users.localUsernameSchema, }, - required: ['username'], + required: ["username"], } as const; // eslint-disable-next-line import/no-default-export @@ -35,7 +37,9 @@ export default define(meta, paramDef, async (ps) => { usernameLower: ps.username.toLowerCase(), }); - const exist2 = await UsedUsernames.countBy({ username: ps.username.toLowerCase() }); + const exist2 = await UsedUsernames.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 7ee9bb8c0..f252f013f 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,38 +1,58 @@ -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 { 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"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: true, requireCredentialPrivateMode: true, res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", 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'] }, - state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: 'all' }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, + sort: { + type: "string", + enum: [ + "+follower", + "-follower", + "+createdAt", + "-createdAt", + "+updatedAt", + "-updatedAt", + ], + }, + state: { + type: "string", + enum: ["all", "admin", "moderator", "adminOrModerator", "alive"], + default: "all", + }, + origin: { + type: "string", + enum: ["combined", "local", "remote"], + default: "local", + }, hostname: { - type: 'string', + type: "string", nullable: true, default: null, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", }, }, required: [], @@ -40,33 +60,67 @@ export const paramDef = { // 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'); + const query = Users.createQueryBuilder("user"); + query.where("user.isExplorable = TRUE"); 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; + 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; } switch (ps.origin) { - case 'local': query.andWhere('user.host IS NULL'); break; - case 'remote': query.andWhere('user.host IS NOT NULL'); break; + case "local": + query.andWhere("user.host IS NULL"); + break; + case "remote": + query.andWhere("user.host IS NOT NULL"); + break; } if (ps.hostname) { - query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); + 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; + 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); diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index becfad52d..4e1a10660 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,44 +1,48 @@ -import { Clips } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Clips } from "@/models/index.js"; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['users', 'clips'], + tags: ["users", "clips"], requireCredentialPrivateMode: true, - description: 'Show all clips this user owns.', + description: "Show all clips this user owns.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Clip', + type: "object", + optional: false, + nullable: false, + ref: "Clip", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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'); + const query = makePaginationQuery( + Clips.createQueryBuilder("clip"), + ps.sinceId, + ps.untilId, + ) + .andWhere("clip.userId = :userId", { userId: ps.userId }) + .andWhere("clip.isPublic = true"); - const clips = await query - .take(ps.limit) - .getMany(); + const clips = await query.take(ps.limit).getMany(); return await Clips.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 4971d21b0..dd3de04c1 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,76 +1,83 @@ -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 { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +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 { ApiError } from "../../error.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Show everyone that follows this user.', + description: "Show everyone that follows this user.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Following', + type: "object", + optional: false, + nullable: false, + ref: "Following", }, }, errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '27fa5435-88ab-43de-9360-387de88727cd', + message: "No such user.", + code: "NO_SUCH_USER", + id: "27fa5435-88ab-43de-9360-387de88727cd", }, forbidden: { - message: 'Forbidden.', - code: 'FORBIDDEN', - id: '3c6a84db-d619-26af-ca14-06232a21df8a', + message: "Forbidden.", + code: "FORBIDDEN", + id: "3c6a84db-d619-26af-ca14-06232a21df8a", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, anyOf: [ { properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], }, { properties: { - username: { type: 'string' }, + username: { type: "string" }, host: { - type: 'string', + type: "string", nullable: true, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", }, }, - required: ['username', 'host'], + required: ["username", "host"], }, ], } 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() }); + const user = await Users.findOneBy( + ps.userId != null + ? { id: ps.userId } + : { + usernameLower: ps.username!.toLowerCase(), + host: toPunyNullable(ps.host) ?? IsNull(), + }, + ); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -78,11 +85,11 @@ export default define(meta, paramDef, async (ps, me) => { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { + if (profile.ffVisibility === "private") { + if (me == null || me.id !== user.id) { throw new ApiError(meta.errors.forbidden); } - } else if (profile.ffVisibility === 'followers') { + } else if (profile.ffVisibility === "followers") { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { @@ -96,13 +103,15 @@ export default define(meta, paramDef, async (ps, me) => { } } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followeeId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.follower', 'follower'); + const query = makePaginationQuery( + Followings.createQueryBuilder("following"), + ps.sinceId, + ps.untilId, + ) + .andWhere("following.followeeId = :userId", { userId: user.id }) + .innerJoinAndSelect("following.follower", "follower"); - const followings = await query - .take(ps.limit) - .getMany(); + const followings = await query.take(ps.limit).getMany(); return await Followings.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 043841aa4..9b71a1589 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,76 +1,83 @@ -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 { ApiError } from '../../error.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +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 { ApiError } from "../../error.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Show everyone that this user is following.', + description: "Show everyone that this user is following.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Following', + type: "object", + optional: false, + nullable: false, + ref: "Following", }, }, errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '63e4aba4-4156-4e53-be25-c9559e42d71b', + message: "No such user.", + code: "NO_SUCH_USER", + id: "63e4aba4-4156-4e53-be25-c9559e42d71b", }, forbidden: { - message: 'Forbidden.', - code: 'FORBIDDEN', - id: 'f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba', + message: "Forbidden.", + code: "FORBIDDEN", + id: "f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, anyOf: [ { properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], }, { properties: { - username: { type: 'string' }, + username: { type: "string" }, host: { - type: 'string', + type: "string", nullable: true, - description: 'The local host is represented with `null`.', + description: "The local host is represented with `null`.", }, }, - required: ['username', 'host'], + required: ["username", "host"], }, ], } 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() }); + const user = await Users.findOneBy( + ps.userId != null + ? { id: ps.userId } + : { + usernameLower: ps.username!.toLowerCase(), + host: toPunyNullable(ps.host) ?? IsNull(), + }, + ); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -78,11 +85,11 @@ export default define(meta, paramDef, async (ps, me) => { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - if (profile.ffVisibility === 'private') { - if (me == null || (me.id !== user.id)) { + if (profile.ffVisibility === "private") { + if (me == null || me.id !== user.id) { throw new ApiError(meta.errors.forbidden); } - } else if (profile.ffVisibility === 'followers') { + } else if (profile.ffVisibility === "followers") { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { @@ -96,13 +103,15 @@ export default define(meta, paramDef, async (ps, me) => { } } - const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere('following.followerId = :userId', { userId: user.id }) - .innerJoinAndSelect('following.followee', 'followee'); + const query = makePaginationQuery( + Followings.createQueryBuilder("following"), + ps.sinceId, + ps.untilId, + ) + .andWhere("following.followerId = :userId", { userId: user.id }) + .innerJoinAndSelect("following.followee", "followee"); - const followings = await query - .take(ps.limit) - .getMany(); + const followings = await query.take(ps.limit).getMany(); return await Followings.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 95ca77825..78b085896 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -1,43 +1,46 @@ -import define from '../../../define.js'; -import { GalleryPosts } from '@/models/index.js'; -import { makePaginationQuery } from '../../../common/make-pagination-query.js'; +import define from "../../../define.js"; +import { GalleryPosts } from "@/models/index.js"; +import { makePaginationQuery } from "../../../common/make-pagination-query.js"; export const meta = { - tags: ['users', 'gallery'], + tags: ["users", "gallery"], requireCredentialPrivateMode: true, - description: 'Show all gallery posts by the given user.', + description: "Show all gallery posts by the given user.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'GalleryPost', + type: "object", + optional: false, + nullable: false, + ref: "GalleryPost", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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 }); + const query = makePaginationQuery( + GalleryPosts.createQueryBuilder("post"), + ps.sinceId, + ps.untilId, + ).andWhere("post.userId = :userId", { userId: ps.userId }); - const posts = await query - .take(ps.limit) - .getMany(); + const posts = await query.take(ps.limit).getMany(); return await GalleryPosts.packMany(posts, user); }); 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 8cf3ea040..827606b8e 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,33 +1,38 @@ -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 { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; +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 { ApiError } from "../../error.js"; +import { getUser } from "../../common/getters.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Get a list of other users that the specified user frequently replies to.', + description: + "Get a list of other users that the specified user frequently replies to.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { user: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, weight: { - type: 'number', - optional: false, nullable: false, + type: "number", + optional: false, + nullable: false, }, }, }, @@ -35,27 +40,28 @@ export const meta = { errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'e6965129-7b2a-40a4-bae2-cd84cd434822', + message: "No such user.", + code: "NO_SUCH_USER", + id: "e6965129-7b2a-40a4-bae2-cd84cd434822", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + userId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, }, - required: ['userId'], + required: ["userId"], } 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -69,7 +75,7 @@ export default define(meta, paramDef, async (ps, me) => { id: -1, }, take: 1000, - select: ['replyId'], + select: ["replyId"], }); // 投稿が少なかったら中断 @@ -80,15 +86,15 @@ export default define(meta, paramDef, async (ps, me) => { // TODO ミュートを考慮 const replyTargetNotes = await Notes.find({ where: { - id: In(recentNotes.map(p => p.replyId)), + id: In(recentNotes.map((p) => p.replyId)), }, - select: ['userId'], + select: ["userId"], }); const repliedUsers: any = {}; // Extract replies from recent notes - for (const userId of replyTargetNotes.map(x => x.userId.toString())) { + for (const userId of replyTargetNotes.map((x) => x.userId.toString())) { if (repliedUsers[userId]) { repliedUsers[userId]++; } else { @@ -100,16 +106,20 @@ export default define(meta, paramDef, async (ps, me) => { const peak = maximum(Object.values(repliedUsers)); // Sort replies by frequency - const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); + const repliedUsersSorted = Object.keys(repliedUsers).sort( + (a, b) => repliedUsers[b] - repliedUsers[a], + ); // 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, - }))); + const repliesObj = await Promise.all( + topRepliedUsers.map(async (user) => ({ + user: await Users.pack(user, me, { detail: true }), + weight: repliedUsers[user] / peak, + })), + ); return repliesObj; }); diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 4a6362a3c..8abed8358 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,31 +1,32 @@ -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 { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import type { UserGroupJoining } from "@/models/entities/user-group-joining.js"; +import define from "../../../define.js"; export const meta = { - tags: ['groups'], + tags: ["groups"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Create a new group.', + description: "Create a new group.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 100 }, + name: { type: "string", minLength: 1, maxLength: 100 }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -35,7 +36,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserGroup).then(x => UserGroups.findOneByOrFail(x.identifiers[0])); + } as UserGroup).then((x) => UserGroups.findOneByOrFail(x.identifiers[0])); // Push the owner await UserGroupJoinings.insert({ 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 2ff1f9aec..4019c7a2e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,31 +1,31 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserGroups } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['groups'], + tags: ["groups"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Delete an existing group.', + description: "Delete an existing group.", errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '63dbd64c-cd77-413f-8e08-61781e210b38', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "63dbd64c-cd77-413f-8e08-61781e210b38", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, }, - required: ['groupId'], + required: ["groupId"], } as const; // eslint-disable-next-line import/no-default-export 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 220fff5f3..5d7db3e91 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,33 +1,33 @@ -import { UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; -import { ApiError } from '../../../../error.js'; -import define from '../../../../define.js'; +import { UserGroupJoinings, UserGroupInvitations } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { UserGroupJoining } from "@/models/entities/user-group-joining.js"; +import { ApiError } from "../../../../error.js"; +import define from "../../../../define.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Join a group the authenticated user has been invited to.', + description: "Join a group the authenticated user has been invited to.", errors: { noSuchInvitation: { - message: 'No such invitation.', - code: 'NO_SUCH_INVITATION', - id: '98c11eca-c890-4f42-9806-c8c8303ebb5e', + message: "No such invitation.", + code: "NO_SUCH_INVITATION", + id: "98c11eca-c890-4f42-9806-c8c8303ebb5e", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - invitationId: { type: 'string', format: 'misskey:id' }, + invitationId: { type: "string", format: "misskey:id" }, }, - required: ['invitationId'], + required: ["invitationId"], } as const; // eslint-disable-next-line import/no-default-export 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 8d1d3db73..33f4c6b54 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,31 +1,32 @@ -import { UserGroupInvitations } from '@/models/index.js'; -import define from '../../../../define.js'; -import { ApiError } from '../../../../error.js'; +import { UserGroupInvitations } from "@/models/index.js"; +import define from "../../../../define.js"; +import { ApiError } from "../../../../error.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Delete an existing group invitation for the authenticated user without joining the group.', + description: + "Delete an existing group invitation for the authenticated user without joining the group.", errors: { noSuchInvitation: { - message: 'No such invitation.', - code: 'NO_SUCH_INVITATION', - id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656', + message: "No such invitation.", + code: "NO_SUCH_INVITATION", + id: "ad7471d4-2cd9-44b4-ac68-e7136b4ce656", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - invitationId: { type: 'string', format: 'misskey:id' }, + invitationId: { type: "string", format: "misskey:id" }, }, - required: ['invitationId'], + required: ["invitationId"], } as const; // eslint-disable-next-line import/no-default-export 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 1a8d320f3..ba84f67ed 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,54 +1,58 @@ -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 { ApiError } from '../../../error.js'; -import define from '../../../define.js'; +import { + UserGroups, + UserGroupJoinings, + UserGroupInvitations, +} from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { UserGroupInvitation } from "@/models/entities/user-group-invitation.js"; +import { createNotification } from "@/services/create-notification.js"; +import { getUser } from "../../../common/getters.js"; +import { ApiError } from "../../../error.js"; +import define from "../../../define.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Invite a user to an existing group.', + description: "Invite a user to an existing group.", errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '583f8bc0-8eee-4b78-9299-1e14fc91e409', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "583f8bc0-8eee-4b78-9299-1e14fc91e409", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'da52de61-002c-475b-90e1-ba64f9cf13a8', + message: "No such user.", + code: "NO_SUCH_USER", + id: "da52de61-002c-475b-90e1-ba64f9cf13a8", }, alreadyAdded: { - message: 'That user has already been added to that group.', - code: 'ALREADY_ADDED', - id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c', + message: "That user has already been added to that group.", + code: "ALREADY_ADDED", + id: "7e35c6a0-39b2-4488-aea6-6ee20bd5da2c", }, alreadyInvited: { - message: 'That user has already been invited to that group.', - code: 'ALREADY_INVITED', - id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6', + message: "That user has already been invited to that group.", + code: "ALREADY_INVITED", + id: "ee0f58b4-b529-4d13-b761-b9a3e69f97e6", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['groupId', 'userId'], + required: ["groupId", "userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -64,8 +68,9 @@ export default define(meta, paramDef, async (ps, me) => { } // 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -92,10 +97,12 @@ export default define(meta, paramDef, async (ps, me) => { createdAt: new Date(), userId: user.id, userGroupId: userGroup.id, - } as UserGroupInvitation).then(x => UserGroupInvitations.findOneByOrFail(x.identifiers[0])); + } as UserGroupInvitation).then((x) => + UserGroupInvitations.findOneByOrFail(x.identifiers[0]), + ); // 通知を作成 - createNotification(user.id, 'groupInvited', { + 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 16c6e544e..ada810a6a 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,29 +1,31 @@ -import { Not, In } from 'typeorm'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; +import { Not, In } from "typeorm"; +import { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['groups', 'account'], + tags: ["groups", "account"], requireCredential: true, - kind: 'read:user-groups', + kind: "read:user-groups", - description: 'List the groups that the authenticated user is a member of.', + description: "List the groups that the authenticated user is a member of.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -36,10 +38,12 @@ export default define(meta, paramDef, async (ps, me) => { const joinings = await UserGroupJoinings.findBy({ userId: me.id, - ...(ownedGroups.length > 0 ? { - userGroupId: Not(In(ownedGroups.map(x => x.id))), - } : {}), + ...(ownedGroups.length > 0 + ? { + userGroupId: Not(In(ownedGroups.map((x) => x.id))), + } + : {}), }); - return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId))); + return await Promise.all(joinings.map((x) => UserGroups.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 83dc757db..e52fb639e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,37 +1,38 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.', + description: + "Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.", errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '62780270-1f67-5dc0-daca-3eb510612e31', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "62780270-1f67-5dc0-daca-3eb510612e31", }, youAreOwner: { - message: 'Your are the owner.', - code: 'YOU_ARE_OWNER', - id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69', + message: "Your are the owner.", + code: "YOU_ARE_OWNER", + id: "b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, }, - required: ['groupId'], + required: ["groupId"], } as const; // eslint-disable-next-line import/no-default-export 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 d77cf1a52..52a00d6f1 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,28 +1,30 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; +import { UserGroups } from "@/models/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['groups', 'account'], + tags: ["groups", "account"], requireCredential: true, - kind: 'read:user-groups', + kind: "read:user-groups", - description: 'List the groups that the authenticated user is the owner of.', + description: "List the groups that the authenticated user is the owner of.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -33,5 +35,5 @@ export default define(meta, paramDef, async (ps, me) => { userId: me.id, }); - return await Promise.all(userGroups.map(x => UserGroups.pack(x))); + return await Promise.all(userGroups.map((x) => UserGroups.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 ba67a1e5c..9ee02a2d3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,45 +1,46 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Removes a specified user from a group. The owner can not be removed.', + description: + "Removes a specified user from a group. The owner can not be removed.", errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '4662487c-05b1-4b78-86e5-fd46998aba74', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "4662487c-05b1-4b78-86e5-fd46998aba74", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '0b5cc374-3681-41da-861e-8bc1146f7a55', + message: "No such user.", + code: "NO_SUCH_USER", + id: "0b5cc374-3681-41da-861e-8bc1146f7a55", }, isOwner: { - message: 'The user is the owner.', - code: 'IS_OWNER', - id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af', + message: "The user is the owner.", + code: "IS_OWNER", + id: "1546eed5-4414-4dea-81c1-b0aec4f6d2af", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['groupId', 'userId'], + required: ["groupId", "userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -55,8 +56,9 @@ export default define(meta, paramDef, async (ps, me) => { } // 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -65,5 +67,8 @@ export default define(meta, paramDef, async (ps, me) => { } // Pull the user - await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id }); + await UserGroupJoinings.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 21e3d9da2..98e04d636 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,37 +1,38 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['groups', 'account'], + tags: ["groups", "account"], requireCredential: true, - kind: 'read:user-groups', + kind: "read:user-groups", - description: 'Show the properties of a group.', + description: "Show the properties of a group.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "ea04751e-9b7e-487b-a509-330fb6bd6b9b", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, }, - required: ['groupId'], + required: ["groupId"], } as const; // eslint-disable-next-line import/no-default-export 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 6456e70dd..b2a1c8855 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,51 +1,53 @@ -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import { UserGroups, UserGroupJoinings } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['groups', 'users'], + tags: ["groups", "users"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Transfer ownership of a group from the authenticated user to another user.', + description: + "Transfer ownership of a group from the authenticated user to another user.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "8e31d36b-2f88-4ccd-a438-e2d78a9162db", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9', + message: "No such user.", + code: "NO_SUCH_USER", + id: "711f7ebb-bbb9-4dfa-b540-b27809fed5e9", }, noSuchGroupMember: { - message: 'No such group member.', - code: 'NO_SUCH_GROUP_MEMBER', - id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4', + message: "No such group member.", + code: "NO_SUCH_GROUP_MEMBER", + id: "d31bebee-196d-42c2-9a3e-9474d4be6cc4", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, + groupId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['groupId', 'userId'], + required: ["groupId", "userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -61,8 +63,9 @@ export default define(meta, paramDef, async (ps, me) => { } // 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); 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 0a96165fc..b45bd3ddc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,38 +1,39 @@ -import { UserGroups } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserGroups } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['groups'], + tags: ["groups"], requireCredential: true, - kind: 'write:user-groups', + kind: "write:user-groups", - description: 'Update the properties of a group.', + description: "Update the properties of a group.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserGroup', + type: "object", + optional: false, + nullable: false, + ref: "UserGroup", }, errors: { noSuchGroup: { - message: 'No such group.', - code: 'NO_SUCH_GROUP', - id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6', + message: "No such group.", + code: "NO_SUCH_GROUP", + id: "9081cda3-7a9e-4fac-a6ce-908d70f282f6", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - groupId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 100 }, + groupId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 100 }, }, - required: ['groupId', 'name'], + required: ["groupId", "name"], } as const; // eslint-disable-next-line import/no-default-export 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 783e63f5d..a2ec91748 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,30 +1,31 @@ -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 { UserLists } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { UserList } from "@/models/entities/user-list.js"; +import define from "../../../define.js"; export const meta = { - tags: ['lists'], + tags: ["lists"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Create a new list of users.', + description: "Create a new list of users.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserList', + type: "object", + optional: false, + nullable: false, + ref: "UserList", }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - name: { type: 'string', minLength: 1, maxLength: 100 }, + name: { type: "string", minLength: 1, maxLength: 100 }, }, - required: ['name'], + required: ["name"], } as const; // eslint-disable-next-line import/no-default-export @@ -34,7 +35,7 @@ export default define(meta, paramDef, async (ps, user) => { createdAt: new Date(), userId: user.id, name: ps.name, - } as UserList).then(x => UserLists.findOneByOrFail(x.identifiers[0])); + } as UserList).then((x) => UserLists.findOneByOrFail(x.identifiers[0])); return await UserLists.pack(userList); }); diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete-all.ts b/packages/backend/src/server/api/endpoints/users/lists/delete-all.ts index 9bea5c164..c7bc18c2b 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete-all.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete-all.ts @@ -1,32 +1,32 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserLists } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['lists'], + tags: ["lists"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Delete all lists of users.', + description: "Delete all lists of users.", errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '78436795-db79-42f5-b1e2-55ea2cf19166', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "78436795-db79-42f5-b1e2-55ea2cf19166", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - while (await UserLists.findOneBy({ userId: user.id }) != null) { + while ((await UserLists.findOneBy({ userId: user.id })) != null) { const userList = await UserLists.findOneBy({ userId: user.id }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); 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 5a7613c98..bf0595be9 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,31 +1,31 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserLists } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['lists'], + tags: ["lists"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Delete an existing list of users.', + description: "Delete an existing list of users.", errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '78436795-db79-42f5-b1e2-55ea2cf19166', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "78436795-db79-42f5-b1e2-55ea2cf19166", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, + listId: { type: "string", format: "misskey:id" }, }, - required: ['listId'], + required: ["listId"], } as const; // eslint-disable-next-line import/no-default-export 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 889052fa3..3539b80c8 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,28 +1,30 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; +import { UserLists } from "@/models/index.js"; +import define from "../../../define.js"; export const meta = { - tags: ['lists', 'account'], + tags: ["lists", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", - description: 'Show all lists that the authenticated user has created.', + description: "Show all lists that the authenticated user has created.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserList', + type: "object", + optional: false, + nullable: false, + ref: "UserList", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: {}, required: [], } as const; @@ -33,5 +35,5 @@ export default define(meta, paramDef, async (ps, me) => { userId: me.id, }); - return await Promise.all(userLists.map(x => UserLists.pack(x))); + return await Promise.all(userLists.map((x) => UserLists.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 d3d1d6555..ee71f9719 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,40 +1,40 @@ -import { publishUserListStream } from '@/services/stream.js'; -import { UserLists, UserListJoinings, Users } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import { publishUserListStream } from "@/services/stream.js"; +import { UserLists, UserListJoinings, Users } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['lists', 'users'], + tags: ["lists", "users"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Remove a user from a list.', + description: "Remove a user from a list.", errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "7f44670e-ab16-43b8-b4c1-ccd2ee89cc02", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '588e7f72-c744-4a61-b180-d354e912bda2', + message: "No such user.", + code: "NO_SUCH_USER", + id: "588e7f72-c744-4a61-b180-d354e912bda2", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, + listId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['listId', 'userId'], + required: ["listId", "userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -50,13 +50,14 @@ export default define(meta, paramDef, async (ps, me) => { } // 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); // Pull the user await UserListJoinings.delete({ userListId: userList.id, userId: user.id }); - publishUserListStream(userList.id, 'userRemoved', await Users.pack(user)); + publishUserListStream(userList.id, "userRemoved", await Users.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 12b7b8634..fb4edc886 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,52 +1,53 @@ -import { pushUserToUserList } from '@/services/user-list/push.js'; -import { UserLists, UserListJoinings, Blockings } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; +import { pushUserToUserList } from "@/services/user-list/push.js"; +import { UserLists, UserListJoinings, Blockings } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; +import { getUser } from "../../../common/getters.js"; export const meta = { - tags: ['lists', 'users'], + tags: ["lists", "users"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Add a user to an existing list.', + description: "Add a user to an existing list.", errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '2214501d-ac96-4049-b717-91e42272a711', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "2214501d-ac96-4049-b717-91e42272a711", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'a89abd3d-f0bc-4cce-beb1-2f446f4f1e6a', + message: "No such user.", + code: "NO_SUCH_USER", + id: "a89abd3d-f0bc-4cce-beb1-2f446f4f1e6a", }, alreadyAdded: { - message: 'That user has already been added to that list.', - code: 'ALREADY_ADDED', - id: '1de7c884-1595-49e9-857e-61f12f4d4fc5', + message: "That user has already been added to that list.", + code: "ALREADY_ADDED", + id: "1de7c884-1595-49e9-857e-61f12f4d4fc5", }, youHaveBeenBlocked: { - message: 'You cannot push this user because you have been blocked by this user.', - code: 'YOU_HAVE_BEEN_BLOCKED', - id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b', + message: + "You cannot push this user because you have been blocked by this user.", + code: "YOU_HAVE_BEEN_BLOCKED", + id: "990232c5-3f9d-4d83-9f3f-ef27b6332a4b", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, + listId: { type: "string", format: "misskey:id" }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['listId', 'userId'], + required: ["listId", "userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -62,8 +63,9 @@ export default define(meta, paramDef, async (ps, me) => { } // 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); 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 fd0612f73..a5b472153 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,37 +1,38 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserLists } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['lists', 'account'], + tags: ["lists", "account"], requireCredential: true, - kind: 'read:account', + kind: "read:account", - description: 'Show the properties of a list.', + description: "Show the properties of a list.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserList', + type: "object", + optional: false, + nullable: false, + ref: "UserList", }, errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "7bc05c21-1d7a-41ae-88f1-66820f4dc686", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, + listId: { type: "string", format: "misskey:id" }, }, - required: ['listId'], + required: ["listId"], } as const; // eslint-disable-next-line import/no-default-export 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 65e708b95..da39bc881 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,38 +1,39 @@ -import { UserLists } from '@/models/index.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; +import { UserLists } from "@/models/index.js"; +import define from "../../../define.js"; +import { ApiError } from "../../../error.js"; export const meta = { - tags: ['lists'], + tags: ["lists"], requireCredential: true, - kind: 'write:account', + kind: "write:account", - description: 'Update the properties of a list.', + description: "Update the properties of a list.", res: { - type: 'object', - optional: false, nullable: false, - ref: 'UserList', + type: "object", + optional: false, + nullable: false, + ref: "UserList", }, errors: { noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '796666fe-3dff-4d39-becb-8a5932c1d5b7', + message: "No such list.", + code: "NO_SUCH_LIST", + id: "796666fe-3dff-4d39-becb-8a5932c1d5b7", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - listId: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', minLength: 1, maxLength: 100 }, + listId: { type: "string", format: "misskey:id" }, + name: { type: "string", minLength: 1, maxLength: 100 }, }, - required: ['listId', 'name'], + required: ["listId", "name"], } as const; // eslint-disable-next-line import/no-default-export diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 1e205eec3..100a17a81 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,80 +1,92 @@ -import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; -import define from '../../define.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 { Brackets } from "typeorm"; +import { Notes } from "@/models/index.js"; +import define from "../../define.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"; export const meta = { - tags: ['users', 'notes'], + tags: ["users", "notes"], requireCredentialPrivateMode: true, - description: 'Show all notes that this user created.', + description: "Show all notes that this user created.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Note', + type: "object", + optional: false, + nullable: false, + ref: "Note", }, }, errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '27e494ba-2ac2-48e8-893b-10d4d8c2387b', + message: "No such user.", + code: "NO_SUCH_USER", + id: "27e494ba-2ac2-48e8-893b-10d4d8c2387b", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - includeReplies: { type: 'boolean', default: true }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, - includeMyRenotes: { type: 'boolean', default: true }, - withFiles: { type: 'boolean', default: false }, - fileType: { type: 'array', items: { - type: 'string', - } }, - excludeNsfw: { type: 'boolean', default: false }, + userId: { type: "string", format: "misskey:id" }, + includeReplies: { type: "boolean", default: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, + includeMyRenotes: { type: "boolean", default: true }, + withFiles: { type: "boolean", default: false }, + fileType: { + type: "array", + items: { + type: "string", + }, + }, + excludeNsfw: { type: "boolean", default: false }, }, - required: ['userId'], + required: ["userId"], } 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); + 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'); + 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) { @@ -83,36 +95,46 @@ export default define(meta, paramDef, async (ps, me) => { } if (ps.withFiles) { - query.andWhere('note.fileIds != \'{}\''); + query.andWhere("note.fileIds != '{}'"); } 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 }); - } - })); + 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)'); + 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'); + 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)'); - })); + 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 diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b76071264..9afcd4c48 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -1,45 +1,49 @@ -import { Pages } from '@/models/index.js'; -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { Pages } from "@/models/index.js"; +import define from "../../define.js"; +import { makePaginationQuery } from "../../common/make-pagination-query.js"; export const meta = { - tags: ['users', 'pages'], + tags: ["users", "pages"], requireCredentialPrivateMode: true, - description: 'Show all pages this user created.', + description: "Show all pages this user created.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'Page', + type: "object", + optional: false, + nullable: false, + ref: "Page", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } 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\'') - .andWhere('page.isPublic = true'); + const query = makePaginationQuery( + Pages.createQueryBuilder("page"), + ps.sinceId, + ps.untilId, + ) + .andWhere("page.userId = :userId", { userId: ps.userId }) + .andWhere("page.visibility = 'public'") + .andWhere("page.isPublic = true"); - const pages = await query - .take(ps.limit) - .getMany(); + const pages = await query.take(ps.limit).getMany(); return await Pages.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 144326958..d5e31b6f6 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,47 +1,49 @@ -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 { ApiError } from '../../error.js'; +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 { ApiError } from "../../error.js"; export const meta = { - tags: ['users', 'reactions'], + tags: ["users", "reactions"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Show all reactions this user made.', + description: "Show all reactions this user made.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'NoteReaction', + type: "object", + optional: false, + nullable: false, + ref: "NoteReaction", }, }, errors: { reactionsNotPublic: { - message: 'Reactions of the user is not public.', - code: 'REACTIONS_NOT_PUBLIC', - id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', + message: "Reactions of the user is not public.", + code: "REACTIONS_NOT_PUBLIC", + id: "673a7dd2-6924-1093-e0c0-e68456ceae5c", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - sinceDate: { type: 'integer' }, - untilDate: { type: 'integer' }, + userId: { type: "string", format: "misskey:id" }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: "string", format: "misskey:id" }, + untilId: { type: "string", format: "misskey:id" }, + sinceDate: { type: "integer" }, + untilDate: { type: "integer" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -52,16 +54,19 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.reactionsNotPublic); } - 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'); + 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"); generateVisibilityQuery(query, me); - const reactions = await query - .take(ps.limit) - .getMany(); + const reactions = await query.take(ps.limit).getMany(); return await NoteReactions.packMany(reactions, 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 d4dc524d9..fa5dcb6c8 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,58 +1,65 @@ -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 { DAY } from '@/const.js'; +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 { DAY } from "@/const.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: true, - kind: 'read:account', + kind: "read:account", - description: 'Show users that the authenticated user might be interested to follow.', + description: + "Show users that the authenticated user might be interested to follow.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'UserDetailed', + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - offset: { type: 'integer', default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + offset: { type: "integer", default: 0 }, }, required: [], } 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() - (7 * DAY)) }) - .andWhere('user.id != :meId', { meId: me.id }) - .orderBy('user.followersCount', 'DESC'); + 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() - 7 * DAY), + }) + .andWhere("user.id != :meId", { meId: me.id }) + .orderBy("user.followersCount", "DESC"); generateMutedUserQueryForUsers(query, me); generateBlockQueryForUsers(query, me); generateBlockedUserQuery(query, me); - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + const followingQuery = Followings.createQueryBuilder("following") + .select("following.followeeId") + .where("following.followerId = :followerId", { followerId: me.id }); - query - .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); + query.andWhere(`user.id NOT IN (${followingQuery.getQuery()})`); query.setParameters(followingQuery.getParameters()); diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index 233a6a90b..dc510c220 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,92 +1,111 @@ -import { Users } from '@/models/index.js'; -import define from '../../define.js'; +import { Users } from "@/models/index.js"; +import define from "../../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: true, - description: 'Show the different kinds of relations between the authenticated user and the specified user(s).', + description: + "Show the different kinds of relations between the authenticated user and the specified user(s).", res: { - optional: false, nullable: false, + optional: false, + nullable: false, oneOf: [ { - type: 'object', + type: "object", properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, isFollowing: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isFollowed: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isBlocking: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isBlocked: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isMuted: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, }, { - type: 'array', + type: "array", items: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { id: { - type: 'string', - optional: false, nullable: false, - format: 'id', + type: "string", + optional: false, + nullable: false, + format: "id", }, isFollowing: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isFollowed: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isBlocking: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isBlocked: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, isMuted: { - type: 'boolean', - optional: false, nullable: false, + type: "boolean", + optional: false, + nullable: false, }, }, }, @@ -96,26 +115,28 @@ export const meta = { } as const; export const paramDef = { - type: 'object', + type: "object", properties: { userId: { anyOf: [ - { type: 'string', format: 'misskey:id' }, + { type: "string", format: "misskey:id" }, { - type: 'array', - items: { type: 'string', format: 'misskey:id' }, + type: "array", + items: { type: "string", format: "misskey:id" }, }, ], }, }, - required: ['userId'], + required: ["userId"], } 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]; - const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id))); + const relations = await Promise.all( + ids.map((id) => Users.getRelation(me.id, id)), + ); return Array.isArray(ps.userId) ? relations : relations[0]; }); diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index a9987eafa..4e02a1ddd 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,55 +1,56 @@ -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 { ApiError } from '../../error.js'; -import define from '../../define.js'; +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 { ApiError } from "../../error.js"; +import define from "../../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: true, - description: 'File a report.', + description: "File a report.", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '1acefcb5-0959-43fd-9685-b48305736cb5', + message: "No such user.", + code: "NO_SUCH_USER", + id: "1acefcb5-0959-43fd-9685-b48305736cb5", }, cannotReportYourself: { - message: 'Cannot report yourself.', - code: 'CANNOT_REPORT_YOURSELF', - id: '1e13149e-b1e8-43cf-902e-c01dbfcb202f', + message: "Cannot report yourself.", + code: "CANNOT_REPORT_YOURSELF", + id: "1e13149e-b1e8-43cf-902e-c01dbfcb202f", }, cannotReportAdmin: { - message: 'Cannot report the admin.', - code: 'CANNOT_REPORT_THE_ADMIN', - id: '35e166f5-05fb-4f87-a2d5-adb42676d48f', + message: "Cannot report the admin.", + code: "CANNOT_REPORT_THE_ADMIN", + id: "35e166f5-05fb-4f87-a2d5-adb42676d48f", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, - comment: { type: 'string', minLength: 1, maxLength: 2048 }, + userId: { type: "string", format: "misskey:id" }, + comment: { type: "string", minLength: 1, maxLength: 2048 }, }, - required: ['userId', 'comment'], + required: ["userId", "comment"], } 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); + const user = await getUser(ps.userId).catch((e) => { + if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") + throw new ApiError(meta.errors.noSuchUser); throw e; }); @@ -69,20 +70,23 @@ export default define(meta, paramDef, async (ps, me) => { reporterId: me.id, reporterHost: null, comment: ps.comment, - }).then(x => AbuseUserReports.findOneByOrFail(x.identifiers[0])); + }).then((x) => AbuseUserReports.findOneByOrFail(x.identifiers[0])); // Publish event to moderators setImmediate(async () => { const moderators = await Users.find({ - where: [{ - isAdmin: true, - }, { - isModerator: true, - }], + where: [ + { + isAdmin: true, + }, + { + isModerator: true, + }, + ], }); for (const moderator of moderators) { - publishAdminStream(moderator.id, 'newAbuseUserReport', { + publishAdminStream(moderator.id, "newAbuseUserReport", { id: report.id, targetUserId: report.targetUserId, reporterId: report.reporterId, @@ -92,9 +96,12 @@ export default define(meta, paramDef, async (ps, me) => { const meta = await fetchMeta(); if (meta.email) { - sendEmail(meta.email, 'New abuse report', + sendEmail( + meta.email, + "New abuse report", sanitizeHtml(ps.comment), - 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 fa1cb8761..9dd1d755f 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,59 +1,60 @@ -import { Brackets } from 'typeorm'; -import { Followings, Users } from '@/models/index.js'; -import { USER_ACTIVE_THRESHOLD } from '@/const.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import { Brackets } from "typeorm"; +import { Followings, Users } from "@/models/index.js"; +import { USER_ACTIVE_THRESHOLD } from "@/const.js"; +import type { User } from "@/models/entities/user.js"; +import define from "../../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Search for a user by username and/or host.', + description: "Search for a user by username and/or host.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'User', + type: "object", + optional: false, + nullable: false, + ref: "User", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - username: { type: 'string', nullable: true }, - host: { type: 'string', nullable: true }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - detail: { type: 'boolean', default: true }, + username: { type: "string", nullable: true }, + host: { type: "string", nullable: true }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + detail: { type: "boolean", default: true }, }, - anyOf: [ - { required: ['username'] }, - { required: ['host'] }, - ], + anyOf: [{ required: ["username"] }, { required: ["host"] }], } as const; // 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日 + 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() + '%' }); + 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.usernameLower LIKE :username", { + username: `${ps.username.toLowerCase()}%`, + }); } - q.andWhere('user.updatedAt IS NOT NULL'); - q.orderBy('user.updatedAt', 'DESC'); + q.andWhere("user.updatedAt IS NOT NULL"); + q.orderBy("user.updatedAt", "DESC"); const users = await q.take(ps.limit).getMany(); @@ -62,50 +63,60 @@ export default define(meta, paramDef, async (ps, me) => { let users: User[] = []; if (me) { - const followingQuery = Followings.createQueryBuilder('following') - .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + 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 }); - })); + 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') + .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'); + 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') + .orderBy("user.updatedAt", "DESC") .take(ps.limit - users.length) .getMany(); users = users.concat(otherUsers); } } 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') + 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(); } diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index 70aaa4526..adeefefb7 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,120 +1,147 @@ -import { Brackets } from 'typeorm'; -import { UserProfiles, Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import define from '../../define.js'; +import { Brackets } from "typeorm"; +import { UserProfiles, Users } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import define from "../../define.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Search for users.', + description: "Search for users.", res: { - type: 'array', - optional: false, nullable: false, + type: "array", + optional: false, + nullable: false, items: { - type: 'object', - optional: false, nullable: false, - ref: 'User', + type: "object", + optional: false, + nullable: false, + ref: "User", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - query: { type: 'string' }, - offset: { type: 'integer', default: 0 }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - origin: { type: 'string', enum: ['local', 'remote', 'combined'], default: 'combined' }, - detail: { type: 'boolean', default: true }, + query: { type: "string" }, + offset: { type: "integer", default: 0 }, + limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, + origin: { + type: "string", + enum: ["local", "remote", "combined"], + default: "combined", + }, + detail: { type: "boolean", default: true }, }, - required: ['query'], + required: ["query"], } 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 activeThreshold = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30); // 30日 - const isUsername = ps.query.startsWith('@'); + 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'); + 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'); + 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') + .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 + '%' }); + 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() + '%' }); - } - })) - .andWhere(new Brackets(qb => { qb - .where('user.updatedAt IS NULL') - .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); - })) - .andWhere('user.isSuspended = FALSE'); + // Also search username if it qualifies as username + if (Users.validateLocalUsername(ps.query)) { + qb.orWhere("user.usernameLower LIKE :username", { + username: `%${ps.query.toLowerCase()}%`, + }); + } + }), + ) + .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'); + 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') + .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 + '%' }); + 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'); + 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') + 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(), + users = users.concat( + await query + .orderBy("user.updatedAt", "DESC", "NULLS LAST") + .take(ps.limit) + .skip(ps.offset) + .getMany(), ); } } diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 892e37bdf..294d70ce6 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,31 +1,33 @@ -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 { ApiError } from '../../error.js'; +import type { FindOptionsWhere } from "typeorm"; +import { In, IsNull } from "typeorm"; +import { resolveUser } from "@/remote/resolve-user.js"; +import { Users } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import define from "../../define.js"; +import { apiLogger } from "../../logger.js"; +import { ApiError } from "../../error.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Show the properties of a user.', + description: "Show the properties of a user.", res: { - optional: false, nullable: false, + optional: false, + nullable: false, oneOf: [ { - type: 'object', - ref: 'UserDetailed', + type: "object", + ref: "UserDetailed", }, { - type: 'array', + type: "array", items: { - type: 'object', - ref: 'UserDetailed', + type: "object", + ref: "UserDetailed", }, }, ], @@ -33,47 +35,52 @@ export const meta = { errors: { failedToResolveRemoteUser: { - message: 'Failed to resolve remote user.', - code: 'FAILED_TO_RESOLVE_REMOTE_USER', - id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c', - kind: 'server', + message: "Failed to resolve remote user.", + code: "FAILED_TO_RESOLVE_REMOTE_USER", + id: "ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c", + kind: "server", }, noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '4362f8dc-731f-4ad8-a694-be5a88922a24', + message: "No such user.", + code: "NO_SUCH_USER", + id: "4362f8dc-731f-4ad8-a694-be5a88922a24", }, }, } as const; export const paramDef = { - type: 'object', + type: "object", anyOf: [ { properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], }, { properties: { - userIds: { type: 'array', uniqueItems: true, items: { - type: 'string', format: 'misskey:id', - } }, - }, - required: ['userIds'], - }, - { - properties: { - username: { type: 'string' }, - host: { - type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', + userIds: { + type: "array", + uniqueItems: true, + items: { + type: "string", + format: "misskey:id", + }, }, }, - required: ['username'], + required: ["userIds"], + }, + { + properties: { + username: { type: "string" }, + host: { + type: "string", + nullable: true, + description: "The local host is represented with `null`.", + }, + }, + required: ["username"], }, ], } as const; @@ -89,33 +96,42 @@ export default define(meta, paramDef, async (ps, me) => { return []; } - const users = await Users.findBy(isAdminOrModerator ? { - id: In(ps.userIds), - } : { - id: In(ps.userIds), - isSuspended: false, - }); + 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)!); + _users.push(users.find((x) => x.id === id)!); } - return await Promise.all(_users.map(u => Users.pack(u, me, { - detail: true, - }))); + 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 => { + 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() }; + const q: FindOptionsWhere = + ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; user = await Users.findOneBy(q); } diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index a68b6ea40..dea34f8bc 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,119 +1,149 @@ -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 { ApiError } from '../../error.js'; +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 { ApiError } from "../../error.js"; export const meta = { - tags: ['users'], + tags: ["users"], requireCredential: false, requireCredentialPrivateMode: true, - description: 'Show statistics about a user.', + description: "Show statistics about a user.", errors: { noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', + message: "No such user.", + code: "NO_SUCH_USER", + id: "9e638e45-3b25-4ef7-8f95-07e8498f1819", }, }, res: { - type: 'object', - optional: false, nullable: false, + type: "object", + optional: false, + nullable: false, properties: { notesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, repliesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, renotesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, repliedCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, renotedCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, pollVotesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, pollVotedCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, localFollowingCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, remoteFollowingCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, localFollowersCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, remoteFollowersCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, followingCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, followersCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, sentReactionsCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, receivedReactionsCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, noteFavoritesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, pageLikesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, pageLikedCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, driveFilesCount: { - type: 'integer', - optional: false, nullable: false, + type: "integer", + optional: false, + nullable: false, }, driveUsage: { - type: 'integer', - optional: false, nullable: false, - description: 'Drive usage in bytes', + type: "integer", + optional: false, + nullable: false, + description: "Drive usage in bytes", }, }, }, } as const; export const paramDef = { - type: 'object', + type: "object", properties: { - userId: { type: 'string', format: 'misskey:id' }, + userId: { type: "string", format: "misskey:id" }, }, - required: ['userId'], + required: ["userId"], } as const; // eslint-disable-next-line import/no-default-export @@ -124,71 +154,73 @@ export default define(meta, paramDef, async (ps, me) => { } const result = await awaitAll({ - notesCount: Notes.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) + 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') + 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') + 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 }) + repliedCount: Notes.createQueryBuilder("note") + .where("note.replyUserId = :userId", { userId: user.id }) .getCount(), - renotedCount: Notes.createQueryBuilder('note') - .where('note.renoteUserId = :userId', { userId: user.id }) + renotedCount: Notes.createQueryBuilder("note") + .where("note.renoteUserId = :userId", { userId: user.id }) .getCount(), - pollVotesCount: PollVotes.createQueryBuilder('vote') - .where('vote.userId = :userId', { userId: user.id }) + 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 }) + 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') + 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') + 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') + 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') + 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 }) + 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 }) + 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 }) + noteFavoritesCount: NoteFavorites.createQueryBuilder("favorite") + .where("favorite.userId = :userId", { userId: user.id }) .getCount(), - pageLikesCount: PageLikes.createQueryBuilder('like') - .where('like.userId = :userId', { userId: user.id }) + 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 }) + 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 }) + 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; + result.followingCount = + result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = + result.localFollowersCount + result.remoteFollowersCount; return result; }); diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 3f0861fdb..c58561a04 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -1,4 +1,10 @@ -type E = { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }; +type E = { + message: string; + code: string; + id: string; + kind?: "client" | "server"; + httpStatusCode?: number; +}; export class ApiError extends Error { public message: string; @@ -9,19 +15,21 @@ export class ApiError extends Error { public info?: any; constructor(e?: E | null | undefined, info?: any | null | undefined) { - if (e == null) e = { - message: 'Internal error occurred. Please contact us if the error persists.', - code: 'INTERNAL_ERROR', - id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', - kind: 'server', - httpStatusCode: 500, - }; + if (e == null) + e = { + message: + "Internal error occurred. Please contact us if the error persists.", + code: "INTERNAL_ERROR", + id: "5d37dbcb-891e-41ca-a3d6-e690c97775ac", + kind: "server", + httpStatusCode: 500, + }; super(e.message); this.message = e.message; this.code = e.code; this.id = e.id; - this.kind = e.kind || 'client'; + this.kind = e.kind || "client"; this.httpStatusCode = e.httpStatusCode; this.info = info; } diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 8701d0a70..b84bbdbb3 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -2,43 +2,48 @@ * API Server */ -import Koa from 'koa'; -import Router from '@koa/router'; -import multer from '@koa/multer'; -import bodyParser from 'koa-bodyparser'; -import cors from '@koa/cors'; -import { Instances, AccessTokens, Users } from '@/models/index.js'; -import config from '@/config/index.js'; -import endpoints from './endpoints.js'; -import compatibility from './compatibility.js'; -import handler from './api-handler.js'; -import signup from './private/signup.js'; -import signin from './private/signin.js'; -import signupPending from './private/signup-pending.js'; -import discord from './service/discord.js'; -import github from './service/github.js'; -import twitter from './service/twitter.js'; +import Koa from "koa"; +import Router from "@koa/router"; +import multer from "@koa/multer"; +import bodyParser from "koa-bodyparser"; +import cors from "@koa/cors"; +import { Instances, AccessTokens, Users } from "@/models/index.js"; +import config from "@/config/index.js"; +import endpoints from "./endpoints.js"; +import compatibility from "./compatibility.js"; +import handler from "./api-handler.js"; +import signup from "./private/signup.js"; +import signin from "./private/signin.js"; +import signupPending from "./private/signup-pending.js"; +import discord from "./service/discord.js"; +import github from "./service/github.js"; +import twitter from "./service/twitter.js"; // Init app const app = new Koa(); -app.use(cors({ - origin: '*', -})); +app.use( + cors({ + origin: "*", + }), +); // No caching app.use(async (ctx, next) => { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); await next(); }); -app.use(bodyParser({ - // リクエストが multipart/form-data でない限りはJSONだと見なす - detectJSON: ctx => !( - ctx.is('multipart/form-data') || - ctx.is('application/x-www-form-urlencoded') - ) -})); +app.use( + bodyParser({ + // リクエストが multipart/form-data でない限りはJSONだと見なす + detectJSON: (ctx) => + !( + ctx.is("multipart/form-data") || + ctx.is("application/x-www-form-urlencoded") + ), + }), +); // Init multer instance const upload = multer({ @@ -57,16 +62,28 @@ const router = new Router(); */ for (const endpoint of [...endpoints, ...compatibility]) { if (endpoint.meta.requireFile) { - router.post(`/${endpoint.name}`, upload.single('file'), handler.bind(null, endpoint)); + router.post( + `/${endpoint.name}`, + upload.single("file"), + handler.bind(null, endpoint), + ); } else { // 後方互換性のため - if (endpoint.name.includes('-')) { - router.post(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); + if (endpoint.name.includes("-")) { + router.post( + `/${endpoint.name.replace(/-/g, "_")}`, + handler.bind(null, endpoint), + ); if (endpoint.meta.allowGet) { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint)); + router.get( + `/${endpoint.name.replace(/-/g, "_")}`, + handler.bind(null, endpoint), + ); } else { - router.get(`/${endpoint.name.replace(/-/g, '_')}`, async ctx => { ctx.status = 405; }); + router.get(`/${endpoint.name.replace(/-/g, "_")}`, async (ctx) => { + ctx.status = 405; + }); } } @@ -75,36 +92,38 @@ for (const endpoint of [...endpoints, ...compatibility]) { if (endpoint.meta.allowGet) { router.get(`/${endpoint.name}`, handler.bind(null, endpoint)); } else { - router.get(`/${endpoint.name}`, async ctx => { ctx.status = 405; }); + router.get(`/${endpoint.name}`, async (ctx) => { + ctx.status = 405; + }); } } } -router.post('/signup', signup); -router.post('/signin', signin); -router.post('/signup-pending', signupPending); +router.post("/signup", signup); +router.post("/signin", signin); +router.post("/signup-pending", signupPending); router.use(discord.routes()); router.use(github.routes()); router.use(twitter.routes()); -router.get('/v1/instance/peers', async ctx => { +router.get("/v1/instance/peers", async (ctx) => { const instances = await Instances.find({ - select: ['host'], + select: ["host"], where: { isSuspended: false, }, }); - ctx.body = instances.map(instance => instance.host); + ctx.body = instances.map((instance) => instance.host); }); -router.post('/miauth/:session/check', async ctx => { +router.post("/miauth/:session/check", async (ctx) => { const token = await AccessTokens.findOneBy({ session: ctx.params.session, }); - if (token && token.session != null && !token.fetched) { + if (token?.session != null && !token.fetched) { AccessTokens.update(token.id, { fetched: true, }); @@ -122,7 +141,7 @@ router.post('/miauth/:session/check', async ctx => { }); // Return 404 for unknown API -router.all('(.*)', async ctx => { +router.all("(.*)", async (ctx) => { ctx.status = 404; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 9a7751716..dd005ad13 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -1,77 +1,85 @@ -import Limiter from 'ratelimiter'; -import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import Logger from '@/services/logger.js'; -import { redisClient } from '../../db/redis.js'; -import { IEndpointMeta } from './endpoints.js'; +import Limiter from "ratelimiter"; +import { CacheableLocalUser, User } from "@/models/entities/user.js"; +import Logger from "@/services/logger.js"; +import { redisClient } from "../../db/redis.js"; +import type { IEndpointMeta } from "./endpoints.js"; -const logger = new Logger('limiter'); +const logger = new Logger("limiter"); -export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) => new Promise((ok, reject) => { - if (process.env.NODE_ENV === 'test') ok(); +export const limiter = ( + limitation: IEndpointMeta["limit"] & { key: NonNullable }, + actor: string, +) => + new Promise((ok, reject) => { + if (process.env.NODE_ENV === "test") ok(); - const hasShortTermLimit = typeof limitation.minInterval === 'number'; + const hasShortTermLimit = typeof limitation.minInterval === "number"; - const hasLongTermLimit = - typeof limitation.duration === 'number' && - typeof limitation.max === 'number'; + const hasLongTermLimit = + typeof limitation.duration === "number" && + typeof limitation.max === "number"; - if (hasShortTermLimit) { - min(); - } else if (hasLongTermLimit) { - max(); - } else { - ok(); - } + if (hasShortTermLimit) { + min(); + } else if (hasLongTermLimit) { + max(); + } else { + ok(); + } - // Short-term limit - function min(): void { - const minIntervalLimiter = new Limiter({ - id: `${actor}:${limitation.key}:min`, - duration: limitation.minInterval, - max: 1, - db: redisClient, - }); + // Short-term limit + function min(): void { + const minIntervalLimiter = new Limiter({ + id: `${actor}:${limitation.key}:min`, + duration: limitation.minInterval, + max: 1, + db: redisClient, + }); - minIntervalLimiter.get((err, info) => { - if (err) { - return reject('ERR'); - } + minIntervalLimiter.get((err, info) => { + if (err) { + return reject("ERR"); + } - logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + logger.debug( + `${actor} ${limitation.key} min remaining: ${info.remaining}`, + ); - if (info.remaining === 0) { - reject('BRIEF_REQUEST_INTERVAL'); - } else { - if (hasLongTermLimit) { - max(); + if (info.remaining === 0) { + reject("BRIEF_REQUEST_INTERVAL"); + } else { + if (hasLongTermLimit) { + max(); + } else { + ok(); + } + } + }); + } + + // Long term limit + function max(): void { + const limiter = new Limiter({ + id: `${actor}:${limitation.key}`, + duration: limitation.duration, + max: limitation.max, + db: redisClient, + }); + + limiter.get((err, info) => { + if (err) { + return reject("ERR"); + } + + logger.debug( + `${actor} ${limitation.key} max remaining: ${info.remaining}`, + ); + + if (info.remaining === 0) { + reject("RATE_LIMIT_EXCEEDED"); } else { ok(); } - } - }); - } - - // Long term limit - function max(): void { - const limiter = new Limiter({ - id: `${actor}:${limitation.key}`, - duration: limitation.duration, - max: limitation.max, - db: redisClient, - }); - - limiter.get((err, info) => { - if (err) { - return reject('ERR'); - } - - logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); - - if (info.remaining === 0) { - reject('RATE_LIMIT_EXCEEDED'); - } else { - ok(); - } - }); - } -}); + }); + } + }); diff --git a/packages/backend/src/server/api/logger.ts b/packages/backend/src/server/api/logger.ts index ec22d6c3e..083888ed7 100644 --- a/packages/backend/src/server/api/logger.ts +++ b/packages/backend/src/server/api/logger.ts @@ -1,3 +1,3 @@ -import Logger from '@/services/logger.js'; +import Logger from "@/services/logger.js"; -export const apiLogger = new Logger('api'); +export const apiLogger = new Logger("api"); diff --git a/packages/backend/src/server/api/openapi/errors.ts b/packages/backend/src/server/api/openapi/errors.ts index e632b4a0f..0fe229d88 100644 --- a/packages/backend/src/server/api/openapi/errors.ts +++ b/packages/backend/src/server/api/openapi/errors.ts @@ -1,67 +1,69 @@ - export const errors = { - '400': { - 'INVALID_PARAM': { + "400": { + INVALID_PARAM: { value: { error: { - message: 'Invalid param.', - code: 'INVALID_PARAM', - id: '3d81ceae-475f-4600-b2a8-2bc116157532', + message: "Invalid param.", + code: "INVALID_PARAM", + id: "3d81ceae-475f-4600-b2a8-2bc116157532", }, }, }, }, - '401': { - 'CREDENTIAL_REQUIRED': { + "401": { + CREDENTIAL_REQUIRED: { value: { error: { - message: 'Credential required.', - code: 'CREDENTIAL_REQUIRED', - id: '1384574d-a912-4b81-8601-c7b1c4085df1', + message: "Credential required.", + code: "CREDENTIAL_REQUIRED", + id: "1384574d-a912-4b81-8601-c7b1c4085df1", }, }, }, }, - '403': { - 'AUTHENTICATION_FAILED': { + "403": { + AUTHENTICATION_FAILED: { value: { error: { - message: 'Authentication failed. Please ensure your token is correct.', - code: 'AUTHENTICATION_FAILED', - id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + message: + "Authentication failed. Please ensure your token is correct.", + code: "AUTHENTICATION_FAILED", + id: "b0a7f5f8-dc2f-4171-b91f-de88ad238e14", }, }, }, }, - '418': { - 'I_AM_CALC': { + "418": { + I_AM_CALC: { value: { error: { - message: 'You sent a request to Calc, Calckey\'s resident stoner furry, instead of the server.', - code: 'I_AM_CALC', - id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84', + message: + "You sent a request to Calc, Calckey's resident stoner furry, instead of the server.", + code: "I_AM_CALC", + id: "60c46cd1-f23a-46b1-bebe-5d2b73951a84", }, }, }, }, - '429': { - 'RATE_LIMIT_EXCEEDED': { + "429": { + RATE_LIMIT_EXCEEDED: { value: { error: { - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + message: "Rate limit exceeded. Please try again later.", + code: "RATE_LIMIT_EXCEEDED", + id: "d5826d14-3982-4d2e-8011-b9e9f02499ef", }, }, }, }, - '500': { - 'INTERNAL_ERROR': { + "500": { + INTERNAL_ERROR: { value: { error: { - message: 'Internal error occurred. Please contact us if the error persists.', - code: 'INTERNAL_ERROR', - id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac', + message: + "Internal error occurred. Please contact us if the error persists.", + code: "INTERNAL_ERROR", + id: "5d37dbcb-891e-41ca-a3d6-e690c97775ac", }, }, }, diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index e49f5e8d0..dfaacf9e5 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -1,26 +1,28 @@ -import endpoints from '../endpoints.js'; -import config from '@/config/index.js'; -import { errors as basicErrors } from './errors.js'; -import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; +import endpoints from "../endpoints.js"; +import config from "@/config/index.js"; +import { errors as basicErrors } from "./errors.js"; +import { schemas, convertSchemaToOpenApiSchema } from "./schemas.js"; export function genOpenapiSpec() { const spec = { - openapi: '3.0.0', + openapi: "3.0.0", info: { - version: 'v1', - title: 'Calckey API', - 'x-logo': { url: '/static-assets/api-doc.png' }, + version: "v1", + title: "Calckey API", + "x-logo": { url: "/static-assets/api-doc.png" }, }, externalDocs: { - description: 'Repository', - url: 'https://codeberg.org/calckey/calckey', + description: "Repository", + url: "https://codeberg.org/calckey/calckey", }, - servers: [{ - url: config.apiUrl, - }], + servers: [ + { + url: config.apiUrl, + }, + ], paths: {} as any, @@ -29,20 +31,20 @@ export function genOpenapiSpec() { securitySchemes: { ApiKeyAuth: { - type: 'apiKey', - in: 'body', - name: 'i', + type: "apiKey", + in: "body", + name: "i", }, // TODO: change this to oauth2 when the remaining oauth stuff is set up Bearer: { - type: 'http', - scheme: 'bearer', - } + type: "http", + scheme: "bearer", + }, }, }, }; - for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) { + for (const endpoint of endpoints.filter((ep) => !ep.meta.secure)) { const errors = {} as any; if (endpoint.meta.errors) { @@ -55,25 +57,34 @@ export function genOpenapiSpec() { } } - const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {}; + const resSchema = endpoint.meta.res + ? convertSchemaToOpenApiSchema(endpoint.meta.res) + : {}; - let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; - desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`; + let desc = + (endpoint.meta.description + ? endpoint.meta.description + : "No description provided.") + "\n\n"; + desc += `**Credential required**: *${ + endpoint.meta.requireCredential ? "Yes" : "No" + }*`; if (endpoint.meta.kind) { const kind = endpoint.meta.kind; desc += ` / **Permission**: *${kind}*`; } - const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; + const requestType = endpoint.meta.requireFile + ? "multipart/form-data" + : "application/json"; const schema = endpoint.params; if (endpoint.meta.requireFile) { schema.properties.file = { - type: 'string', - format: 'binary', - description: 'The file contents.', + type: "string", + format: "binary", + description: "The file contents.", }; - schema.required.push('file'); + schema.required.push("file"); } const security = [ @@ -94,7 +105,7 @@ export function genOpenapiSpec() { summary: endpoint.name, description: desc, externalDocs: { - description: 'Source code', + description: "Source code", url: `https://codeberg.org/calckey/calckey/src/branch/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, }, tags: endpoint.meta.tags || undefined, @@ -108,85 +119,89 @@ export function genOpenapiSpec() { }, }, responses: { - ...(endpoint.meta.res ? { - '200': { - description: 'OK (with results)', - content: { - 'application/json': { - schema: resSchema, - }, - }, - }, - } : { - '204': { - description: 'OK (without any results)', - }, - }), - '400': { - description: 'Client error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: { ...errors, ...basicErrors['400'] }, - }, - }, - }, - '401': { - description: 'Authentication error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['401'], - }, - }, - }, - '403': { - description: 'Forbidden error', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['403'], - }, - }, - }, - '418': { - description: 'I\'m Calc', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', - }, - examples: basicErrors['418'], - }, - }, - }, - ...(endpoint.meta.limit ? { - '429': { - description: 'To many requests', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/Error', + ...(endpoint.meta.res + ? { + "200": { + description: "OK (with results)", + content: { + "application/json": { + schema: resSchema, + }, }, - examples: basicErrors['429'], }, + } + : { + "204": { + description: "OK (without any results)", + }, + }), + "400": { + description: "Client error", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Error", + }, + examples: { ...errors, ...basicErrors["400"] }, }, }, - } : {}), - '500': { - description: 'Internal server error', + }, + "401": { + description: "Authentication error", content: { - 'application/json': { + "application/json": { schema: { - $ref: '#/components/schemas/Error', + $ref: "#/components/schemas/Error", }, - examples: basicErrors['500'], + examples: basicErrors["401"], + }, + }, + }, + "403": { + description: "Forbidden error", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Error", + }, + examples: basicErrors["403"], + }, + }, + }, + "418": { + description: "I'm Calc", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Error", + }, + examples: basicErrors["418"], + }, + }, + }, + ...(endpoint.meta.limit + ? { + "429": { + description: "To many requests", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Error", + }, + examples: basicErrors["429"], + }, + }, + }, + } + : {}), + "500": { + description: "Internal server error", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Error", + }, + examples: basicErrors["500"], }, }, }, @@ -199,10 +214,12 @@ export function genOpenapiSpec() { if (endpoint.meta.allowGet) { path.get = { ...info }; // API Key authentication is not permitted for GET requests - path.get.security = path.get.security.filter(elem => !Object.prototype.hasOwnProperty.call(elem, 'ApiKeyAuth')); + path.get.security = path.get.security.filter( + (elem) => !Object.prototype.hasOwnProperty.call(elem, "ApiKeyAuth"), + ); } - spec.paths['/' + endpoint.name] = path; + spec.paths[`/${endpoint.name}`] = path; } return spec; diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 14bef9cab..68b15d567 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,17 +1,20 @@ -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; - if (schema.type === 'object' && schema.properties) { - res.required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k); + if (schema.type === "object" && schema.properties) { + res.required = Object.entries(schema.properties) + .filter(([k, v]) => !v.optional) + .map(([k]) => k); for (const k of Object.keys(schema.properties)) { res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]); } } - if (schema.type === 'array' && schema.items) { + if (schema.type === "array" && schema.items) { res.items = convertSchemaToOpenApiSchema(schema.items); } @@ -28,33 +31,36 @@ export function convertSchemaToOpenApiSchema(schema: Schema) { export const schemas = { Error: { - type: 'object', + type: "object", properties: { error: { - type: 'object', - description: 'An error object.', + type: "object", + description: "An error object.", properties: { code: { - type: 'string', - description: 'An error code. Unique within the endpoint.', + type: "string", + description: "An error code. Unique within the endpoint.", }, message: { - type: 'string', - description: 'An error message.', + type: "string", + description: "An error message.", }, id: { - type: 'string', - format: 'uuid', - description: 'An error ID. This ID is static.', + type: "string", + format: "uuid", + description: "An error ID. This ID is static.", }, }, - required: ['code', 'id', 'message'], + required: ["code", "id", "message"], }, }, - required: ['error'], + required: ["error"], }, ...Object.fromEntries( - Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]) + Object.entries(refs).map(([key, schema]) => [ + key, + convertSchemaToOpenApiSchema(schema), + ]), ), }; diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index 79b31764f..b06f47ed4 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -1,25 +1,31 @@ -import Koa from 'koa'; -import bcrypt from 'bcryptjs'; -import * as speakeasy from 'speakeasy'; -import signin from '../common/signin.js'; -import config from '@/config/index.js'; -import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { verifyLogin, hash } from '../2fa.js'; -import { randomBytes } from 'node:crypto'; -import { IsNull } from 'typeorm'; -import { limiter } from '../limiter.js'; -import { getIpHash } from '@/misc/get-ip-hash.js'; +import type Koa from "koa"; +import bcrypt from "bcryptjs"; +import * as speakeasy from "speakeasy"; +import signin from "../common/signin.js"; +import config from "@/config/index.js"; +import { + Users, + Signins, + UserProfiles, + UserSecurityKeys, + AttestationChallenges, +} from "@/models/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { genId } from "@/misc/gen-id.js"; +import { verifyLogin, hash } from "../2fa.js"; +import { randomBytes } from "node:crypto"; +import { IsNull } from "typeorm"; +import { limiter } from "../limiter.js"; +import { getIpHash } from "@/misc/get-ip-hash.js"; export default async (ctx: Koa.Context) => { - ctx.set('Access-Control-Allow-Origin', config.url); - ctx.set('Access-Control-Allow-Credentials', 'true'); + ctx.set("Access-Control-Allow-Origin", config.url); + ctx.set("Access-Control-Allow-Credentials", "true"); const body = ctx.request.body as any; - const username = body['username']; - const password = body['password']; - const token = body['token']; + const username = body["username"]; + const password = body["password"]; + const token = body["token"]; function error(status: number, error: { id: string }) { ctx.status = status; @@ -28,50 +34,53 @@ export default async (ctx: Koa.Context) => { try { // not more than 1 attempt per second and not more than 10 attempts per hour - await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); + await limiter( + { key: "signin", duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, + getIpHash(ctx.ip), + ); } catch (err) { ctx.status = 429; ctx.body = { error: { - message: 'Too many failed attempts to sign in. Try again later.', - code: 'TOO_MANY_AUTHENTICATION_FAILURES', - id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + 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') { + if (typeof username !== "string") { ctx.status = 400; return; } - if (typeof password !== 'string') { + if (typeof password !== "string") { ctx.status = 400; return; } - if (token != null && typeof token !== 'string') { + if (token != null && typeof token !== "string") { ctx.status = 400; return; } // Fetch user - const user = await Users.findOneBy({ + const user = (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull(), - }) as ILocalUser; + })) as ILocalUser; if (user == null) { error(404, { - id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', + id: "6cc579cc-885d-43d8-95c2-b8c7fc963280", }); return; } if (user.isSuspended) { error(403, { - id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', + id: "e03a5f46-d309-4865-9b69-56282d94e1eb", }); return; } @@ -92,7 +101,10 @@ export default async (ctx: Koa.Context) => { success: false, }); - error(status || 500, failure || { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); + error( + status || 500, + failure || { id: "4e30e80c-e338-45a0-8c8f-44455efa3b76" }, + ); } if (!profile.twoFactorEnabled) { @@ -101,7 +113,7 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c", }); return; } @@ -110,14 +122,14 @@ export default async (ctx: Koa.Context) => { if (token) { if (!same) { await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c", }); return; } const verified = (speakeasy as any).totp.verify({ secret: profile.twoFactorSecret, - encoding: 'base32', + encoding: "base32", token: token, window: 2, }); @@ -127,30 +139,30 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f', + id: "cdf1235b-ac71-46d4-a3a6-84ccce48df6f", }); return; } } else if (body.credentialId) { - if (!same && !profile.usePasswordLessLogin) { + if (!(same || profile.usePasswordLessLogin)) { await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c", }); return; } - const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); - const clientData = JSON.parse(clientDataJSON.toString('utf-8')); + const clientDataJSON = Buffer.from(body.clientDataJSON, "hex"); + const clientData = JSON.parse(clientDataJSON.toString("utf-8")); const challenge = await AttestationChallenges.findOneBy({ userId: user.id, id: body.challengeId, registrationChallenge: false, - challenge: hash(clientData.challenge).toString('hex'), + challenge: hash(clientData.challenge).toString("hex"), }); if (!challenge) { await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', + id: "2715a88a-2125-4013-932f-aa6fe72792da", }); return; } @@ -162,33 +174,31 @@ export default async (ctx: Koa.Context) => { if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { await fail(403, { - id: '2715a88a-2125-4013-932f-aa6fe72792da', + id: "2715a88a-2125-4013-932f-aa6fe72792da", }); return; } const securityKey = await UserSecurityKeys.findOneBy({ id: Buffer.from( - body.credentialId - .replace(/-/g, '+') - .replace(/_/g, '/'), - 'base64' - ).toString('hex'), + body.credentialId.replace(/-/g, "+").replace(/_/g, "/"), + "base64", + ).toString("hex"), }); if (!securityKey) { await fail(403, { - id: '66269679-aeaf-4474-862b-eb761197e046', + id: "66269679-aeaf-4474-862b-eb761197e046", }); return; } const isValid = verifyLogin({ - publicKey: Buffer.from(securityKey.publicKey, 'hex'), - authenticatorData: Buffer.from(body.authenticatorData, 'hex'), + publicKey: Buffer.from(securityKey.publicKey, "hex"), + authenticatorData: Buffer.from(body.authenticatorData, "hex"), clientDataJSON, clientData, - signature: Buffer.from(body.signature, 'hex'), + signature: Buffer.from(body.signature, "hex"), challenge: challenge.challenge, }); @@ -197,14 +207,14 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - id: '93b86c4b-72f9-40eb-9815-798928603d1e', + id: "93b86c4b-72f9-40eb-9815-798928603d1e", }); return; } } else { - if (!same && !profile.usePasswordLessLogin) { + if (!(same || profile.usePasswordLessLogin)) { await fail(403, { - id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + id: "932c904e-9460-45b7-9ce6-7ed33be7eb2c", }); return; } @@ -215,23 +225,24 @@ export default async (ctx: Koa.Context) => { if (keys.length === 0) { await fail(403, { - id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4', + id: "f27fd449-9af4-4841-9249-1f989b9fa4a4", }); return; } // 32 byte challenge - const challenge = randomBytes(32).toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); + const challenge = randomBytes(32) + .toString("base64") + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); const challengeId = genId(); await AttestationChallenges.insert({ userId: user.id, id: challengeId, - challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + challenge: hash(Buffer.from(challenge, "utf-8")).toString("hex"), createdAt: new Date(), registrationChallenge: false, }); @@ -239,7 +250,7 @@ export default async (ctx: Koa.Context) => { ctx.body = { challenge, challengeId, - securityKeys: keys.map(key => ({ + securityKeys: keys.map((key) => ({ id: key.id, })), }; diff --git a/packages/backend/src/server/api/private/signup-pending.ts b/packages/backend/src/server/api/private/signup-pending.ts index e5e39ba00..c7fdcea22 100644 --- a/packages/backend/src/server/api/private/signup-pending.ts +++ b/packages/backend/src/server/api/private/signup-pending.ts @@ -1,12 +1,12 @@ -import Koa from 'koa'; -import { Users, UserPendings, UserProfiles } from '@/models/index.js'; -import { signup } from '../common/signup.js'; -import signin from '../common/signin.js'; +import type Koa from "koa"; +import { Users, UserPendings, UserProfiles } from "@/models/index.js"; +import { signup } from "../common/signup.js"; +import signin from "../common/signin.js"; export default async (ctx: Koa.Context) => { const body = ctx.request.body; - const code = body['code']; + const code = body["code"]; try { const pendingUser = await UserPendings.findOneByOrFail({ code }); @@ -22,11 +22,14 @@ export default async (ctx: Koa.Context) => { const profile = await UserProfiles.findOneByOrFail({ userId: account.id }); - await UserProfiles.update({ userId: profile.userId }, { - email: pendingUser.email, - emailVerified: true, - emailVerifyCode: null, - }); + await UserProfiles.update( + { userId: profile.userId }, + { + email: pendingUser.email, + emailVerified: true, + emailVerifyCode: null, + }, + ); signin(ctx, account); } catch (e) { diff --git a/packages/backend/src/server/api/private/signup.ts b/packages/backend/src/server/api/private/signup.ts index 26f172637..dc5eb2316 100644 --- a/packages/backend/src/server/api/private/signup.ts +++ b/packages/backend/src/server/api/private/signup.ts @@ -1,14 +1,14 @@ -import Koa from 'koa'; -import rndstr from 'rndstr'; -import bcrypt from 'bcryptjs'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js'; -import { Users, RegistrationTickets, UserPendings } from '@/models/index.js'; -import { signup } from '../common/signup.js'; -import config from '@/config/index.js'; -import { sendEmail } from '@/services/send-email.js'; -import { genId } from '@/misc/gen-id.js'; -import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; +import type Koa from "koa"; +import rndstr from "rndstr"; +import bcrypt from "bcryptjs"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { verifyHcaptcha, verifyRecaptcha } from "@/misc/captcha.js"; +import { Users, RegistrationTickets, UserPendings } from "@/models/index.js"; +import { signup } from "../common/signup.js"; +import config from "@/config/index.js"; +import { sendEmail } from "@/services/send-email.js"; +import { genId } from "@/misc/gen-id.js"; +import { validateEmailForAccount } from "@/services/validate-email-for-account.js"; export default async (ctx: Koa.Context) => { const body = ctx.request.body; @@ -17,28 +17,35 @@ export default async (ctx: Koa.Context) => { // Verify *Captcha // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test') { + if (process.env.NODE_ENV !== "test") { if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { - await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => { + await verifyHcaptcha( + instance.hcaptchaSecretKey, + body["hcaptcha-response"], + ).catch((e) => { ctx.throw(400, e); }); } if (instance.enableRecaptcha && instance.recaptchaSecretKey) { - await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => { + await verifyRecaptcha( + instance.recaptchaSecretKey, + body["g-recaptcha-response"], + ).catch((e) => { ctx.throw(400, e); }); } } - const username = body['username']; - const password = body['password']; - const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; - const invitationCode = body['invitationCode']; - const emailAddress = body['emailAddress']; + const username = body["username"]; + const password = body["password"]; + const host: string | null = + process.env.NODE_ENV === "test" ? body["host"] || null : null; + const invitationCode = body["invitationCode"]; + const emailAddress = body["emailAddress"]; if (instance.emailRequiredForSignup) { - if (emailAddress == null || typeof emailAddress !== 'string') { + if (emailAddress == null || typeof emailAddress !== "string") { ctx.status = 400; return; } @@ -51,7 +58,7 @@ export default async (ctx: Koa.Context) => { } if (instance.disableRegistration) { - if (invitationCode == null || typeof invitationCode !== 'string') { + if (invitationCode == null || typeof invitationCode !== "string") { ctx.status = 400; return; } @@ -69,7 +76,7 @@ export default async (ctx: Koa.Context) => { } if (instance.emailRequiredForSignup) { - const code = rndstr('a-z0-9', 16); + const code = rndstr("a-z0-9", 16); // Generate hash of password const salt = await bcrypt.genSalt(8); @@ -86,15 +93,20 @@ export default async (ctx: Koa.Context) => { const link = `${config.url}/signup-complete/${code}`; - sendEmail(emailAddress, 'Signup', + sendEmail( + emailAddress, + "Signup", `To complete signup, please click this link:
${link}`, - `To complete signup, please click this link: ${link}`); + `To complete signup, please click this link: ${link}`, + ); ctx.status = 204; } else { try { const { account, secret } = await signup({ - username, password, host, + username, + password, + host, }); const res = await Users.pack(account, account, { diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts index 97cbcbecd..9906d2f7c 100644 --- a/packages/backend/src/server/api/service/discord.ts +++ b/packages/backend/src/server/api/service/discord.ts @@ -1,43 +1,43 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { OAuth2 } from 'oauth'; -import { v4 as uuid } from 'uuid'; -import { IsNull } from 'typeorm'; -import { getJson } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { redisClient } from '../../../db/redis.js'; -import signin from '../common/signin.js'; +import type Koa from "koa"; +import Router from "@koa/router"; +import { OAuth2 } from "oauth"; +import { v4 as uuid } from "uuid"; +import { IsNull } from "typeorm"; +import { getJson } from "@/misc/fetch.js"; +import config from "@/config/index.js"; +import { publishMainStream } from "@/services/stream.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, UserProfiles } from "@/models/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { redisClient } from "../../../db/redis.js"; +import signin from "../common/signin.js"; function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; + return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; } function compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { - return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; + return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; } - const referer = ctx.headers['referer']; + const referer = ctx.headers["referer"]; - return (normalizeUrl(referer) === normalizeUrl(config.url)); + return normalizeUrl(referer) === normalizeUrl(config.url); } // Init router const router = new Router(); -router.get('/disconnect/discord', async ctx => { +router.get("/disconnect/discord", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (!userToken) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } @@ -48,19 +48,23 @@ router.get('/disconnect/discord', async ctx => { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - delete profile.integrations.discord; + profile.integrations.discord = undefined; await UserProfiles.update(user.id, { integrations: profile.integrations, }); - ctx.body = 'Discordの連携を解除しました :v:'; + ctx.body = "Discordの連携を解除しました :v:"; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); }); async function getOAuth2() { @@ -70,31 +74,32 @@ async function getOAuth2() { return new OAuth2( meta.discordClientId!, meta.discordClientSecret!, - 'https://discord.com/', - 'api/oauth2/authorize', - 'api/oauth2/token'); + "https://discord.com/", + "api/oauth2/authorize", + "api/oauth2/token", + ); } else { return null; } } -router.get('/connect/discord', async ctx => { +router.get("/connect/discord", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (!userToken) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } const params = { redirect_uri: `${config.url}/api/dc/cb`, - scope: ['identify'], + scope: ["identify"], state: uuid(), - response_type: 'code', + response_type: "code", }; redisClient.set(userToken, JSON.stringify(params)); @@ -103,19 +108,19 @@ router.get('/connect/discord', async ctx => { ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); -router.get('/signin/discord', async ctx => { +router.get("/signin/discord", async (ctx) => { const sessid = uuid(); const params = { redirect_uri: `${config.url}/api/dc/cb`, - scope: ['identify'], + scope: ["identify"], state: uuid(), - response_type: 'code', + response_type: "code", }; - ctx.cookies.set('signin_with_discord_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), + ctx.cookies.set("signin_with_discord_sid", sessid, { + path: "/", + secure: config.url.startsWith("https"), httpOnly: true, }); @@ -125,23 +130,23 @@ router.get('/signin/discord', async ctx => { ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); -router.get('/dc/cb', async ctx => { +router.get("/dc/cb", async (ctx) => { const userToken = getUserToken(ctx); const oauth2 = await getOAuth2(); if (!userToken) { - const sessid = ctx.cookies.get('signin_with_discord_sid'); + const sessid = ctx.cookies.get("signin_with_discord_sid"); if (!sessid) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } const code = ctx.query.code; - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); + if (!code || typeof code !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -152,44 +157,62 @@ router.get('/dc/cb', async ctx => { }); if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } - const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - grant_type: 'authorization_code', - redirect_uri, - }, (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - })); + const { accessToken, refreshToken, expiresDate } = await new Promise( + (res, rej) => + oauth2!.getOAuthAccessToken( + code, + { + grant_type: "authorization_code", + redirect_uri, + }, + (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000, + }); + } + }, + ), + ); - const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { - 'Authorization': `Bearer ${accessToken}`, - })) as Record; + const { id, username, discriminator } = (await getJson( + "https://discord.com/api/users/@me", + "*/*", + 10 * 1000, + { + Authorization: `Bearer ${accessToken}`, + }, + )) as Record; - if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); + if ( + typeof id !== "string" || + typeof username !== "string" || + typeof discriminator !== "string" + ) { + ctx.throw(400, "invalid session"); return; } const profile = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id }) + .where("\"integrations\"->'discord'->>'id' = :id", { id: id }) .andWhere('"userHost" IS NULL') .getOne(); if (profile == null) { - ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); + ctx.throw( + 404, + `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`, + ); return; } @@ -207,12 +230,16 @@ router.get('/dc/cb', async ctx => { }, }); - signin(ctx, await Users.findOneBy({ id: profile.userId }) as ILocalUser, true); + signin( + ctx, + (await Users.findOneBy({ id: profile.userId })) as ILocalUser, + true, + ); } else { const code = ctx.query.code; - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); + if (!code || typeof code !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -223,33 +250,48 @@ router.get('/dc/cb', async ctx => { }); if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } - const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - grant_type: 'authorization_code', - redirect_uri, - }, (err, accessToken, refreshToken, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000, - }); - } - })); + const { accessToken, refreshToken, expiresDate } = await new Promise( + (res, rej) => + oauth2!.getOAuthAccessToken( + code, + { + grant_type: "authorization_code", + redirect_uri, + }, + (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000, + }); + } + }, + ), + ); - const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { - 'Authorization': `Bearer ${accessToken}`, - })) as Record; - if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { - ctx.throw(400, 'invalid session'); + const { id, username, discriminator } = (await getJson( + "https://discord.com/api/users/@me", + "*/*", + 10 * 1000, + { + Authorization: `Bearer ${accessToken}`, + }, + )) as Record; + if ( + typeof id !== "string" || + typeof username !== "string" || + typeof discriminator !== "string" + ) { + ctx.throw(400, "invalid session"); return; } @@ -277,10 +319,14 @@ router.get('/dc/cb', async ctx => { ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); } }); diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts index 04dbd1f7a..f77c5f795 100644 --- a/packages/backend/src/server/api/service/github.ts +++ b/packages/backend/src/server/api/service/github.ts @@ -1,43 +1,43 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { OAuth2 } from 'oauth'; -import { v4 as uuid } from 'uuid'; -import { IsNull } from 'typeorm'; -import { getJson } from '@/misc/fetch.js'; -import config from '@/config/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { redisClient } from '../../../db/redis.js'; -import signin from '../common/signin.js'; +import type Koa from "koa"; +import Router from "@koa/router"; +import { OAuth2 } from "oauth"; +import { v4 as uuid } from "uuid"; +import { IsNull } from "typeorm"; +import { getJson } from "@/misc/fetch.js"; +import config from "@/config/index.js"; +import { publishMainStream } from "@/services/stream.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, UserProfiles } from "@/models/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { redisClient } from "../../../db/redis.js"; +import signin from "../common/signin.js"; function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; + return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; } function compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { - return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; + return url ? (url.endsWith("/") ? url.substr(0, url.length - 1) : url) : ""; } - const referer = ctx.headers['referer']; + const referer = ctx.headers["referer"]; - return (normalizeUrl(referer) === normalizeUrl(config.url)); + return normalizeUrl(referer) === normalizeUrl(config.url); } // Init router const router = new Router(); -router.get('/disconnect/github', async ctx => { +router.get("/disconnect/github", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (!userToken) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } @@ -48,51 +48,60 @@ router.get('/disconnect/github', async ctx => { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - delete profile.integrations.github; + profile.integrations.github = undefined; await UserProfiles.update(user.id, { integrations: profile.integrations, }); - ctx.body = 'GitHubの連携を解除しました :v:'; + ctx.body = "GitHubの連携を解除しました :v:"; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); }); async function getOath2() { const meta = await fetchMeta(true); - if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) { + if ( + meta.enableGithubIntegration && + meta.githubClientId && + meta.githubClientSecret + ) { return new OAuth2( meta.githubClientId, meta.githubClientSecret, - 'https://github.com/', - 'login/oauth/authorize', - 'login/oauth/access_token'); + "https://github.com/", + "login/oauth/authorize", + "login/oauth/access_token", + ); } else { return null; } } -router.get('/connect/github', async ctx => { +router.get("/connect/github", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (!userToken) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } const params = { redirect_uri: `${config.url}/api/gh/cb`, - scope: ['read:user'], + scope: ["read:user"], state: uuid(), }; @@ -102,18 +111,18 @@ router.get('/connect/github', async ctx => { ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); -router.get('/signin/github', async ctx => { +router.get("/signin/github", async (ctx) => { const sessid = uuid(); const params = { redirect_uri: `${config.url}/api/gh/cb`, - scope: ['read:user'], + scope: ["read:user"], state: uuid(), }; - ctx.cookies.set('signin_with_github_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), + ctx.cookies.set("signin_with_github_sid", sessid, { + path: "/", + secure: config.url.startsWith("https"), httpOnly: true, }); @@ -123,23 +132,23 @@ router.get('/signin/github', async ctx => { ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); -router.get('/gh/cb', async ctx => { +router.get("/gh/cb", async (ctx) => { const userToken = getUserToken(ctx); const oauth2 = await getOath2(); if (!userToken) { - const sessid = ctx.cookies.get('signin_with_github_sid'); + const sessid = ctx.cookies.get("signin_with_github_sid"); if (!sessid) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } const code = ctx.query.code; - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); + if (!code || typeof code !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -150,47 +159,64 @@ router.get('/gh/cb', async ctx => { }); if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } const { accessToken } = await new Promise((res, rej) => - oauth2!.getOAuthAccessToken(code, { - redirect_uri, - }, (err, accessToken, refresh, result) => { - if (err) { - rej(err); - } else if (result.error) { - rej(result.error); - } else { - res({ accessToken }); - } - })); + oauth2!.getOAuthAccessToken( + code, + { + redirect_uri, + }, + (err, accessToken, refresh, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ accessToken }); + } + }, + ), + ); - const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { - 'Authorization': `bearer ${accessToken}`, - })) as Record; - if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); + const { login, id } = (await getJson( + "https://api.github.com/user", + "application/vnd.github.v3+json", + 10 * 1000, + { + Authorization: `bearer ${accessToken}`, + }, + )) as Record; + if (typeof login !== "string" || typeof id !== "string") { + ctx.throw(400, "invalid session"); return; } const link = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'github\'->>\'id\' = :id', { id: id }) + .where("\"integrations\"->'github'->>'id' = :id", { id: id }) .andWhere('"userHost" IS NULL') .getOne(); if (link == null) { - ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); + ctx.throw( + 404, + `@${login}と連携しているMisskeyアカウントはありませんでした...`, + ); return; } - signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); + signin( + ctx, + (await Users.findOneBy({ id: link.userId })) as ILocalUser, + true, + ); } else { const code = ctx.query.code; - if (!code || typeof code !== 'string') { - ctx.throw(400, 'invalid session'); + if (!code || typeof code !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -201,7 +227,7 @@ router.get('/gh/cb', async ctx => { }); if (ctx.query.state !== state) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } @@ -217,14 +243,21 @@ router.get('/gh/cb', async ctx => { } else { res({ accessToken }); } - })); + }, + ), + ); - const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { - 'Authorization': `bearer ${accessToken}`, - })) as Record; + const { login, id } = (await getJson( + "https://api.github.com/user", + "application/vnd.github.v3+json", + 10 * 1000, + { + Authorization: `bearer ${accessToken}`, + }, + )) as Record; - if (typeof login !== 'string' || typeof id !== 'string') { - ctx.throw(400, 'invalid session'); + if (typeof login !== "string" || typeof id !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -249,10 +282,14 @@ router.get('/gh/cb', async ctx => { ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); } }); diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts index 2b4f9f6da..369559241 100644 --- a/packages/backend/src/server/api/service/twitter.ts +++ b/packages/backend/src/server/api/service/twitter.ts @@ -1,42 +1,46 @@ -import Koa from 'koa'; -import Router from '@koa/router'; -import { v4 as uuid } from 'uuid'; -import autwh from 'autwh'; -import { IsNull } from 'typeorm'; -import { publishMainStream } from '@/services/stream.js'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, UserProfiles } from '@/models/index.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import signin from '../common/signin.js'; -import { redisClient } from '../../../db/redis.js'; +import type Koa from "koa"; +import Router from "@koa/router"; +import { v4 as uuid } from "uuid"; +import autwh from "autwh"; +import { IsNull } from "typeorm"; +import { publishMainStream } from "@/services/stream.js"; +import config from "@/config/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, UserProfiles } from "@/models/index.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import signin from "../common/signin.js"; +import { redisClient } from "../../../db/redis.js"; function getUserToken(ctx: Koa.BaseContext): string | null { - return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; + return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1]; } function compareOrigin(ctx: Koa.BaseContext): boolean { function normalizeUrl(url?: string): string { - return url == null ? '' : url.endsWith('/') ? url.substr(0, url.length - 1) : url; + return url == null + ? "" + : url.endsWith("/") + ? url.substr(0, url.length - 1) + : url; } - const referer = ctx.headers['referer']; + const referer = ctx.headers["referer"]; - return (normalizeUrl(referer) === normalizeUrl(config.url)); + return normalizeUrl(referer) === normalizeUrl(config.url); } // Init router const router = new Router(); -router.get('/disconnect/twitter', async ctx => { +router.get("/disconnect/twitter", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (userToken == null) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } @@ -47,25 +51,33 @@ router.get('/disconnect/twitter', async ctx => { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - delete profile.integrations.twitter; + profile.integrations.twitter = undefined; await UserProfiles.update(user.id, { integrations: profile.integrations, }); - ctx.body = 'Twitterの連携を解除しました :v:'; + ctx.body = "Twitterの連携を解除しました :v:"; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); }); async function getTwAuth() { const meta = await fetchMeta(true); - if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) { + if ( + meta.enableTwitterIntegration && + meta.twitterConsumerKey && + meta.twitterConsumerSecret + ) { return autwh({ consumerKey: meta.twitterConsumerKey, consumerSecret: meta.twitterConsumerSecret, @@ -76,15 +88,15 @@ async function getTwAuth() { } } -router.get('/connect/twitter', async ctx => { +router.get("/connect/twitter", async (ctx) => { if (!compareOrigin(ctx)) { - ctx.throw(400, 'invalid origin'); + ctx.throw(400, "invalid origin"); return; } const userToken = getUserToken(ctx); if (userToken == null) { - ctx.throw(400, 'signin required'); + ctx.throw(400, "signin required"); return; } @@ -94,7 +106,7 @@ router.get('/connect/twitter', async ctx => { ctx.redirect(twCtx.url); }); -router.get('/signin/twitter', async ctx => { +router.get("/signin/twitter", async (ctx) => { const twAuth = await getTwAuth(); const twCtx = await twAuth!.begin(); @@ -102,25 +114,25 @@ router.get('/signin/twitter', async ctx => { redisClient.set(sessid, JSON.stringify(twCtx)); - ctx.cookies.set('signin_with_twitter_sid', sessid, { - path: '/', - secure: config.url.startsWith('https'), + ctx.cookies.set("signin_with_twitter_sid", sessid, { + path: "/", + secure: config.url.startsWith("https"), httpOnly: true, }); ctx.redirect(twCtx.url); }); -router.get('/tw/cb', async ctx => { +router.get("/tw/cb", async (ctx) => { const userToken = getUserToken(ctx); const twAuth = await getTwAuth(); if (userToken == null) { - const sessid = ctx.cookies.get('signin_with_twitter_sid'); + const sessid = ctx.cookies.get("signin_with_twitter_sid"); if (sessid == null) { - ctx.throw(400, 'invalid session'); + ctx.throw(400, "invalid session"); return; } @@ -133,29 +145,38 @@ router.get('/tw/cb', async ctx => { const twCtx = await get; const verifier = ctx.query.oauth_verifier; - if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); + if (!verifier || typeof verifier !== "string") { + ctx.throw(400, "invalid session"); return; } const result = await twAuth!.done(JSON.parse(twCtx), verifier); const link = await UserProfiles.createQueryBuilder() - .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId }) + .where("\"integrations\"->'twitter'->>'userId' = :id", { + id: result.userId, + }) .andWhere('"userHost" IS NULL') .getOne(); if (link == null) { - ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); + ctx.throw( + 404, + `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`, + ); return; } - signin(ctx, await Users.findOneBy({ id: link.userId }) as ILocalUser, true); + signin( + ctx, + (await Users.findOneBy({ id: link.userId })) as ILocalUser, + true, + ); } else { const verifier = ctx.query.oauth_verifier; - if (!verifier || typeof verifier !== 'string') { - ctx.throw(400, 'invalid session'); + if (!verifier || typeof verifier !== "string") { + ctx.throw(400, "invalid session"); return; } @@ -191,10 +212,14 @@ router.get('/tw/cb', async ctx => { ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + user.id, + "meUpdated", + await Users.pack(user, user, { + detail: true, + includeSecrets: true, + }), + ); } }); diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index c9cffd2d3..93dbdc426 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,8 +1,8 @@ -import Connection from '.'; -import { Note } from '@/models/entities/note.js'; -import { Notes } from '@/models/index.js'; -import { Packed } from '@/misc/schema.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; +import type Connection from "."; +import type { Note } from "@/models/entities/note.js"; +import { Notes } from "@/models/index.js"; +import type { Packed } from "@/misc/schema.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; /** * Stream channel @@ -51,30 +51,35 @@ export default abstract class Channel { const type = payload === undefined ? typeOrPayload.type : typeOrPayload; const body = payload === undefined ? typeOrPayload.body : payload; - this.connection.sendMessageToWs('channel', { + this.connection.sendMessageToWs("channel", { id: this.id, type: type, body: body, }); } - protected withPackedNote(callback: (note: Packed<'Note'>) => void): (Note) => void { + protected withPackedNote( + callback: (note: Packed<"Note">) => void, + ): (Note) => void { return async (note: Note) => { try { // because `note` was previously JSON.stringify'ed, the fields that // were objects before are now strings and have to be restored or // removed from the object note.createdAt = new Date(note.createdAt); - delete note.reply; - delete note.renote; - delete note.user; - delete note.channel; + note.reply = undefined; + note.renote = undefined; + note.user = undefined; + note.channel = undefined; const packed = await Notes.pack(note, this.user, { detail: true }); callback(packed); } catch (err) { - if (err instanceof IdentifiableError && err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') { + if ( + err instanceof IdentifiableError && + err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24" + ) { // skip: note not visible to user return; } else { diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts index 945182ea1..59ae22825 100644 --- a/packages/backend/src/server/api/stream/channels/admin.ts +++ b/packages/backend/src/server/api/stream/channels/admin.ts @@ -1,13 +1,13 @@ -import Channel from '../channel.js'; +import Channel from "../channel.js"; export default class extends Channel { - public readonly chName = 'admin'; + public readonly chName = "admin"; public static shouldShare = true; public static requireCredential = true; public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user!.id}`, data => { + this.subscriber.on(`adminStream:${this.user!.id}`, (data) => { this.send(data); }); } diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index a9a98e904..8e0d08110 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -1,16 +1,16 @@ -import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { StreamMessages } from '../types.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; +import Channel from "../channel.js"; +import { Notes } from "@/models/index.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import type { StreamMessages } from "../types.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; export default class extends Channel { - public readonly chName = 'antenna'; + public readonly chName = "antenna"; public static shouldShare = false; public static requireCredential = false; private antennaId: string; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onEvent = this.onEvent.bind(this); } @@ -22,10 +22,12 @@ export default class extends Channel { this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent); } - private async onEvent(data: StreamMessages['antenna']['payload']) { - if (data.type === 'note') { + private async onEvent(data: StreamMessages["antenna"]["payload"]) { + if (data.type === "note") { try { - const note = await Notes.pack(data.body.id, this.user, { detail: true }); + const note = await Notes.pack(data.body.id, this.user, { + detail: true, + }); // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; @@ -34,9 +36,12 @@ export default class extends Channel { this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } catch (e) { - if (e instanceof IdentifiableError && e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') { + if ( + e instanceof IdentifiableError && + e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24" + ) { // skip: note not visible to user return; } else { diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 7ed47c389..6ae838d2a 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -1,19 +1,19 @@ -import Channel from '../channel.js'; -import { Users } from '@/models/index.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { User } from '@/models/entities/user.js'; -import { StreamMessages } from '../types.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { Users } from "@/models/index.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import type { User } from "@/models/entities/user.js"; +import type { StreamMessages } from "../types.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'channel'; + public readonly chName = "channel"; public static shouldShare = false; public static requireCredential = false; private channelId: string; - private typers: Record = {}; + private typers: Record = {}; private emitTypersIntervalId: ReturnType; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.onNote.bind(this); this.emitTypers = this.emitTypers.bind(this); @@ -23,12 +23,12 @@ export default class extends Channel { this.channelId = params.channelId as string; // Subscribe stream - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent); this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { if (note.channelId !== this.channelId) return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -38,11 +38,11 @@ export default class extends Channel { this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } - private onEvent(data: StreamMessages['channel']['payload']) { - if (data.type === 'typing') { + private onEvent(data: StreamMessages["channel"]["payload"]) { + if (data.type === "typing") { const id = data.body; const begin = this.typers[id] == null; this.typers[id] = new Date(); @@ -57,20 +57,22 @@ export default class extends Channel { // Remove not typing users for (const [userId, date] of Object.entries(this.typers)) { - if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; + if (now.getTime() - date.getTime() > 5000) this.typers[userId] = undefined; } - const users = await Users.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await Users.packMany(Object.keys(this.typers), null, { + detail: false, + }); this.send({ - type: 'typers', + type: "typers", body: users, }); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent); clearInterval(this.emitTypersIntervalId); diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts index 140255acd..275730eae 100644 --- a/packages/backend/src/server/api/stream/channels/drive.ts +++ b/packages/backend/src/server/api/stream/channels/drive.ts @@ -1,13 +1,13 @@ -import Channel from '../channel.js'; +import Channel from "../channel.js"; export default class extends Channel { - public readonly chName = 'drive'; + public readonly chName = "drive"; public static shouldShare = true; public static requireCredential = true; public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user!.id}`, data => { + this.subscriber.on(`driveStream:${this.user!.id}`, (data) => { this.send(data); }); } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 391851ecd..3837becd2 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -1,16 +1,16 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { isInstanceMuted } from "@/misc/is-instance-muted.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'globalTimeline'; + public readonly chName = "globalTimeline"; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } @@ -18,26 +18,38 @@ export default class extends Channel { public async init(params: any) { const meta = await fetchMeta(); if (meta.disableGlobalTimeline) { - if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return; + if (this.user == null || (!(this.user.isAdmin || this.user.isModerator))) + return; } // Subscribe events - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - private async onNote(note: Packed<'Note'>) { - if (note.visibility !== 'public') return; + private async onNote(note: Packed<"Note">) { + if (note.visibility !== "public") return; if (note.channelId != null) return; // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; + if ( + reply.userId !== this.user!.id && + note.userId !== this.user!.id && + reply.userId !== note.userId + ) + return; } // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; + if ( + isInstanceMuted( + note, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; @@ -49,15 +61,19 @@ export default class extends Channel { // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる - if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; + if ( + this.userProfile && + (await checkWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); } } diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index f9f7ae410..9d7b518d9 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -1,15 +1,15 @@ -import Channel from '../channel.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'hashtag'; + public readonly chName = "hashtag"; public static shouldShare = false; public static requireCredential = false; private q: string[][]; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } @@ -20,12 +20,16 @@ export default class extends Channel { if (this.q == null) return; // Subscribe stream - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - 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)))); + 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))), + ); if (!matched) return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -35,11 +39,11 @@ export default class extends Channel { this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); } } 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 9f5188547..47d773638 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -1,40 +1,52 @@ -import Channel from '../channel.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import { isInstanceMuted } from "@/misc/is-instance-muted.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'homeTimeline'; + public readonly chName = "homeTimeline"; public static shouldShare = true; public static requireCredential = true; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { // Subscribe events - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { if (note.channelId) { if (!this.followingChannels.has(note.channelId)) return; } else { // その投稿のユーザーをフォローしていなかったら弾く - if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return; + if (this.user!.id !== note.userId && !this.following.has(note.userId)) + return; } // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; + if ( + isInstanceMuted( + note, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; + if ( + reply.userId !== this.user!.id && + note.userId !== this.user!.id && + reply.userId !== note.userId + ) + return; } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -47,15 +59,19 @@ export default class extends Channel { // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる - if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; + if ( + this.userProfile && + (await checkWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("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 e73136b8e..398127c40 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -1,48 +1,69 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import { isInstanceMuted } from "@/misc/is-instance-muted.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'hybridTimeline'; + public readonly chName = "hybridTimeline"; public static shouldShare = true; public static requireCredential = true; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; + if ( + meta.disableLocalTimeline && + !this.user!.isAdmin && + !this.user!.isModerator + ) + return; // Subscribe events - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または // チャンネルの投稿ではなく、全体公開のローカルの投稿 または // フォローしているチャンネルの投稿 の場合だけ - if (!( - (note.channelId == null && this.user!.id === note.userId) || - (note.channelId == null && this.following.has(note.userId)) || - (note.channelId == null && (note.user.host == null && note.visibility === 'public')) || - (note.channelId != null && this.followingChannels.has(note.channelId)) - )) return; + if ( + !( + (note.channelId == null && this.user!.id === note.userId) || + (note.channelId == null && this.following.has(note.userId)) || + (note.channelId == null && + note.user.host == null && + note.visibility === "public") || + (note.channelId != null && this.followingChannels.has(note.channelId)) + ) + ) + return; // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; + if ( + isInstanceMuted( + note, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; + if ( + reply.userId !== this.user!.id && + note.userId !== this.user!.id && + reply.userId !== note.userId + ) + return; } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -55,15 +76,19 @@ export default class extends Channel { // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる - if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; + if ( + this.userProfile && + (await checkWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); } } diff --git a/packages/backend/src/server/api/stream/channels/index.ts b/packages/backend/src/server/api/stream/channels/index.ts index 926b0d4e4..d1127be47 100644 --- a/packages/backend/src/server/api/stream/channels/index.ts +++ b/packages/backend/src/server/api/stream/channels/index.ts @@ -1,19 +1,19 @@ -import main from './main.js'; -import homeTimeline from './home-timeline.js'; -import localTimeline from './local-timeline.js'; -import hybridTimeline from './hybrid-timeline.js'; -import recommendedTimeline from './recommended-timeline.js'; -import globalTimeline from './global-timeline.js'; -import serverStats from './server-stats.js'; -import queueStats from './queue-stats.js'; -import userList from './user-list.js'; -import antenna from './antenna.js'; -import messaging from './messaging.js'; -import messagingIndex from './messaging-index.js'; -import drive from './drive.js'; -import hashtag from './hashtag.js'; -import channel from './channel.js'; -import admin from './admin.js'; +import main from "./main.js"; +import homeTimeline from "./home-timeline.js"; +import localTimeline from "./local-timeline.js"; +import hybridTimeline from "./hybrid-timeline.js"; +import recommendedTimeline from "./recommended-timeline.js"; +import globalTimeline from "./global-timeline.js"; +import serverStats from "./server-stats.js"; +import queueStats from "./queue-stats.js"; +import userList from "./user-list.js"; +import antenna from "./antenna.js"; +import messaging from "./messaging.js"; +import messagingIndex from "./messaging-index.js"; +import drive from "./drive.js"; +import hashtag from "./hashtag.js"; +import channel from "./channel.js"; +import admin from "./admin.js"; export default { main, 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 729de6d4a..253fd29b4 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -1,15 +1,15 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'localTimeline'; + public readonly chName = "localTimeline"; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } @@ -17,23 +17,30 @@ export default class extends Channel { public async init(params: any) { const meta = await fetchMeta(); if (meta.disableLocalTimeline) { - if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return; + if (this.user == null || (!(this.user.isAdmin || this.user.isModerator))) + return; } // Subscribe events - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { if (note.user.host !== null) return; - if (note.visibility !== 'public') return; - if (note.channelId != null && !this.followingChannels.has(note.channelId)) return; + if (note.visibility !== "public") return; + if (note.channelId != null && !this.followingChannels.has(note.channelId)) + return; // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; + if ( + reply.userId !== this.user!.id && + note.userId !== this.user!.id && + reply.userId !== note.userId + ) + return; } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -46,15 +53,19 @@ export default class extends Channel { // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる - if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; + if ( + this.userProfile && + (await checkWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); } } diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 7f42263db..b8c72442f 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -1,24 +1,39 @@ -import Channel from '../channel.js'; -import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; +import Channel from "../channel.js"; +import { + isInstanceMuted, + isUserFromMutedInstance, +} from "@/misc/is-instance-muted.js"; export default class extends Channel { - public readonly chName = 'main'; + public readonly chName = "main"; public static shouldShare = true; public static requireCredential = true; public async init(params: any) { // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user!.id}`, async data => { + this.subscriber.on(`mainStream:${this.user!.id}`, async (data) => { switch (data.type) { - case 'notification': { + case "notification": { // Ignore notifications from instances the user has muted - if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return; + if ( + isUserFromMutedInstance( + data.body, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; if (data.body.userId && this.muting.has(data.body.userId)) return; break; } - case 'mention': { - if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return; + case "mention": { + if ( + isInstanceMuted( + data.body, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; if (this.muting.has(data.body.userId)) return; break; 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 b930785d2..8165172d7 100644 --- a/packages/backend/src/server/api/stream/channels/messaging-index.ts +++ b/packages/backend/src/server/api/stream/channels/messaging-index.ts @@ -1,13 +1,13 @@ -import Channel from '../channel.js'; +import Channel from "../channel.js"; export default class extends Channel { - public readonly chName = 'messagingIndex'; + public readonly chName = "messagingIndex"; public static shouldShare = true; public static requireCredential = true; public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user!.id}`, (data) => { this.send(data); }); } diff --git a/packages/backend/src/server/api/stream/channels/messaging.ts b/packages/backend/src/server/api/stream/channels/messaging.ts index 877d44c38..3ce99400f 100644 --- a/packages/backend/src/server/api/stream/channels/messaging.ts +++ b/packages/backend/src/server/api/stream/channels/messaging.ts @@ -1,23 +1,29 @@ -import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from '../../common/read-messaging-message.js'; -import Channel from '../channel.js'; -import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index.js'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { StreamMessages } from '../types.js'; +import { + readUserMessagingMessage, + readGroupMessagingMessage, + deliverReadActivity, +} from "../../common/read-messaging-message.js"; +import Channel from "../channel.js"; +import { UserGroupJoinings, Users, MessagingMessages } from "@/models/index.js"; +import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import type { StreamMessages } from "../types.js"; export default class extends Channel { - public readonly chName = 'messaging'; + public readonly chName = "messaging"; public static shouldShare = false; public static requireCredential = true; private otherpartyId: string | null; private otherparty: User | null; private groupId: string | null; - private subCh: `messagingStream:${User['id']}-${User['id']}` | `messagingStream:${UserGroup['id']}`; - private typers: Record = {}; + private subCh: + | `messagingStream:${User["id"]}-${User["id"]}` + | `messagingStream:${UserGroup["id"]}`; + private typers: Record = {}; private emitTypersIntervalId: ReturnType; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onEvent = this.onEvent.bind(this); this.onMessage = this.onMessage.bind(this); @@ -26,7 +32,9 @@ export default class extends Channel { public async init(params: any) { this.otherpartyId = params.otherparty; - this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({ id: this.otherpartyId }) : null; + this.otherparty = this.otherpartyId + ? await Users.findOneByOrFail({ id: this.otherpartyId }) + : null; this.groupId = params.group; // Check joining @@ -51,8 +59,12 @@ export default class extends Channel { this.subscriber.on(this.subCh, this.onEvent); } - private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { - if (data.type === 'typing') { + private onEvent( + data: + | StreamMessages["messaging"]["payload"] + | StreamMessages["groupMessaging"]["payload"], + ) { + if (data.type === "typing") { const id = data.body; const begin = this.typers[id] == null; this.typers[id] = new Date(); @@ -66,14 +78,22 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { - case 'read': + case "read": if (this.otherpartyId) { readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]); // リモートユーザーからのメッセージだったら既読配信 - if (Users.isLocalUser(this.user!) && Users.isRemoteUser(this.otherparty!)) { - MessagingMessages.findOneBy({ id: body.id }).then(message => { - if (message) deliverReadActivity(this.user as ILocalUser, this.otherparty as IRemoteUser, message); + if ( + Users.isLocalUser(this.user!) && + Users.isRemoteUser(this.otherparty!) + ) { + MessagingMessages.findOneBy({ id: body.id }).then((message) => { + if (message) + deliverReadActivity( + this.user as ILocalUser, + this.otherparty as IRemoteUser, + message, + ); }); } } else if (this.groupId) { @@ -88,13 +108,15 @@ export default class extends Channel { // Remove not typing users for (const [userId, date] of Object.entries(this.typers)) { - if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; + if (now.getTime() - date.getTime() > 5000) this.typers[userId] = undefined; } - const users = await Users.packMany(Object.keys(this.typers), null, { detail: false }); + const users = await Users.packMany(Object.keys(this.typers), null, { + detail: false, + }); this.send({ - type: 'typers', + type: "typers", body: users, }); } 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 b67600474..a5a93c332 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,34 +1,34 @@ -import Xev from 'xev'; -import Channel from '../channel.js'; +import Xev from "xev"; +import Channel from "../channel.js"; const ev = new Xev(); export default class extends Channel { - public readonly chName = 'queueStats'; + public readonly chName = "queueStats"; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onStats = this.onStats.bind(this); this.onMessage = this.onMessage.bind(this); } public async init(params: any) { - ev.addListener('queueStats', this.onStats); + ev.addListener("queueStats", this.onStats); } private onStats(stats: any) { - this.send('stats', stats); + this.send("stats", stats); } public onMessage(type: string, body: any) { switch (type) { - case 'requestLog': - ev.once(`queueStatsLog:${body.id}`, statsLog => { - this.send('statsLog', statsLog); + case "requestLog": + ev.once(`queueStatsLog:${body.id}`, (statsLog) => { + this.send("statsLog", statsLog); }); - ev.emit('requestQueueStatsLog', { + ev.emit("requestQueueStatsLog", { id: body.id, length: body.length, }); @@ -37,6 +37,6 @@ export default class extends Channel { } public dispose() { - ev.removeListener('queueStats', this.onStats); + ev.removeListener("queueStats", this.onStats); } } diff --git a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts index c0625aec8..a2a03fca1 100644 --- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts @@ -1,46 +1,67 @@ -import Channel from '../channel.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import { isInstanceMuted } from "@/misc/is-instance-muted.js"; +import type { Packed } from "@/misc/schema.js"; export default class extends Channel { - public readonly chName = 'recommendedTimeline'; + public readonly chName = "recommendedTimeline"; public static shouldShare = true; public static requireCredential = true; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { const meta = await fetchMeta(); - if (meta.disableRecommendedTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; + if ( + meta.disableRecommendedTimeline && + !this.user!.isAdmin && + !this.user!.isModerator + ) + return; // Subscribe events - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または // チャンネルの投稿ではなく、全体公開のローカルの投稿 または // フォローしているチャンネルの投稿 の場合だけ const meta = await fetchMeta(); - if (!( - ((note.user.host != null && meta.recommendedInstances.includes(note.user.host)) && note.visibility === 'public') - )) return; + if ( + !( + note.user.host != null && + meta.recommendedInstances.includes(note.user.host) && + note.visibility === "public" + ) + ) + return; // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; + if ( + isInstanceMuted( + note, + new Set(this.userProfile?.mutedInstances ?? []), + ) + ) + return; // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; + if ( + reply.userId !== this.user!.id && + note.userId !== this.user!.id && + reply.userId !== note.userId + ) + return; } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -53,15 +74,19 @@ export default class extends Channel { // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる - if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; + if ( + this.userProfile && + (await checkWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; this.connection.cacheNote(note); - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); } } 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 db75a6fa3..58659138d 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,34 +1,34 @@ -import Xev from 'xev'; -import Channel from '../channel.js'; +import Xev from "xev"; +import Channel from "../channel.js"; const ev = new Xev(); export default class extends Channel { - public readonly chName = 'serverStats'; + public readonly chName = "serverStats"; public static shouldShare = true; public static requireCredential = false; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.onStats = this.onStats.bind(this); this.onMessage = this.onMessage.bind(this); } public async init(params: any) { - ev.addListener('serverStats', this.onStats); + ev.addListener("serverStats", this.onStats); } private onStats(stats: any) { - this.send('stats', stats); + this.send("stats", stats); } public onMessage(type: string, body: any) { switch (type) { - case 'requestLog': - ev.once(`serverStatsLog:${body.id}`, statsLog => { - this.send('statsLog', statsLog); + case "requestLog": + ev.once(`serverStatsLog:${body.id}`, (statsLog) => { + this.send("statsLog", statsLog); }); - ev.emit('requestServerStatsLog', { + ev.emit("requestServerStatsLog", { id: body.id, length: body.length, }); @@ -37,6 +37,6 @@ export default class extends Channel { } public dispose() { - ev.removeListener('serverStats', this.onStats); + ev.removeListener("serverStats", this.onStats); } } 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 9b2476148..f63776c9e 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,18 +1,18 @@ -import Channel from '../channel.js'; -import { UserListJoinings, UserLists } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { Packed } from '@/misc/schema.js'; +import Channel from "../channel.js"; +import { UserListJoinings, UserLists } 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"; export default class extends Channel { - public readonly chName = 'userList'; + public readonly chName = "userList"; public static shouldShare = false; public static requireCredential = false; private listId: string; - public listUsers: User['id'][] = []; + public listUsers: User["id"][] = []; private listUsersClock: NodeJS.Timer; - constructor(id: string, connection: Channel['connection']) { + constructor(id: string, connection: Channel["connection"]) { super(id, connection); this.updateListUsers = this.updateListUsers.bind(this); this.onNote = this.withPackedNote(this.onNote.bind(this)); @@ -31,7 +31,7 @@ export default class extends Channel { // Subscribe stream this.subscriber.on(`userListStream:${this.listId}`, this.send); - this.subscriber.on('notesStream', this.onNote); + this.subscriber.on("notesStream", this.onNote); this.updateListUsers(); this.listUsersClock = setInterval(this.updateListUsers, 5000); @@ -42,13 +42,13 @@ export default class extends Channel { where: { userListId: this.listId, }, - select: ['userId'], + select: ["userId"], }); - this.listUsers = users.map(x => x.userId); + this.listUsers = users.map((x) => x.userId); } - private async onNote(note: Packed<'Note'>) { + private async onNote(note: Packed<"Note">) { if (!this.listUsers.includes(note.userId)) return; // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する @@ -56,13 +56,13 @@ export default class extends Channel { // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する if (isUserRelated(note, this.blocking)) return; - this.send('note', note); + this.send("note", note); } public dispose() { // Unsubscribe events this.subscriber.off(`userListStream:${this.listId}`, this.send); - this.subscriber.off('notesStream', this.onNote); + this.subscriber.off("notesStream", this.onNote); clearInterval(this.listUsersClock); } diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 2d23145f1..9675d184c 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,18 +1,29 @@ -import { EventEmitter } from 'events'; -import * as websocket from 'websocket'; -import readNote from '@/services/note/read.js'; -import { User } from '@/models/entities/user.js'; -import { Channel as ChannelModel } from '@/models/entities/channel.js'; -import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { Packed } from '@/misc/schema.js'; -import { readNotification } from '../common/read-notification.js'; -import channels from './channels/index.js'; -import Channel from './channel.js'; -import { StreamEventEmitter, StreamMessages } from './types.js'; +import type { EventEmitter } from "events"; +import type * as websocket from "websocket"; +import readNote from "@/services/note/read.js"; +import type { User } from "@/models/entities/user.js"; +import type { Channel as ChannelModel } from "@/models/entities/channel.js"; +import { + Users, + Followings, + Mutings, + UserProfiles, + ChannelFollowings, + Blockings, +} from "@/models/index.js"; +import type { AccessToken } from "@/models/entities/access-token.js"; +import type { UserProfile } from "@/models/entities/user-profile.js"; +import { + publishChannelStream, + publishGroupMessagingStream, + publishMessagingStream, +} from "@/services/stream.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import type { Packed } from "@/misc/schema.js"; +import { readNotification } from "../common/read-notification.js"; +import channels from "./channels/index.js"; +import type Channel from "./channel.js"; +import type { StreamEventEmitter, StreamMessages } from "./types.js"; /** * Main stream connection @@ -20,16 +31,16 @@ import { StreamEventEmitter, StreamMessages } from './types.js'; export default class Connection { public user?: User; public userProfile?: UserProfile | null; - public following: Set = new Set(); - public muting: Set = new Set(); - public blocking: Set = new Set(); // "被"blocking - public followingChannels: Set = new Set(); + public following: Set = new Set(); + public muting: Set = new Set(); + public blocking: Set = new Set(); // "被"blocking + public followingChannels: Set = new Set(); public token?: AccessToken; private wsConnection: websocket.connection; public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; - private cachedNotes: Packed<'Note'>[] = []; + private cachedNotes: Packed<"Note">[] = []; constructor( wsConnection: websocket.connection, @@ -47,9 +58,9 @@ export default class Connection { this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this); this.onBroadcastMessage = this.onBroadcastMessage.bind(this); - this.wsConnection.on('message', this.onWsConnectionMessage); + this.wsConnection.on("message", this.onWsConnectionMessage); - this.subscriber.on('broadcast', data => { + this.subscriber.on("broadcast", (data) => { this.onBroadcastMessage(data); }); @@ -64,39 +75,40 @@ export default class Connection { } } - private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう + private onUserEvent(data: StreamMessages["user"]["payload"]) { + // { type, body }と展開するとそれぞれ型が分離してしまう switch (data.type) { - case 'follow': + case "follow": this.following.add(data.body.id); break; - case 'unfollow': + case "unfollow": this.following.delete(data.body.id); break; - case 'mute': + case "mute": this.muting.add(data.body.id); break; - case 'unmute': + case "unmute": this.muting.delete(data.body.id); break; - // TODO: block events + // TODO: block events - case 'followChannel': + case "followChannel": this.followingChannels.add(data.body.id); break; - case 'unfollowChannel': + case "unfollowChannel": this.followingChannels.delete(data.body.id); break; - case 'updateUserProfile': + case "updateUserProfile": this.userProfile = data.body; break; - case 'terminate': + case "terminate": this.wsConnection.close(); this.dispose(); break; @@ -110,7 +122,7 @@ export default class Connection { * クライアントからメッセージ受信時 */ private async onWsConnectionMessage(data: websocket.Message) { - if (data.type !== 'utf8') return; + if (data.type !== "utf8") return; if (data.utf8Data == null) return; let obj: Record; @@ -124,32 +136,57 @@ export default class Connection { const { type, body } = obj; switch (type) { - case 'readNotification': this.onReadNotification(body); break; - case 'subNote': this.onSubscribeNote(body); break; - case 's': this.onSubscribeNote(body); break; // alias - case 'sr': this.onSubscribeNote(body); this.readNote(body); break; - case 'unsubNote': this.onUnsubscribeNote(body); break; - case 'un': this.onUnsubscribeNote(body); break; // alias - case 'connect': this.onChannelConnectRequested(body); break; - case 'disconnect': this.onChannelDisconnectRequested(body); break; - case 'channel': this.onChannelMessageRequested(body); break; - case 'ch': this.onChannelMessageRequested(body); break; // alias + case "readNotification": + this.onReadNotification(body); + break; + case "subNote": + this.onSubscribeNote(body); + break; + case "s": + this.onSubscribeNote(body); + break; // alias + case "sr": + this.onSubscribeNote(body); + this.readNote(body); + break; + case "unsubNote": + this.onUnsubscribeNote(body); + break; + case "un": + this.onUnsubscribeNote(body); + break; // alias + case "connect": + this.onChannelConnectRequested(body); + break; + case "disconnect": + this.onChannelDisconnectRequested(body); + break; + case "channel": + this.onChannelMessageRequested(body); + break; + case "ch": + this.onChannelMessageRequested(body); + break; // alias // 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、 // クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別 // なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。 - case 'typingOnChannel': this.typingOnChannel(body.channel); break; - case 'typingOnMessaging': this.typingOnMessaging(body); break; + case "typingOnChannel": + this.typingOnChannel(body.channel); + break; + case "typingOnMessaging": + this.typingOnMessaging(body); + break; } } - private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) { + private onBroadcastMessage(data: StreamMessages["broadcast"]["payload"]) { this.sendMessageToWs(data.type, data.body); } - public cacheNote(note: Packed<'Note'>) { - const add = (note: Packed<'Note'>) => { - const existIndex = this.cachedNotes.findIndex(n => n.id === note.id); + public cacheNote(note: Packed<"Note">) { + const add = (note: Packed<"Note">) => { + const existIndex = this.cachedNotes.findIndex((n) => n.id === note.id); if (existIndex > -1) { this.cachedNotes[existIndex] = note; return; @@ -169,10 +206,10 @@ export default class Connection { private readNote(body: any) { const id = body.id; - const note = this.cachedNotes.find(n => n.id === id); + const note = this.cachedNotes.find((n) => n.id === id); if (note == null) return; - if (this.user && (note.userId !== this.user.id)) { + if (this.user && note.userId !== this.user.id) { readNote(this.user.id, [note], { following: this.following, followingChannels: this.followingChannels, @@ -210,13 +247,13 @@ export default class Connection { this.subscribingNotes[payload.id]--; if (this.subscribingNotes[payload.id] <= 0) { - delete this.subscribingNotes[payload.id]; + this.subscribingNotes[payload.id] = undefined; this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage); } } - private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { - this.sendMessageToWs('noteUpdated', { + private async onNoteStreamMessage(data: StreamMessages["note"]["payload"]) { + this.sendMessageToWs("noteUpdated", { id: data.body.id, type: data.type, body: data.body.body, @@ -243,22 +280,32 @@ export default class Connection { * クライアントにメッセージ送信 */ public sendMessageToWs(type: string, payload: any) { - this.wsConnection.send(JSON.stringify({ - type: type, - body: payload, - })); + this.wsConnection.send( + JSON.stringify({ + type: type, + body: payload, + }), + ); } /** * チャンネルに接続 */ - public connectChannel(id: string, params: any, channel: string, pong = false) { + public connectChannel( + id: string, + params: any, + channel: string, + pong = false, + ) { if ((channels as any)[channel].requireCredential && this.user == null) { return; } // 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視 - if ((channels as any)[channel].shouldShare && this.channels.some(c => c.chName === channel)) { + if ( + (channels as any)[channel].shouldShare && + this.channels.some((c) => c.chName === channel) + ) { return; } @@ -267,7 +314,7 @@ export default class Connection { ch.init(params); if (pong) { - this.sendMessageToWs('connected', { + this.sendMessageToWs("connected", { id: id, }); } @@ -278,11 +325,11 @@ export default class Connection { * @param id チャンネルコネクションID */ public disconnectChannel(id: string) { - const channel = this.channels.find(c => c.id === id); + const channel = this.channels.find((c) => c.id === id); if (channel) { if (channel.dispose) channel.dispose(); - this.channels = this.channels.filter(c => c.id !== id); + this.channels = this.channels.filter((c) => c.id !== id); } } @@ -291,24 +338,32 @@ export default class Connection { * @param data メッセージ */ private onChannelMessageRequested(data: any) { - const channel = this.channels.find(c => c.id === data.id); - if (channel != null && channel.onMessage != null) { + const channel = this.channels.find((c) => c.id === data.id); + if (channel?.onMessage != null) { channel.onMessage(data.type, data.body); } } - private typingOnChannel(channel: ChannelModel['id']) { + private typingOnChannel(channel: ChannelModel["id"]) { if (this.user) { - publishChannelStream(channel, 'typing', this.user.id); + publishChannelStream(channel, "typing", this.user.id); } } - private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) { + private typingOnMessaging(param: { + partner?: User["id"]; + group?: UserGroup["id"]; + }) { if (this.user) { if (param.partner) { - publishMessagingStream(param.partner, this.user.id, 'typing', this.user.id); + publishMessagingStream( + param.partner, + this.user.id, + "typing", + this.user.id, + ); } else if (param.group) { - publishGroupMessagingStream(param.group, 'typing', this.user.id); + publishGroupMessagingStream(param.group, "typing", this.user.id); } } } @@ -318,10 +373,10 @@ export default class Connection { where: { followerId: this.user!.id, }, - select: ['followeeId'], + select: ["followeeId"], }); - this.following = new Set(followings.map(x => x.followeeId)); + this.following = new Set(followings.map((x) => x.followeeId)); } private async updateMuting() { @@ -329,21 +384,22 @@ export default class Connection { where: { muterId: this.user!.id, }, - select: ['muteeId'], + select: ["muteeId"], }); - this.muting = new Set(mutings.map(x => x.muteeId)); + this.muting = new Set(mutings.map((x) => x.muteeId)); } - private async updateBlocking() { // ここでいうBlockingは被Blockingの意 + private async updateBlocking() { + // ここでいうBlockingは被Blockingの意 const blockings = await Blockings.find({ where: { blockeeId: this.user!.id, }, - select: ['blockerId'], + select: ["blockerId"], }); - this.blocking = new Set(blockings.map(x => x.blockerId)); + this.blocking = new Set(blockings.map((x) => x.blockerId)); } private async updateFollowingChannels() { @@ -351,10 +407,12 @@ export default class Connection { where: { followerId: this.user!.id, }, - select: ['followeeId'], + select: ["followeeId"], }); - this.followingChannels = new Set(followings.map(x => x.followeeId)); + this.followingChannels = new Set( + followings.map((x) => x.followeeId), + ); } private async updateUserProfile() { @@ -367,7 +425,7 @@ export default class Connection { * ストリームが切れたとき */ public dispose() { - for (const c of this.channels.filter(c => c.dispose)) { + for (const c of this.channels.filter((c) => c.dispose)) { if (c.dispose) c.dispose(); } } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 8050d8a1d..837f42c87 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -1,29 +1,39 @@ -import { EventEmitter } from 'events'; -import Emitter from 'strict-event-emitter-types'; -import { Channel } from '@/models/entities/channel.js'; -import { User } from '@/models/entities/user.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { Note } from '@/models/entities/note.js'; -import { Antenna } from '@/models/entities/antenna.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFolder } from '@/models/entities/drive-folder.js'; -import { Emoji } from '@/models/entities/emoji.js'; -import { UserList } from '@/models/entities/user-list.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { AbuseUserReport } from '@/models/entities/abuse-user-report.js'; -import { Signin } from '@/models/entities/signin.js'; -import { Page } from '@/models/entities/page.js'; -import { Packed } from '@/misc/schema.js'; -import { Webhook } from '@/models/entities/webhook'; +import type { EventEmitter } from "events"; +import type Emitter from "strict-event-emitter-types"; +import type { Channel } from "@/models/entities/channel.js"; +import type { User } from "@/models/entities/user.js"; +import type { UserProfile } from "@/models/entities/user-profile.js"; +import type { Note } from "@/models/entities/note.js"; +import type { Antenna } from "@/models/entities/antenna.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { DriveFolder } from "@/models/entities/drive-folder.js"; +import { Emoji } from "@/models/entities/emoji.js"; +import type { UserList } from "@/models/entities/user-list.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import type { AbuseUserReport } from "@/models/entities/abuse-user-report.js"; +import type { Signin } from "@/models/entities/signin.js"; +import type { Page } from "@/models/entities/page.js"; +import type { Packed } from "@/misc/schema.js"; +import type { Webhook } from "@/models/entities/webhook"; //#region Stream type-body definitions export interface InternalStreamTypes { - userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; }; - userChangeSilencedState: { id: User['id']; isSilenced: User['isSilenced']; }; - userChangeModeratorState: { id: User['id']; isModerator: User['isModerator']; }; - userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; }; - remoteUserUpdated: { id: User['id']; }; + userChangeSuspendedState: { + id: User["id"]; + isSuspended: User["isSuspended"]; + }; + userChangeSilencedState: { id: User["id"]; isSilenced: User["isSilenced"] }; + userChangeModeratorState: { + id: User["id"]; + isModerator: User["isModerator"]; + }; + userTokenRegenerated: { + id: User["id"]; + oldToken: User["token"]; + newToken: User["token"]; + }; + remoteUserUpdated: { id: User["id"] }; webhookCreated: Webhook; webhookDeleted: Webhook; webhookUpdated: Webhook; @@ -34,7 +44,7 @@ export interface InternalStreamTypes { export interface BroadcastTypes { emojiAdded: { - emoji: Packed<'Emoji'>; + emoji: Packed<"Emoji">; }; } @@ -45,45 +55,45 @@ export interface UserStreamTypes { updateUserProfile: UserProfile; mute: User; unmute: User; - follow: Packed<'UserDetailedNotMe'>; - unfollow: Packed<'User'>; - userAdded: Packed<'User'>; + follow: Packed<"UserDetailedNotMe">; + unfollow: Packed<"User">; + userAdded: Packed<"User">; } export interface MainStreamTypes { - notification: Packed<'Notification'>; - mention: Packed<'Note'>; - reply: Packed<'Note'>; - renote: Packed<'Note'>; - follow: Packed<'UserDetailedNotMe'>; - followed: Packed<'User'>; - unfollow: Packed<'User'>; - meUpdated: Packed<'User'>; + notification: Packed<"Notification">; + mention: Packed<"Note">; + reply: Packed<"Note">; + renote: Packed<"Note">; + follow: Packed<"UserDetailedNotMe">; + followed: Packed<"User">; + unfollow: Packed<"User">; + meUpdated: Packed<"User">; pageEvent: { - pageId: Page['id']; + pageId: Page["id"]; event: string; var: any; - userId: User['id']; - user: Packed<'User'>; + userId: User["id"]; + user: Packed<"User">; }; urlUploadFinished: { marker?: string | null; - file: Packed<'DriveFile'>; + file: Packed<"DriveFile">; }; readAllNotifications: undefined; - unreadNotification: Packed<'Notification'>; - unreadMention: Note['id']; + unreadNotification: Packed<"Notification">; + unreadMention: Note["id"]; readAllUnreadMentions: undefined; - unreadSpecifiedNote: Note['id']; + unreadSpecifiedNote: Note["id"]; readAllUnreadSpecifiedNotes: undefined; readAllMessagingMessages: undefined; - messagingMessage: Packed<'MessagingMessage'>; - unreadMessagingMessage: Packed<'MessagingMessage'>; + messagingMessage: Packed<"MessagingMessage">; + unreadMessagingMessage: Packed<"MessagingMessage">; readAllAntennas: undefined; unreadAntenna: Antenna; readAllAnnouncements: undefined; readAllChannels: undefined; - unreadChannel: Note['id']; + unreadChannel: Note["id"]; myTokenRegenerated: undefined; signin: Signin; registryUpdated: { @@ -91,24 +101,24 @@ export interface MainStreamTypes { key: string; value: any | null; }; - driveFileCreated: Packed<'DriveFile'>; + driveFileCreated: Packed<"DriveFile">; readAntenna: Antenna; - receiveFollowRequest: Packed<'User'>; + receiveFollowRequest: Packed<"User">; } export interface DriveStreamTypes { - fileCreated: Packed<'DriveFile'>; - fileDeleted: DriveFile['id']; - fileUpdated: Packed<'DriveFile'>; - folderCreated: Packed<'DriveFolder'>; - folderDeleted: DriveFolder['id']; - folderUpdated: Packed<'DriveFolder'>; + fileCreated: Packed<"DriveFile">; + fileDeleted: DriveFile["id"]; + fileUpdated: Packed<"DriveFile">; + folderCreated: Packed<"DriveFolder">; + folderDeleted: DriveFolder["id"]; + folderUpdated: Packed<"DriveFolder">; } export interface NoteStreamTypes { pollVoted: { choice: number; - userId: User['id']; + userId: User["id"]; }; deleted: { deletedAt: Date; @@ -119,27 +129,27 @@ export interface NoteStreamTypes { name: string; url: string; } | null; - userId: User['id']; + userId: User["id"]; }; unreacted: { reaction: string; - userId: User['id']; + userId: User["id"]; }; } type NoteStreamEventTypes = { [key in keyof NoteStreamTypes]: { - id: Note['id']; + id: Note["id"]; body: NoteStreamTypes[key]; }; }; export interface ChannelStreamTypes { - typing: User['id']; + typing: User["id"]; } export interface UserListStreamTypes { - userAdded: Packed<'User'>; - userRemoved: Packed<'User'>; + userAdded: Packed<"User">; + userRemoved: Packed<"User">; } export interface AntennaStreamTypes { @@ -147,32 +157,32 @@ export interface AntennaStreamTypes { } export interface MessagingStreamTypes { - read: MessagingMessage['id'][]; - typing: User['id']; - message: Packed<'MessagingMessage'>; - deleted: MessagingMessage['id']; + read: MessagingMessage["id"][]; + typing: User["id"]; + message: Packed<"MessagingMessage">; + deleted: MessagingMessage["id"]; } export interface GroupMessagingStreamTypes { read: { - ids: MessagingMessage['id'][]; - userId: User['id']; + ids: MessagingMessage["id"][]; + userId: User["id"]; }; - typing: User['id']; - message: Packed<'MessagingMessage'>; - deleted: MessagingMessage['id']; + typing: User["id"]; + message: Packed<"MessagingMessage">; + deleted: MessagingMessage["id"]; } export interface MessagingIndexStreamTypes { - read: MessagingMessage['id'][]; - message: Packed<'MessagingMessage'>; + read: MessagingMessage["id"][]; + message: Packed<"MessagingMessage">; } export interface AdminStreamTypes { newAbuseUserReport: { - id: AbuseUserReport['id']; - targetUserId: User['id'], - reporterId: User['id'], + id: AbuseUserReport["id"]; + targetUserId: User["id"]; + reporterId: User["id"]; comment: string; }; } @@ -181,80 +191,92 @@ export interface AdminStreamTypes { // 辞書(interface or type)から{ type, body }ユニオンを定義 // https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type // VS Codeの展開を防止するためにEvents型を定義 -type Events = { [K in keyof T]: { type: K; body: T[K]; } }; -type EventUnionFromDictionary< - T extends object, - U = Events -> = U[keyof U]; +type Events = { [K in keyof T]: { type: K; body: T[K] } }; +type EventUnionFromDictionary> = U[keyof U]; // name/messages(spec) pairs dictionary export type StreamMessages = { internal: { - name: 'internal'; + name: "internal"; payload: EventUnionFromDictionary; }; broadcast: { - name: 'broadcast'; + name: "broadcast"; payload: EventUnionFromDictionary; }; user: { - name: `user:${User['id']}`; + name: `user:${User["id"]}`; payload: EventUnionFromDictionary; }; main: { - name: `mainStream:${User['id']}`; + name: `mainStream:${User["id"]}`; payload: EventUnionFromDictionary; }; drive: { - name: `driveStream:${User['id']}`; + name: `driveStream:${User["id"]}`; payload: EventUnionFromDictionary; }; note: { - name: `noteStream:${Note['id']}`; + name: `noteStream:${Note["id"]}`; payload: EventUnionFromDictionary; }; channel: { - name: `channelStream:${Channel['id']}`; + name: `channelStream:${Channel["id"]}`; payload: EventUnionFromDictionary; }; userList: { - name: `userListStream:${UserList['id']}`; + name: `userListStream:${UserList["id"]}`; payload: EventUnionFromDictionary; }; antenna: { - name: `antennaStream:${Antenna['id']}`; + name: `antennaStream:${Antenna["id"]}`; payload: EventUnionFromDictionary; }; messaging: { - name: `messagingStream:${User['id']}-${User['id']}`; + name: `messagingStream:${User["id"]}-${User["id"]}`; payload: EventUnionFromDictionary; }; groupMessaging: { - name: `messagingStream:${UserGroup['id']}`; + name: `messagingStream:${UserGroup["id"]}`; payload: EventUnionFromDictionary; }; messagingIndex: { - name: `messagingIndexStream:${User['id']}`; + name: `messagingIndexStream:${User["id"]}`; payload: EventUnionFromDictionary; }; admin: { - name: `adminStream:${User['id']}`; + name: `adminStream:${User["id"]}`; payload: EventUnionFromDictionary; }; notes: { - name: 'notesStream'; + name: "notesStream"; payload: Note; }; }; // API event definitions // ストリームごとのEmitterの辞書を用意 -type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter void }> }; +type EventEmitterDictionary = { + [x in keyof StreamMessages]: Emitter< + EventEmitter, + { + [y in StreamMessages[x]["name"]]: ( + e: StreamMessages[x]["payload"], + ) => void; + } + >; +}; // 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void + ? I + : never; // Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする -export type StreamEventEmitter = UnionToIntersection; +export type StreamEventEmitter = UnionToIntersection< + EventEmitterDictionary[keyof StreamMessages] +>; // { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる // provide stream channels union -export type StreamChannels = StreamMessages[keyof StreamMessages]['name']; +export type StreamChannels = StreamMessages[keyof StreamMessages]["name"]; diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 7cf365faf..9e84ec307 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -1,12 +1,12 @@ -import * as http from 'node:http'; -import { EventEmitter } from 'events'; -import { ParsedUrlQuery } from 'querystring'; -import * as websocket from 'websocket'; +import type * as http from "node:http"; +import { EventEmitter } from "events"; +import type { ParsedUrlQuery } from "querystring"; +import * as websocket from "websocket"; -import { subscriber as redisClient } from '@/db/redis.js'; -import { Users } from '@/models/index.js'; -import MainStreamConnection from './stream/index.js'; -import authenticate from './authenticate.js'; +import { subscriber as redisClient } from "@/db/redis.js"; +import { Users } from "@/models/index.js"; +import MainStreamConnection from "./stream/index.js"; +import authenticate from "./authenticate.js"; export const initializeStreamingServer = (server: http.Server) => { // Init websocket server @@ -14,15 +14,17 @@ export const initializeStreamingServer = (server: http.Server) => { httpServer: server, }); - ws.on('request', async (request) => { + ws.on("request", async (request) => { const q = request.resourceURL.query as ParsedUrlQuery; - const [user, app] = await authenticate(request.httpRequest.headers.authorization, q.i) - .catch(err => { - request.reject(403, err.message); - return []; - }); - if (typeof user === 'undefined') { + const [user, app] = await authenticate( + request.httpRequest.headers.authorization, + q.i, + ).catch((err) => { + request.reject(403, err.message); + return []; + }); + if (typeof user === "undefined") { return; } @@ -40,31 +42,33 @@ export const initializeStreamingServer = (server: http.Server) => { ev.emit(parsed.channel, parsed.message); } - redisClient.on('message', onRedisMessage); + redisClient.on("message", onRedisMessage); const main = new MainStreamConnection(connection, ev, user, app); - const intervalId = user ? setInterval(() => { - Users.update(user.id, { - lastActiveDate: new Date(), - }); - }, 1000 * 60 * 5) : null; + const intervalId = user + ? setInterval(() => { + Users.update(user.id, { + lastActiveDate: new Date(), + }); + }, 1000 * 60 * 5) + : null; if (user) { Users.update(user.id, { lastActiveDate: new Date(), }); } - connection.once('close', () => { + connection.once("close", () => { ev.removeAllListeners(); main.dispose(); - redisClient.off('message', onRedisMessage); + redisClient.off("message", onRedisMessage); if (intervalId) clearInterval(intervalId); }); - connection.on('message', async (data) => { - if (data.type === 'utf8' && data.utf8Data === 'ping') { - connection.send('pong'); + connection.on("message", async (data) => { + if (data.type === "utf8" && data.utf8Data === "ping") { + connection.send("pong"); } }); }); diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts index 07a493700..26df1de51 100644 --- a/packages/backend/src/server/file/index.ts +++ b/packages/backend/src/server/file/index.ts @@ -2,13 +2,13 @@ * File Server */ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; -import sendDriveFile from './send-drive-file.js'; +import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import Koa from "koa"; +import cors from "@koa/cors"; +import Router from "@koa/router"; +import sendDriveFile from "./send-drive-file.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -17,22 +17,25 @@ const _dirname = dirname(_filename); 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'`); + ctx.set( + "Content-Security-Policy", + `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`, + ); await next(); }); // Init router const router = new Router(); -router.get('/app-default.jpg', ctx => { +router.get("/app-default.jpg", (ctx) => { const file = fs.createReadStream(`${_dirname}/assets/dummy.png`); ctx.body = file; - ctx.set('Content-Type', 'image/jpeg'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.set("Content-Type", "image/jpeg"); + ctx.set("Cache-Control", "max-age=31536000, immutable"); }); -router.get('/:key', sendDriveFile); -router.get('/:key/(.*)', sendDriveFile); +router.get("/:key", sendDriveFile); +router.get("/:key/(.*)", sendDriveFile); // Register router app.use(router.routes()); diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index acfde9cfc..772d19ae6 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -1,47 +1,56 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import Koa from 'koa'; -import send from 'koa-send'; -import rename from 'rename'; -import { serverLogger } from '../index.js'; -import { contentDisposition } from '@/misc/content-disposition.js'; -import { DriveFiles } from '@/models/index.js'; -import { InternalStorage } from '@/services/drive/internal-storage.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { detectType } from '@/misc/get-file-info.js'; -import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js'; -import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js'; -import { StatusError } from '@/misc/fetch.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import type Koa from "koa"; +import send from "koa-send"; +import rename from "rename"; +import { serverLogger } from "../index.js"; +import { contentDisposition } from "@/misc/content-disposition.js"; +import { DriveFiles } from "@/models/index.js"; +import { InternalStorage } from "@/services/drive/internal-storage.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { downloadUrl } from "@/misc/download-url.js"; +import { detectType } from "@/misc/get-file-info.js"; +import { + convertToWebp, + convertToJpeg, + convertToPng, +} from "@/services/drive/image-processor.js"; +import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js"; +import { StatusError } from "@/misc/fetch.js"; +import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const assets = `${_dirname}/../../server/file/assets/`; -const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { - serverLogger.error(e); - ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); -}; +const commonReadableHandlerGenerator = + (ctx: Koa.Context) => (e: Error): void => { + serverLogger.error(e); + ctx.status = 500; + ctx.set("Cache-Control", "max-age=300"); + }; // eslint-disable-next-line import/no-default-export -export default async function(ctx: Koa.Context) { +export default async function (ctx: Koa.Context) { const key = ctx.params.key; // Fetch drive file - const file = await DriveFiles.createQueryBuilder('file') - .where('file.accessKey = :accessKey', { accessKey: key }) - .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) - .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) + const file = await DriveFiles.createQueryBuilder("file") + .where("file.accessKey = :accessKey", { accessKey: key }) + .orWhere("file.thumbnailAccessKey = :thumbnailAccessKey", { + thumbnailAccessKey: key, + }) + .orWhere("file.webpublicAccessKey = :webpublicAccessKey", { + webpublicAccessKey: key, + }) .getOne(); if (file == null) { ctx.status = 404; - ctx.set('Cache-Control', 'max-age=86400'); - await send(ctx as any, '/dummy.png', { root: assets }); + ctx.set("Cache-Control", "max-age=86400"); + await send(ctx as any, "/dummy.png", { root: assets }); return; } @@ -49,7 +58,8 @@ export default async function(ctx: Koa.Context) { const isWebpublic = file.webpublicAccessKey === key; if (!file.storedInternal) { - if (file.isLink && file.uri) { // 期限切れリモートファイル + if (file.isLink && file.uri) { + // 期限切れリモートファイル const [path, cleanup] = await createTemp(); try { @@ -59,15 +69,23 @@ export default async function(ctx: Koa.Context) { const convertFile = async () => { if (isThumbnail) { - if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml', 'image/avif'].includes(mime)) { + if ( + [ + "image/jpeg", + "image/webp", + "image/png", + "image/svg+xml", + "image/avif", + ].includes(mime) + ) { return await convertToWebp(path, 498, 280); - } else if (mime.startsWith('video/')) { + } else if (mime.startsWith("video/")) { return await GenerateVideoThumbnail(path); } } if (isWebpublic) { - if (['image/svg+xml'].includes(mime)) { + if (["image/svg+xml"].includes(mime)) { return await convertToPng(path, 2048, 2048); } } @@ -81,17 +99,22 @@ export default async function(ctx: Koa.Context) { 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'); + ctx.set( + "Content-Type", + FILE_TYPE_BROWSERSAFE.includes(image.type) + ? image.type + : "application/octet-stream", + ); + ctx.set("Cache-Control", "max-age=31536000, immutable"); } catch (e) { serverLogger.error(`${e}`); if (e instanceof StatusError && e.isClientError) { ctx.status = e.statusCode; - ctx.set('Cache-Control', 'max-age=86400'); + ctx.set("Cache-Control", "max-age=86400"); } else { ctx.status = 500; - ctx.set('Cache-Control', 'max-age=300'); + ctx.set("Cache-Control", "max-age=300"); } } finally { cleanup(); @@ -100,27 +123,35 @@ export default async function(ctx: Koa.Context) { } ctx.status = 204; - ctx.set('Cache-Control', 'max-age=86400'); + ctx.set("Cache-Control", "max-age=86400"); return; } if (isThumbnail || isWebpublic) { const { mime, ext } = await detectType(InternalStorage.resolvePath(key)); const filename = rename(file.name, { - suffix: isThumbnail ? '-thumb' : '-web', + suffix: isThumbnail ? "-thumb" : "-web", extname: ext ? `.${ext}` : undefined, }).toString(); ctx.body = InternalStorage.read(key); - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', filename)); + ctx.set( + "Content-Type", + FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : "application/octet-stream", + ); + ctx.set("Cache-Control", "max-age=31536000, immutable"); + ctx.set("Content-Disposition", contentDisposition("inline", filename)); } else { const readable = InternalStorage.read(file.accessKey!); - readable.on('error', commonReadableHandlerGenerator(ctx)); + readable.on("error", commonReadableHandlerGenerator(ctx)); ctx.body = readable; - ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); - ctx.set('Content-Disposition', contentDisposition('inline', file.name)); + ctx.set( + "Content-Type", + FILE_TYPE_BROWSERSAFE.includes(file.type) + ? file.type + : "application/octet-stream", + ); + ctx.set("Cache-Control", "max-age=31536000, immutable"); + ctx.set("Content-Disposition", contentDisposition("inline", file.name)); } } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index ae236ce34..4d4b81d7a 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -2,65 +2,69 @@ * Core Server */ -import cluster from 'node:cluster'; -import * as fs from 'node:fs'; -import * as http from 'node:http'; -import Koa from 'koa'; -import Router from '@koa/router'; -import mount from 'koa-mount'; -import koaLogger from 'koa-logger'; -import * as slow from 'koa-slow'; +import cluster from "node:cluster"; +import * as fs from "node:fs"; +import * as http from "node:http"; +import Koa from "koa"; +import Router from "@koa/router"; +import mount from "koa-mount"; +import koaLogger from "koa-logger"; +import * as slow from "koa-slow"; -import { IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import Logger from '@/services/logger.js'; -import { UserProfiles, Users } from '@/models/index.js'; -import { genIdenticon } from '@/misc/gen-identicon.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { publishMainStream } from '@/services/stream.js'; -import * as Acct from '@/misc/acct.js'; -import { envOption } from '@/env.js'; -import activityPub from './activitypub.js'; -import nodeinfo from './nodeinfo.js'; -import wellKnown from './well-known.js'; -import apiServer from './api/index.js'; -import fileServer from './file/index.js'; -import proxyServer from './proxy/index.js'; -import webServer from './web/index.js'; -import { initializeStreamingServer } from './api/streaming.js'; +import { IsNull } from "typeorm"; +import config from "@/config/index.js"; +import Logger from "@/services/logger.js"; +import { UserProfiles, Users } from "@/models/index.js"; +import { genIdenticon } from "@/misc/gen-identicon.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { publishMainStream } from "@/services/stream.js"; +import * as Acct from "@/misc/acct.js"; +import { envOption } from "@/env.js"; +import activityPub from "./activitypub.js"; +import nodeinfo from "./nodeinfo.js"; +import wellKnown from "./well-known.js"; +import apiServer from "./api/index.js"; +import fileServer from "./file/index.js"; +import proxyServer from "./proxy/index.js"; +import webServer from "./web/index.js"; +import { initializeStreamingServer } from "./api/streaming.js"; -export const serverLogger = new Logger('server', 'gray', false); +export const serverLogger = new Logger("server", "gray", false); // Init app const app = new Koa(); app.proxy = true; -if (!['production', 'test'].includes(process.env.NODE_ENV || '')) { +if (!["production", "test"].includes(process.env.NODE_ENV || "")) { // Logger - app.use(koaLogger(str => { - serverLogger.info(str); - })); + app.use( + koaLogger((str) => { + serverLogger.info(str); + }), + ); // Delay if (envOption.slow) { - app.use(slow({ - delay: 3000, - })); + app.use( + slow({ + delay: 3000, + }), + ); } } // HSTS // 6months (15552000sec) -if (config.url.startsWith('https') && !config.disableHsts) { +if (config.url.startsWith("https") && !config.disableHsts) { app.use(async (ctx, next) => { - ctx.set('strict-transport-security', 'max-age=15552000; preload'); + ctx.set("strict-transport-security", "max-age=15552000; preload"); await next(); }); } -app.use(mount('/api', apiServer)); -app.use(mount('/files', fileServer)); -app.use(mount('/proxy', proxyServer)); +app.use(mount("/api", apiServer)); +app.use(mount("/files", fileServer)); +app.use(mount("/proxy", proxyServer)); // Init router const router = new Router(); @@ -70,49 +74,60 @@ router.use(activityPub.routes()); router.use(nodeinfo.routes()); router.use(wellKnown.routes()); -router.get('/avatar/@:acct', async ctx => { +router.get("/avatar/@:acct", async (ctx) => { const { username, host } = Acct.parse(ctx.params.acct); const user = await Users.findOne({ where: { usernameLower: username.toLowerCase(), - host: (host == null) || (host === config.host) ? IsNull() : host, + host: host == null || host === config.host ? IsNull() : host, isSuspended: false, }, - relations: ['avatar'], + relations: ["avatar"], }); if (user) { ctx.redirect(Users.getAvatarUrlSync(user)); } else { - ctx.redirect('/static-assets/user-unknown.png'); + ctx.redirect("/static-assets/user-unknown.png"); } }); -router.get('/identicon/:x', async ctx => { +router.get("/identicon/:x", async (ctx) => { const [temp, cleanup] = await createTemp(); await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); - ctx.set('Content-Type', 'image/png'); - ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); + ctx.set("Content-Type", "image/png"); + ctx.body = fs.createReadStream(temp).on("close", () => cleanup()); }); -router.get('/verify-email/:code', async ctx => { +router.get("/verify-email/:code", async (ctx) => { const profile = await UserProfiles.findOneBy({ emailVerifyCode: ctx.params.code, }); if (profile != null) { - ctx.body = 'Verify succeeded!'; + ctx.body = "Verify succeeded!"; ctx.status = 200; - await UserProfiles.update({ userId: profile.userId }, { - emailVerified: true, - emailVerifyCode: null, - }); + await UserProfiles.update( + { userId: profile.userId }, + { + emailVerified: true, + emailVerifyCode: null, + }, + ); - publishMainStream(profile.userId, 'meUpdated', await Users.pack(profile.userId, { id: profile.userId }, { - detail: true, - includeSecrets: true, - })); + publishMainStream( + profile.userId, + "meUpdated", + await Users.pack( + profile.userId, + { id: profile.userId }, + { + detail: true, + includeSecrets: true, + }, + ), + ); } else { ctx.status = 404; } @@ -138,32 +153,37 @@ export const startServer = () => { return server; }; -export default () => new Promise(resolve => { - const server = createServer(); +export default () => + new Promise((resolve) => { + const server = createServer(); - initializeStreamingServer(server); + initializeStreamingServer(server); - server.on('error', e => { - switch ((e as any).code) { - case 'EACCES': - serverLogger.error(`You do not have permission to listen on port ${config.port}.`); - break; - case 'EADDRINUSE': - serverLogger.error(`Port ${config.port} is already in use by another process.`); - break; - default: - serverLogger.error(e); - break; - } + server.on("error", (e) => { + switch ((e as any).code) { + case "EACCES": + serverLogger.error( + `You do not have permission to listen on port ${config.port}.`, + ); + break; + case "EADDRINUSE": + serverLogger.error( + `Port ${config.port} is already in use by another process.`, + ); + break; + default: + serverLogger.error(e); + break; + } - if (cluster.isWorker) { - process.send!('listenFailed'); - } else { - // disableClustering - process.exit(1); - } + if (cluster.isWorker) { + process.send!("listenFailed"); + } else { + // disableClustering + process.exit(1); + } + }); + + // @ts-ignore + server.listen(config.port, resolve); }); - - // @ts-ignore - server.listen(config.port, resolve); -}); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index d0d8c14ce..a7fa0de4c 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -1,54 +1,64 @@ -import Router from '@koa/router'; -import config from '@/config/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Users, Notes } from '@/models/index.js'; -import { IsNull, MoreThan } from 'typeorm'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { Cache } from '@/misc/cache.js'; +import Router from "@koa/router"; +import config from "@/config/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { Users, Notes } from "@/models/index.js"; +import { IsNull, MoreThan } from "typeorm"; +import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import { Cache } from "@/misc/cache.js"; const router = new Router(); -const nodeinfo2_1path = '/nodeinfo/2.1'; -const nodeinfo2_0path = '/nodeinfo/2.0'; +const nodeinfo2_1path = "/nodeinfo/2.1"; +const nodeinfo2_0path = "/nodeinfo/2.0"; // to cleo: leave this http or bonks -export const links = [{ - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', - href: config.url + nodeinfo2_1path -}, { - rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', - href: config.url + nodeinfo2_0path, -}]; +export const links = [ + { + rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", + href: config.url + nodeinfo2_1path, + }, + { + rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", + href: config.url + nodeinfo2_0path, + }, +]; const nodeinfo2 = async () => { const now = Date.now(); - const [ - meta, - total, - activeHalfyear, - activeMonth, - localPosts, - ] = await Promise.all([ - fetchMeta(true), - Users.count({ where: { host: IsNull() } }), - Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }), - Users.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }), - Notes.count({ where: { userHost: IsNull() } }), - ]); + const [meta, total, activeHalfyear, activeMonth, localPosts] = + await Promise.all([ + fetchMeta(true), + Users.count({ where: { host: IsNull() } }), + Users.count({ + where: { + host: IsNull(), + lastActiveDate: MoreThan(new Date(now - 15552000000)), + }, + }), + Users.count({ + where: { + host: IsNull(), + lastActiveDate: MoreThan(new Date(now - 2592000000)), + }, + }), + Notes.count({ where: { userHost: IsNull() } }), + ]); - const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; + const proxyAccount = meta.proxyAccountId + ? await Users.pack(meta.proxyAccountId).catch(() => null) + : null; return { software: { - name: 'calckey', + name: "calckey", version: config.version, repository: meta.repositoryUrl, - homepage: 'https://calckey.cloud', + homepage: "https://calckey.cloud", }, - protocols: ['activitypub'], + protocols: ["activitypub"], services: { inbound: [] as string[], - outbound: ['atom1.0', 'rss2.0'], + outbound: ["atom1.0", "rss2.0"], }, openRegistrations: !meta.disableRegistration, usage: { @@ -81,28 +91,28 @@ const nodeinfo2 = async () => { enableEmail: meta.enableEmail, enableServiceWorker: meta.enableServiceWorker, proxyAccountName: proxyAccount ? proxyAccount.username : null, - themeColor: meta.themeColor || '#31748f', + themeColor: meta.themeColor || "#31748f", }, }; }; const cache = new Cache>>(1000 * 60 * 10); -router.get(nodeinfo2_1path, async ctx => { +router.get(nodeinfo2_1path, async (ctx) => { const base = await cache.fetch(null, () => nodeinfo2()); - ctx.body = { version: '2.1', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); + ctx.body = { version: "2.1", ...base }; + ctx.set("Cache-Control", "public, max-age=600"); }); -router.get(nodeinfo2_0path, async ctx => { +router.get(nodeinfo2_0path, async (ctx) => { const base = await cache.fetch(null, () => nodeinfo2()); // @ts-ignore - delete base.software.repository; + base.software.repository = undefined; - ctx.body = { version: '2.0', ...base }; - ctx.set('Cache-Control', 'public, max-age=600'); + ctx.body = { version: "2.0", ...base }; + ctx.set("Cache-Control", "public, max-age=600"); }); export default router; diff --git a/packages/backend/src/server/proxy/index.ts b/packages/backend/src/server/proxy/index.ts index 506ba10ef..004b3779f 100644 --- a/packages/backend/src/server/proxy/index.ts +++ b/packages/backend/src/server/proxy/index.ts @@ -2,23 +2,26 @@ * Media Proxy */ -import Koa from 'koa'; -import cors from '@koa/cors'; -import Router from '@koa/router'; -import { proxyMedia } from './proxy-media.js'; +import Koa from "koa"; +import cors from "@koa/cors"; +import Router from "@koa/router"; +import { proxyMedia } from "./proxy-media.js"; // Init app const app = new Koa(); app.use(cors()); app.use(async (ctx, next) => { - ctx.set('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`); + ctx.set( + "Content-Security-Policy", + `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`, + ); await next(); }); // Init router const router = new Router(); -router.get('/:url*', proxyMedia); +router.get("/:url*", proxyMedia); // Register router app.use(router.routes()); diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index ca036e8fd..08ce0fba7 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -1,20 +1,21 @@ -import * as fs from 'node:fs'; -import Koa from 'koa'; -import sharp from 'sharp'; -import { IImage, convertToWebp } from '@/services/drive/image-processor.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { detectType } from '@/misc/get-file-info.js'; -import { StatusError } from '@/misc/fetch.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; -import { serverLogger } from '../index.js'; -import { isMimeImage } from '@/misc/is-mime-image.js'; +import * as fs from "node:fs"; +import type Koa from "koa"; +import sharp from "sharp"; +import type { IImage } from "@/services/drive/image-processor.js"; +import { convertToWebp } from "@/services/drive/image-processor.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { downloadUrl } from "@/misc/download-url.js"; +import { detectType } from "@/misc/get-file-info.js"; +import { StatusError } from "@/misc/fetch.js"; +import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; +import { serverLogger } from "../index.js"; +import { isMimeImage } from "@/misc/is-mime-image.js"; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export async function proxyMedia(ctx: Koa.Context) { - const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; + const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`; - if (typeof url !== 'string') { + if (typeof url !== "string") { ctx.status = 400; return; } @@ -26,53 +27,60 @@ export async function proxyMedia(ctx: Koa.Context) { await downloadUrl(url, path); const { mime, ext } = await detectType(path); - const isConvertibleImage = isMimeImage(mime, 'sharp-convertible-image'); + const isConvertibleImage = isMimeImage(mime, "sharp-convertible-image"); let image: IImage; - if ('static' in ctx.query && isConvertibleImage) { + if ("static" in ctx.query && isConvertibleImage) { image = await convertToWebp(path, 498, 280); - } else if ('preview' in ctx.query && isConvertibleImage) { + } else if ("preview" in ctx.query && isConvertibleImage) { image = await convertToWebp(path, 200, 200); - } else if ('badge' in ctx.query) { + } else if ("badge" in ctx.query) { if (!isConvertibleImage) { // 画像でないなら404でお茶を濁す - throw new StatusError('Unexpected mime', 404); + throw new StatusError("Unexpected mime", 404); } const mask = sharp(path) .resize(96, 96, { - fit: 'inside', + fit: "inside", withoutEnlargement: false, }) .greyscale() .normalise() .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast - .flatten({ background: '#000' }) - .toColorspace('b-w'); + .flatten({ background: "#000" }) + .toColorspace("b-w"); const stats = await mask.clone().stats(); if (stats.entropy < 0.1) { // エントロピーがあまりない場合は404にする - throw new StatusError('Skip to provide badge', 404); + throw new StatusError("Skip to provide badge", 404); } const data = sharp({ - create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, + create: { + width: 96, + height: 96, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }, }) - .pipelineColorspace('b-w') - .boolean(await mask.png().toBuffer(), 'eor'); + .pipelineColorspace("b-w") + .boolean(await mask.png().toBuffer(), "eor"); image = { data: await data.png().toBuffer(), - ext: 'png', - type: 'image/png', + ext: "png", + type: "image/png", }; - } else if (mime === 'image/svg+xml') { + } else if (mime === "image/svg+xml") { image = await convertToWebp(path, 2048, 2048, 1); - } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { - throw new StatusError('Rejected type', 403, 'Rejected type'); + } else if ( + !(mime.startsWith("image/") &&FILE_TYPE_BROWSERSAFE.includes(mime)) + ) { + throw new StatusError("Rejected type", 403, "Rejected type"); } else { image = { data: fs.readFileSync(path), @@ -81,8 +89,8 @@ export async function proxyMedia(ctx: Koa.Context) { }; } - ctx.set('Content-Type', image.type); - ctx.set('Cache-Control', 'max-age=31536000, immutable'); + ctx.set("Content-Type", image.type); + ctx.set("Cache-Control", "max-age=31536000, immutable"); ctx.body = image.data; } catch (e) { serverLogger.error(`${e}`); diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts index 157ef54ae..9cbeb28ae 100644 --- a/packages/backend/src/server/web/feed.ts +++ b/packages/backend/src/server/web/feed.ts @@ -1,10 +1,10 @@ -import { Feed } from 'feed'; -import { In, IsNull } from 'typeorm'; -import config from '@/config/index.js'; -import { User } from '@/models/entities/user.js'; -import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js'; +import { Feed } from "feed"; +import { In, IsNull } from "typeorm"; +import config from "@/config/index.js"; +import type { User } from "@/models/entities/user.js"; +import { Notes, DriveFiles, UserProfiles, Users } from "@/models/index.js"; -export default async function(user: User) { +export default async function (user: User) { const author = { link: `${config.url}/@${user.username}`, name: user.name || user.username, @@ -16,7 +16,7 @@ export default async function(user: User) { where: { userId: user.id, renoteId: IsNull(), - visibility: In(['public', 'home']), + visibility: In(["public", "home"]), }, order: { createdAt: -1 }, take: 20, @@ -26,8 +26,12 @@ export default async function(user: User) { id: author.link, title: `${author.name} (@${user.username}@${config.host})`, updated: notes[0].createdAt, - generator: 'Calckey', - description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, + generator: "Calckey", + description: `${user.notesCount} Notes, ${ + profile.ffVisibility === "public" ? user.followingCount : "?" + } Following, ${ + profile.ffVisibility === "public" ? user.followersCount : "?" + } Followers${profile.description ? ` · ${profile.description}` : ""}`, link: author.link, image: await Users.getAvatarUrl(user), feedLinks: { @@ -39,10 +43,13 @@ export default async function(user: User) { }); for (const note of notes) { - const files = note.fileIds.length > 0 ? await DriveFiles.findBy({ - id: In(note.fileIds), - }) : []; - const file = files.find(file => file.type.startsWith('image/')); + const files = + note.fileIds.length > 0 + ? await DriveFiles.findBy({ + id: In(note.fileIds), + }) + : []; + const file = files.find((file) => file.type.startsWith("image/")); feed.addItem({ title: `New note by ${author.name}`, diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 4e7e0a76e..9c55c24bb 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -2,31 +2,39 @@ * Web Client Server */ -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { readFileSync } from 'node:fs'; -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 { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { readFileSync } from "node:fs"; +import Koa from "koa"; +import Router from "@koa/router"; +import send from "koa-send"; +import favicon from "koa-favicon"; +import views from "koa-views"; +import sharp from "sharp"; +import { createBullBoard } from "@bull-board/api"; +import { BullAdapter } from "@bull-board/api/bullAdapter.js"; +import { KoaAdapter } from "@bull-board/koa"; -import { In, IsNull } from 'typeorm'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import config from '@/config/index.js'; -import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js'; -import * as Acct from '@/misc/acct.js'; -import { getNoteSummary } from '@/misc/get-note-summary.js'; -import { queues } from '@/queue/queues.js'; -import { genOpenapiSpec } from '../api/openapi/gen-spec.js'; -import { urlPreviewHandler } from './url-preview.js'; -import { manifestHandler } from './manifest.js'; -import packFeed from './feed.js'; -import { MINUTE, DAY } from '@/const.js'; +import { In, IsNull } from "typeorm"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import config from "@/config/index.js"; +import { + Users, + Notes, + UserProfiles, + Pages, + Channels, + Clips, + GalleryPosts, +} from "@/models/index.js"; +import * as Acct from "@/misc/acct.js"; +import { getNoteSummary } from "@/misc/get-note-summary.js"; +import { queues } from "@/queue/queues.js"; +import { genOpenapiSpec } from "../api/openapi/gen-spec.js"; +import { urlPreviewHandler } from "./url-preview.js"; +import { manifestHandler } from "./manifest.js"; +import packFeed from "./feed.js"; +import { MINUTE, DAY } from "@/const.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -40,12 +48,12 @@ const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; const app = new Koa(); //#region Bull Dashboard -const bullBoardPath = '/queue'; +const bullBoardPath = "/queue"; // Authenticate app.use(async (ctx, next) => { - if (ctx.path === bullBoardPath || ctx.path.startsWith(bullBoardPath + '/')) { - const token = ctx.cookies.get('token'); + if (ctx.path === bullBoardPath || ctx.path.startsWith(`${bullBoardPath}/`)) { + const token = ctx.cookies.get("token"); if (token == null) { ctx.status = 401; return; @@ -62,7 +70,7 @@ app.use(async (ctx, next) => { const serverAdapter = new KoaAdapter(); createBullBoard({ - queues: queues.map(q => new BullAdapter(q)), + queues: queues.map((q) => new BullAdapter(q)), serverAdapter, }); @@ -71,16 +79,24 @@ app.use(serverAdapter.registerPlugin()); //#endregion // Init renderer -app.use(views(_dirname + '/views', { - extension: 'pug', - options: { - version: config.version, - getClientEntry: () => process.env.NODE_ENV === 'production' ? - config.clientEntry : - JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], - config, - }, -})); +app.use( + views(`${_dirname}/views`, { + extension: "pug", + options: { + version: config.version, + getClientEntry: () => + process.env.NODE_ENV === "production" + ? config.clientEntry + : JSON.parse( + readFileSync( + `${_dirname}/../../../../../built/_client_dist_/manifest.json`, + "utf-8", + ), + )["src/init.ts"], + config, + }, + }), +); // Serve favicon app.use(favicon(`${_dirname}/../../../assets/favicon.ico`)); @@ -88,7 +104,7 @@ app.use(favicon(`${_dirname}/../../../assets/favicon.ico`)); // Common request handler app.use(async (ctx, next) => { // IFrameの中に入れられないようにする - ctx.set('X-Frame-Options', 'DENY'); + ctx.set("X-Frame-Options", "DENY"); await next(); }); @@ -97,43 +113,46 @@ const router = new Router(); //#region static assets -router.get('/static-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/static-assets/', ''), { +router.get("/static-assets/(.*)", async (ctx) => { + await send(ctx as any, ctx.path.replace("/static-assets/", ""), { root: staticAssets, maxage: 7 * DAY, }); }); -router.get('/client-assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/client-assets/', ''), { +router.get("/client-assets/(.*)", async (ctx) => { + await send(ctx as any, ctx.path.replace("/client-assets/", ""), { root: clientAssets, maxage: 7 * DAY, }); }); -router.get('/assets/(.*)', async ctx => { - await send(ctx as any, ctx.path.replace('/assets/', ''), { +router.get("/assets/(.*)", async (ctx) => { + await send(ctx as any, ctx.path.replace("/assets/", ""), { root: assets, maxage: 7 * DAY, }); }); // Apple touch icon -router.get('/apple-touch-icon.png', async ctx => { - await send(ctx as any, '/apple-touch-icon.png', { +router.get("/apple-touch-icon.png", async (ctx) => { + await send(ctx as any, "/apple-touch-icon.png", { root: staticAssets, }); }); -router.get('/twemoji/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji/', ''); +router.get("/twemoji/(.*)", async (ctx) => { + const path = ctx.path.replace("/twemoji/", ""); if (!path.match(/^[0-9a-f-]+\.svg$/)) { ctx.status = 404; return; } - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + ctx.set( + "Content-Security-Policy", + "default-src 'none'; style-src 'unsafe-inline'", + ); await send(ctx as any, path, { root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, @@ -141,8 +160,8 @@ router.get('/twemoji/(.*)', async ctx => { }); }); -router.get('/twemoji-badge/(.*)', async ctx => { - const path = ctx.path.replace('/twemoji-badge/', ''); +router.get("/twemoji-badge/(.*)", async (ctx) => { + const path = ctx.path.replace("/twemoji-badge/", ""); if (!path.match(/^[0-9a-f-]+\.png$/)) { ctx.status = 404; @@ -150,53 +169,64 @@ router.get('/twemoji-badge/(.*)', async ctx => { } const mask = await sharp( - `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/${path.replace('.png', '')}.svg`, + `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/${path.replace( + ".png", + "", + )}.svg`, { density: 1000 }, ) .resize(488, 488) .greyscale() .normalise() .linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast - .flatten({ background: '#000' }) + .flatten({ background: "#000" }) .extend({ top: 12, bottom: 12, left: 12, right: 12, - background: '#000', + background: "#000", }) - .toColorspace('b-w') + .toColorspace("b-w") .png() .toBuffer(); const buffer = await sharp({ - create: { width: 512, height: 512, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }, + create: { + width: 512, + height: 512, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }, }) - .pipelineColorspace('b-w') - .boolean(mask, 'eor') + .pipelineColorspace("b-w") + .boolean(mask, "eor") .resize(96, 96) .png() .toBuffer(); - ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); - ctx.set('Cache-Control', 'max-age=2592000'); - ctx.set('Content-Type', 'image/png'); + ctx.set( + "Content-Security-Policy", + "default-src 'none'; style-src 'unsafe-inline'", + ); + ctx.set("Cache-Control", "max-age=2592000"); + ctx.set("Content-Type", "image/png"); ctx.body = buffer; }); // ServiceWorker -router.get(`/sw.js`, async ctx => { - await send(ctx as any, `/sw.js`, { +router.get("/sw.js", async (ctx) => { + await send(ctx as any, "/sw.js", { root: swAssets, maxage: 10 * MINUTE, }); }); // Manifest -router.get('/manifest.json', manifestHandler); +router.get("/manifest.json", manifestHandler); -router.get('/robots.txt', async ctx => { - await send(ctx as any, '/robots.txt', { +router.get("/robots.txt", async (ctx) => { + await send(ctx as any, "/robots.txt", { root: staticAssets, }); }); @@ -204,16 +234,16 @@ router.get('/robots.txt', async ctx => { //#endregion // Docs -router.get('/api-doc', async ctx => { - await send(ctx as any, '/redoc.html', { +router.get("/api-doc", async (ctx) => { + await send(ctx as any, "/redoc.html", { root: staticAssets, }); }); // URL preview endpoint -router.get('/url', urlPreviewHandler); +router.get("/url", urlPreviewHandler); -router.get('/api.json', async ctx => { +router.get("/api.json", async (ctx) => { ctx.body = genOpenapiSpec(); }); @@ -229,11 +259,13 @@ const getFeed = async (acct: string) => { isSuspended: false, }); - return user && await packFeed(user); + return user && (await packFeed(user)); }; // As the /@user[.json|.rss|.atom]/sub endpoint is complicated, we will use a regex to switch between them. -const reUser = new RegExp(`^/@(?[^/]+?)(?:\.(?json|rss|atom))?(?:/(?[^/]+))?$`); +const reUser = new RegExp( + "^/@(?[^/]+?)(?:\.(?json|rss|atom))?(?:/(?[^/]+))?$", +); router.get(reUser, async (ctx, next) => { const groups = reUser.exec(ctx.originalUrl)?.groups; if (!groups) { @@ -243,7 +275,7 @@ router.get(reUser, async (ctx, next) => { ctx.params = groups; - console.log(ctx, ctx.params) + console.log(ctx, ctx.params); if (groups.feed) { if (groups.sub) { await next(); @@ -251,13 +283,13 @@ router.get(reUser, async (ctx, next) => { } switch (groups.feed) { - case 'json': + case "json": await jsonFeed(ctx, next); break; - case 'rss': + case "rss": await rssFeed(ctx, next); break; - case 'atom': + case "atom": await atomFeed(ctx, next); break; } @@ -268,11 +300,11 @@ router.get(reUser, async (ctx, next) => { }); // Atom -const atomFeed: Router.Middleware = async ctx => { +const atomFeed: Router.Middleware = async (ctx) => { const feed = await getFeed(ctx.params.user); if (feed) { - ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); + ctx.set("Content-Type", "application/atom+xml; charset=utf-8"); ctx.body = feed.atom1(); } else { ctx.status = 404; @@ -280,11 +312,11 @@ const atomFeed: Router.Middleware = async ctx => { }; // RSS -const rssFeed: Router.Middleware = async ctx => { +const rssFeed: Router.Middleware = async (ctx) => { const feed = await getFeed(ctx.params.user); if (feed) { - ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); + ctx.set("Content-Type", "application/rss+xml; charset=utf-8"); ctx.body = feed.rss2(); } else { ctx.status = 404; @@ -292,11 +324,11 @@ const rssFeed: Router.Middleware = async ctx => { }; // JSON -const jsonFeed: Router.Middleware = async ctx => { +const jsonFeed: Router.Middleware = async (ctx) => { const feed = await getFeed(ctx.params.user); if (feed) { - ctx.set('Content-Type', 'application/json; charset=utf-8'); + ctx.set("Content-Type", "application/json; charset=utf-8"); ctx.body = feed.json1(); } else { ctx.status = 404; @@ -325,25 +357,27 @@ const userPage: Router.Middleware = async (ctx, next) => { const meta = await fetchMeta(); const me = profile.fields ? profile.fields - .filter(filed => filed.value != null && filed.value.match(/^https?:/)) - .map(field => field.value) + .filter((filed) => filed.value?.match(/^https?:/)) + .map((field) => field.value) : []; const userDetail = { - user, profile, me, + user, + profile, + me, avatarUrl: await Users.getAvatarUrl(user), sub: subParam, - instanceName: meta.name || 'Calckey', + instanceName: meta.name || "Calckey", icon: meta.iconUrl, themeColor: meta.themeColor, privateMode: meta.privateMode, }; - await ctx.render('user', userDetail); - ctx.set('Cache-Control', 'public, max-age=15'); + await ctx.render("user", userDetail); + ctx.set("Cache-Control", "public, max-age=15"); }; -router.get('/users/:user', async ctx => { +router.get("/users/:user", async (ctx) => { const user = await Users.findOneBy({ id: ctx.params.user, host: IsNull(), @@ -355,33 +389,35 @@ router.get('/users/:user', async ctx => { return; } - ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); + ctx.redirect(`/@${user.username}${user.host == null ? "" : `@${user.host}`}`); }); // Note -router.get('/notes/:note', async (ctx, next) => { +router.get("/notes/:note", async (ctx, next) => { const note = await Notes.findOneBy({ id: ctx.params.note, - visibility: In(['public', 'home']), + visibility: In(["public", "home"]), }); if (note) { const _note = await Notes.pack(note); const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); const meta = await fetchMeta(); - await ctx.render('note', { + await ctx.render("note", { note: _note, profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })), + avatarUrl: await Users.getAvatarUrl( + await Users.findOneByOrFail({ id: note.userId }), + ), // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), - instanceName: meta.name || 'Calckey', + instanceName: meta.name || "Calckey", icon: meta.iconUrl, privateMode: meta.privateMode, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=15'); + ctx.set("Cache-Control", "public, max-age=15"); return; } @@ -389,29 +425,31 @@ router.get('/notes/:note', async (ctx, next) => { await next(); }); -router.get('/posts/:note', async (ctx, next) => { +router.get("/posts/:note", async (ctx, next) => { const note = await Notes.findOneBy({ id: ctx.params.note, - visibility: In(['public', 'home']), + visibility: In(["public", "home"]), }); if (note) { const _note = await Notes.pack(note); const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); const meta = await fetchMeta(); - await ctx.render('note', { + await ctx.render("note", { note: _note, profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })), + avatarUrl: await Users.getAvatarUrl( + await Users.findOneByOrFail({ id: note.userId }), + ), // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), - instanceName: meta.name || 'Calckey', + instanceName: meta.name || "Calckey", icon: meta.iconUrl, privateMode: meta.privateMode, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=15'); + ctx.set("Cache-Control", "public, max-age=15"); return; } @@ -420,7 +458,7 @@ router.get('/posts/:note', async (ctx, next) => { }); // Page -router.get('/@:user/pages/:page', async (ctx, next) => { +router.get("/@:user/pages/:page", async (ctx, next) => { const { username, host } = Acct.parse(ctx.params.user); const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), @@ -438,20 +476,22 @@ router.get('/@:user/pages/:page', async (ctx, next) => { const _page = await Pages.pack(page); const profile = await UserProfiles.findOneByOrFail({ userId: page.userId }); const meta = await fetchMeta(); - await ctx.render('page', { + await ctx.render("page", { page: _page, profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: page.userId })), - instanceName: meta.name || 'Calckey', + avatarUrl: await Users.getAvatarUrl( + await Users.findOneByOrFail({ id: page.userId }), + ), + instanceName: meta.name || "Calckey", icon: meta.iconUrl, themeColor: meta.themeColor, privateMode: meta.privateMode, }); - if (['public'].includes(page.visibility)) { - ctx.set('Cache-Control', 'public, max-age=15'); + if (["public"].includes(page.visibility)) { + ctx.set("Cache-Control", "public, max-age=15"); } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); } return; @@ -462,7 +502,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => { // Clip // TODO: handling of private clips -router.get('/clips/:clip', async (ctx, next) => { +router.get("/clips/:clip", async (ctx, next) => { const clip = await Clips.findOneBy({ id: ctx.params.clip, }); @@ -471,17 +511,19 @@ router.get('/clips/:clip', async (ctx, next) => { const _clip = await Clips.pack(clip); const profile = await UserProfiles.findOneByOrFail({ userId: clip.userId }); const meta = await fetchMeta(); - await ctx.render('clip', { + await ctx.render("clip", { clip: _clip, profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })), - instanceName: meta.name || 'Calckey', + avatarUrl: await Users.getAvatarUrl( + await Users.findOneByOrFail({ id: clip.userId }), + ), + instanceName: meta.name || "Calckey", privateMode: meta.privateMode, icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=15'); + ctx.set("Cache-Control", "public, max-age=15"); return; } @@ -490,24 +532,26 @@ router.get('/clips/:clip', async (ctx, next) => { }); // Gallery post -router.get('/gallery/:post', async (ctx, next) => { +router.get("/gallery/:post", async (ctx, next) => { const post = await GalleryPosts.findOneBy({ id: ctx.params.post }); if (post) { const _post = await GalleryPosts.pack(post); const profile = await UserProfiles.findOneByOrFail({ userId: post.userId }); const meta = await fetchMeta(); - await ctx.render('gallery-post', { + await ctx.render("gallery-post", { post: _post, profile, - avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: post.userId })), - instanceName: meta.name || 'Calckey', + avatarUrl: await Users.getAvatarUrl( + await Users.findOneByOrFail({ id: post.userId }), + ), + instanceName: meta.name || "Calckey", icon: meta.iconUrl, themeColor: meta.themeColor, privateMode: meta.privateMode, }); - ctx.set('Cache-Control', 'public, max-age=15'); + ctx.set("Cache-Control", "public, max-age=15"); return; } @@ -516,7 +560,7 @@ router.get('/gallery/:post', async (ctx, next) => { }); // Channel -router.get('/channels/:channel', async (ctx, next) => { +router.get("/channels/:channel", async (ctx, next) => { const channel = await Channels.findOneBy({ id: ctx.params.channel, }); @@ -524,15 +568,15 @@ router.get('/channels/:channel', async (ctx, next) => { if (channel) { const _channel = await Channels.pack(channel); const meta = await fetchMeta(); - await ctx.render('channel', { + await ctx.render("channel", { channel: _channel, - instanceName: meta.name || 'Calckey', + instanceName: meta.name || "Calckey", icon: meta.iconUrl, themeColor: meta.themeColor, privateMode: meta.privateMode, }); - ctx.set('Cache-Control', 'public, max-age=15'); + ctx.set("Cache-Control", "public, max-age=15"); return; } @@ -541,16 +585,16 @@ router.get('/channels/:channel', async (ctx, next) => { }); //#endregion -router.get('/_info_card_', async ctx => { +router.get("/_info_card_", async (ctx) => { const meta = await fetchMeta(true); if (meta.privateMode) { ctx.status = 403; return; } - ctx.remove('X-Frame-Options'); + ctx.remove("X-Frame-Options"); - await ctx.render('info-card', { + await ctx.render("info-card", { version: config.version, host: config.host, meta: meta, @@ -559,46 +603,56 @@ router.get('/_info_card_', async ctx => { }); }); -router.get('/bios', async ctx => { - await ctx.render('bios', { +router.get("/bios", async (ctx) => { + await ctx.render("bios", { version: config.version, }); }); -router.get('/cli', async ctx => { - await ctx.render('cli', { +router.get("/cli", async (ctx) => { + await ctx.render("cli", { version: config.version, }); }); const override = (source: string, target: string, depth = 0) => - [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); + [undefined + , + ...target.split("/").filter((x) => x), + ...source + .split("/") + .filter((x) => x) + .splice(depth), + ].join("/"); -router.get('/flush', async ctx => { - await ctx.render('flush'); +router.get("/flush", async (ctx) => { + await ctx.render("flush"); }); // If a non-WebSocket request comes in to streaming and base html is returned with cache, the path will be cached by Proxy, etc. and it will be wrong. -router.get('/streaming', async ctx => { +router.get("/streaming", async (ctx) => { ctx.status = 503; - ctx.set('Cache-Control', 'private, max-age=0'); + ctx.set("Cache-Control", "private, max-age=0"); }); // Render base html for all requests -router.get('(.*)', async ctx => { +router.get("(.*)", async (ctx) => { const meta = await fetchMeta(); - let motd = ['Loading...']; + let motd = ["Loading..."]; if (meta.customMOTD.length > 0) { motd = meta.customMOTD; } let splashIconUrl = meta.iconUrl; if (meta.customSplashIcons.length > 0) { - splashIconUrl = meta.customSplashIcons[Math.floor(Math.random() * meta.customSplashIcons.length)]; + splashIconUrl = + meta.customSplashIcons[ + Math.floor(Math.random() * meta.customSplashIcons.length) + ]; } - await ctx.render('base', { + await ctx.render("base", { img: meta.bannerUrl, - title: meta.name || 'Calckey', - instanceName: meta.name || 'Calckey', + title: meta.name || "Calckey", + instanceName: meta.name || "Calckey", desc: meta.description, icon: meta.iconUrl, splashIcon: splashIconUrl, @@ -606,7 +660,7 @@ router.get('(.*)', async ctx => { randomMOTD: motd[Math.floor(Math.random() * motd.length)], privateMode: meta.privateMode, }); - ctx.set('Cache-Control', 'public, max-age=3'); + ctx.set("Cache-Control", "public, max-age=3"); }); // Register router diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts index 36dfbbf42..31acc42f6 100644 --- a/packages/backend/src/server/web/manifest.ts +++ b/packages/backend/src/server/web/manifest.ts @@ -1,6 +1,6 @@ -import Koa from 'koa'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import manifest from './manifest.json' assert { type: 'json' }; +import type Koa from "koa"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import manifest from "./manifest.json" assert { type: "json" }; export const manifestHandler = async (ctx: Koa.Context) => { // TODO @@ -9,10 +9,10 @@ export const manifestHandler = async (ctx: Koa.Context) => { const instance = await fetchMeta(true); - res.short_name = instance.name || 'Calckey'; - res.name = instance.name || 'Calckey'; + res.short_name = instance.name || "Calckey"; + res.name = instance.name || "Calckey"; if (instance.themeColor) res.theme_color = instance.themeColor; - ctx.set('Cache-Control', 'max-age=300'); + ctx.set("Cache-Control", "max-age=300"); ctx.body = res; }; diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index 23819135a..d7da4e72c 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -1,16 +1,16 @@ -import Koa from 'koa'; -import summaly from 'summaly'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import Logger from '@/services/logger.js'; -import config from '@/config/index.js'; -import { query } from '@/prelude/url.js'; -import { getJson } from '@/misc/fetch.js'; +import type Koa from "koa"; +import summaly from "summaly"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import Logger from "@/services/logger.js"; +import config from "@/config/index.js"; +import { query } from "@/prelude/url.js"; +import { getJson } from "@/misc/fetch.js"; -const logger = new Logger('url-preview'); +const logger = new Logger("url-preview"); export const urlPreviewHandler = async (ctx: Koa.Context) => { const url = ctx.query.url; - if (typeof url !== 'string') { + if (typeof url !== "string") { ctx.status = 400; return; } @@ -23,18 +23,24 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => { const meta = await fetchMeta(); - logger.info(meta.summalyProxy - ? `(Proxy) Getting preview of ${url}@${lang} ...` - : `Getting preview of ${url}@${lang} ...`); + logger.info( + meta.summalyProxy + ? `(Proxy) Getting preview of ${url}@${lang} ...` + : `Getting preview of ${url}@${lang} ...`, + ); try { - const summary = meta.summalyProxy ? await getJson(`${meta.summalyProxy}?${query({ - url: url, - lang: lang ?? 'en-US', - })}`) : await summaly.default(url, { - followRedirects: false, - lang: lang ?? 'en-US', - }); + const summary = meta.summalyProxy + ? await getJson( + `${meta.summalyProxy}?${query({ + url: url, + lang: lang ?? "en-US", + })}`, + ) + : await summaly.default(url, { + followRedirects: false, + lang: lang ?? "en-US", + }); logger.succ(`Got preview of ${url}: ${summary.title}`); @@ -42,14 +48,14 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => { summary.thumbnail = wrap(summary.thumbnail); // Cache 7days - ctx.set('Cache-Control', 'max-age=604800, immutable'); + ctx.set("Cache-Control", "max-age=604800, immutable"); ctx.body = summary; } catch (err) { logger.warn(`Failed to get preview of ${url}: ${err}`); ctx.status = 200; - ctx.set('Cache-Control', 'max-age=86400, immutable'); - ctx.body = '{}'; + ctx.set("Cache-Control", "max-age=86400, immutable"); + ctx.body = "{}"; } }; @@ -57,9 +63,9 @@ function wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) ? `${config.url}/proxy/preview.webp?${query({ - url, - preview: '1', - })}` + url, + preview: "1", + })}` : url : null; } diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 1d094f2ed..4b839c879 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -1,64 +1,80 @@ -import Router from '@koa/router'; +import Router from "@koa/router"; -import config from '@/config/index.js'; -import * as Acct from '@/misc/acct.js'; -import { links } from './nodeinfo.js'; -import { escapeAttribute, escapeValue } from '@/prelude/xml.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { FindOptionsWhere, IsNull } from 'typeorm'; +import config from "@/config/index.js"; +import * as Acct from "@/misc/acct.js"; +import { links } from "./nodeinfo.js"; +import { escapeAttribute, escapeValue } from "@/prelude/xml.js"; +import { Users } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import type { FindOptionsWhere } from "typeorm"; +import { IsNull } from "typeorm"; // Init router const router = new Router(); -const XRD = (...x: { element: string, value?: string, attributes?: Record }[]) => - `${x.map(({ element, value, attributes }) => - `<${ - Object.entries(typeof attributes === 'object' && attributes || {}).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element) - }${ - typeof value === 'string' ? `>${escapeValue(value)}`).reduce((a, c) => a + c, '')}`; +const XRD = ( + ...x: { + element: string; + value?: string; + attributes?: Record; + }[] +) => + `${x + .map( + ({ element, value, attributes }) => + `<${Object.entries( + (typeof attributes === "object" && attributes) || {}, + ).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element)}${ + typeof value === "string" ? `>${escapeValue(value)}`, + ) + .reduce((a, c) => a + c, "")}`; -const allPath = '/.well-known/(.*)'; -const webFingerPath = '/.well-known/webfinger'; -const jrd = 'application/jrd+json'; -const xrd = 'application/xrd+xml'; +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', + "Access-Control-Allow-Headers": "Accept", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Vary", }); await next(); }); -router.options(allPath, async ctx => { +router.options(allPath, async (ctx) => { ctx.status = 204; }); -router.get('/.well-known/host-meta', async ctx => { - ctx.set('Content-Type', xrd); - ctx.body = XRD({ element: 'Link', attributes: { - rel: 'lrdd', - type: xrd, - template: `${config.url}${webFingerPath}?resource={uri}`, - } }); +router.get("/.well-known/host-meta", async (ctx) => { + ctx.set("Content-Type", xrd); + ctx.body = XRD({ + element: "Link", + attributes: { + rel: "lrdd", + type: xrd, + template: `${config.url}${webFingerPath}?resource={uri}`, + }, + }); }); -router.get('/.well-known/host-meta.json', async ctx => { - ctx.set('Content-Type', jrd); +router.get("/.well-known/host-meta.json", async (ctx) => { + ctx.set("Content-Type", jrd); ctx.body = { - links: [{ - rel: 'lrdd', - type: jrd, - template: `${config.url}${webFingerPath}?resource={uri}`, - }], + links: [ + { + rel: "lrdd", + type: jrd, + template: `${config.url}${webFingerPath}?resource={uri}`, + }, + ], }; }); -router.get('/.well-known/nodeinfo', async ctx => { +router.get("/.well-known/nodeinfo", async (ctx) => { ctx.body = { links }; }); @@ -67,36 +83,43 @@ router.get('/.well-known/change-password', async ctx => { }); */ -router.get(webFingerPath, async ctx => { - const fromId = (id: User['id']): FindOptionsWhere => ({ +router.get(webFingerPath, async (ctx) => { + const fromId = (id: User["id"]): FindOptionsWhere => ({ id, host: IsNull(), isSuspended: false, }); const generateQuery = (resource: string): FindOptionsWhere | number => - resource.startsWith(`${config.url.toLowerCase()}/users/`) ? - fromId(resource.split('/').pop()!) : - fromAcct(Acct.parse( - resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop()! : - resource.startsWith('acct:') ? resource.slice('acct:'.length) : - resource)); + resource.startsWith(`${config.url.toLowerCase()}/users/`) + ? fromId(resource.split("/").pop()!) + : fromAcct( + Acct.parse( + resource.startsWith(`${config.url.toLowerCase()}/@`) + ? resource.split("/").pop()! + : resource.startsWith("acct:") + ? resource.slice("acct:".length) + : resource, + ), + ); const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => - !acct.host || acct.host === config.host.toLowerCase() ? { - usernameLower: acct.username, - host: IsNull(), - isSuspended: false, - } : 422; + !acct.host || acct.host === config.host.toLowerCase() + ? { + usernameLower: acct.username, + host: IsNull(), + isSuspended: false, + } + : 422; - if (typeof ctx.query.resource !== 'string') { + if (typeof ctx.query.resource !== "string") { ctx.status = 400; return; } const query = generateQuery(ctx.query.resource.toLowerCase()); - if (typeof query === 'number') { + if (typeof query === "number") { ctx.status = query; return; } @@ -110,26 +133,27 @@ router.get(webFingerPath, async ctx => { const subject = `acct:${user.username}@${config.host}`; const self = { - rel: 'self', - type: 'application/activity+json', + rel: "self", + type: "application/activity+json", href: `${config.url}/users/${user.id}`, }; const profilePage = { - rel: 'http://webfinger.net/rel/profile-page', - type: 'text/html', + rel: "http://webfinger.net/rel/profile-page", + type: "text/html", href: `${config.url}/@${user.username}`, }; const subscribe = { - rel: 'http://ostatus.org/schema/1.0/subscribe', + rel: "http://ostatus.org/schema/1.0/subscribe", template: `${config.url}/authorize-follow?acct={uri}`, }; if (ctx.accepts(jrd, xrd) === xrd) { ctx.body = XRD( - { element: 'Subject', value: subject }, - { element: 'Link', attributes: self }, - { element: 'Link', attributes: profilePage }, - { element: 'Link', attributes: subscribe }); + { element: "Subject", value: subject }, + { element: "Link", attributes: self }, + { element: "Link", attributes: profilePage }, + { element: "Link", attributes: subscribe }, + ); ctx.type = xrd; } else { ctx.body = { @@ -139,12 +163,12 @@ router.get(webFingerPath, async ctx => { ctx.type = jrd; } - ctx.vary('Accept'); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.vary("Accept"); + ctx.set("Cache-Control", "public, max-age=180"); }); // Return 404 for other .well-known -router.all(allPath, async ctx => { +router.all(allPath, async (ctx) => { ctx.status = 404; }); diff --git a/packages/backend/src/services/add-note-to-antenna.ts b/packages/backend/src/services/add-note-to-antenna.ts index 1f344222e..38979acb4 100644 --- a/packages/backend/src/services/add-note-to-antenna.ts +++ b/packages/backend/src/services/add-note-to-antenna.ts @@ -1,14 +1,18 @@ -import { Antenna } from '@/models/entities/antenna.js'; -import { Note } from '@/models/entities/note.js'; -import { AntennaNotes, Mutings, Notes } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { publishAntennaStream, publishMainStream } from '@/services/stream.js'; -import { User } from '@/models/entities/user.js'; +import type { Antenna } from "@/models/entities/antenna.js"; +import type { Note } from "@/models/entities/note.js"; +import { AntennaNotes, Mutings, Notes } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { isUserRelated } from "@/misc/is-user-related.js"; +import { publishAntennaStream, publishMainStream } from "@/services/stream.js"; +import type { User } from "@/models/entities/user.js"; -export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }) { +export async function addNoteToAntenna( + antenna: Antenna, + note: Note, + noteUser: { id: User["id"] }, +) { // 通知しない設定になっているか、自分自身の投稿なら既読にする - const read = !antenna.notify || (antenna.userId === noteUser.id); + const read = !antenna.notify || antenna.userId === noteUser.id; AntennaNotes.insert({ id: genId(), @@ -17,14 +21,14 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { read: read, }); - publishAntennaStream(antenna.id, 'note', note); + publishAntennaStream(antenna.id, "note", note); if (!read) { const mutings = await Mutings.find({ where: { muterId: antenna.userId, }, - select: ['muteeId'], + select: ["muteeId"], }); // Copy @@ -39,15 +43,18 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { _note.renote = await Notes.findOneByOrFail({ id: note.renoteId }); } - if (isUserRelated(_note, new Set(mutings.map(x => x.muteeId)))) { + if (isUserRelated(_note, new Set(mutings.map((x) => x.muteeId)))) { return; } // 2秒経っても既読にならなかったら通知 setTimeout(async () => { - const unread = await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false }); + const unread = await AntennaNotes.findOneBy({ + antennaId: antenna.id, + read: false, + }); if (unread) { - publishMainStream(antenna.userId, 'unreadAntenna', antenna); + publishMainStream(antenna.userId, "unreadAntenna", antenna); } }, 2000); } diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index a2c61cca2..60bd6e943 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -1,20 +1,27 @@ -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { renderBlock } from '@/remote/activitypub/renderer/block.js'; -import { deliver } from '@/queue/index.js'; -import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { Blocking } from '@/models/entities/blocking.js'; -import { User } from '@/models/entities/user.js'; -import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js'; -import { perUserFollowingChart } from '@/services/chart/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; -import { webhookDeliver } from '@/queue/index.js'; +import { publishMainStream, publishUserEvent } from "@/services/stream.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { renderBlock } from "@/remote/activitypub/renderer/block.js"; +import { deliver } from "@/queue/index.js"; +import renderReject from "@/remote/activitypub/renderer/reject.js"; +import type { Blocking } from "@/models/entities/blocking.js"; +import type { User } from "@/models/entities/user.js"; +import { + Blockings, + Users, + FollowRequests, + Followings, + UserListJoinings, + UserLists, +} from "@/models/index.js"; +import { perUserFollowingChart } from "@/services/chart/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import { getActiveWebhooks } from "@/misc/webhook-cache.js"; +import { webhookDeliver } from "@/queue/index.js"; -export default async function(blocker: User, blockee: User) { +export default async function (blocker: User, blockee: User) { await Promise.all([ cancelRequest(blocker, blockee), cancelRequest(blockee, blocker), @@ -58,19 +65,21 @@ async function cancelRequest(follower: User, followee: User) { if (Users.isLocalUser(followee)) { Users.pack(followee, followee, { detail: true, - }).then(packed => publishMainStream(followee.id, 'meUpdated', packed)); + }).then((packed) => publishMainStream(followee.id, "meUpdated", packed)); } if (Users.isLocalUser(follower)) { Users.pack(followee, follower, { detail: true, - }).then(async packed => { - publishUserEvent(follower.id, 'unfollow', packed); - publishMainStream(follower.id, 'unfollow', packed); + }).then(async (packed) => { + publishUserEvent(follower.id, "unfollow", packed); + publishMainStream(follower.id, "unfollow", packed); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === follower.id && x.on.includes("unfollow"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'unfollow', { + webhookDeliver(webhook, "unfollow", { user: packed, }); } @@ -79,13 +88,20 @@ async function cancelRequest(follower: User, followee: User) { // リモートにフォローリクエストをしていたらUndoFollow送信 if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); + const content = renderActivity( + renderUndo(renderFollow(follower, followee), follower), + ); deliver(follower, content, followee.inbox); } // リモートからフォローリクエストを受けていたらReject送信 if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId!), followee)); + const content = renderActivity( + renderReject( + renderFollow(follower, followee, request.requestId!), + followee, + ), + ); deliver(followee, content, follower.inbox); } } @@ -102,8 +118,8 @@ async function unFollow(follower: User, followee: User) { await Promise.all([ Followings.delete(following.id), - Users.decrement({ id: follower.id }, 'followingCount', 1), - Users.decrement({ id: followee.id }, 'followersCount', 1), + Users.decrement({ id: follower.id }, "followingCount", 1), + Users.decrement({ id: followee.id }, "followersCount", 1), perUserFollowingChart.update(follower, followee, false), ]); @@ -111,13 +127,15 @@ async function unFollow(follower: User, followee: User) { if (Users.isLocalUser(follower)) { Users.pack(followee, follower, { detail: true, - }).then(async packed => { - publishUserEvent(follower.id, 'unfollow', packed); - publishMainStream(follower.id, 'unfollow', packed); + }).then(async (packed) => { + publishUserEvent(follower.id, "unfollow", packed); + publishMainStream(follower.id, "unfollow", packed); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === follower.id && x.on.includes("unfollow"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'unfollow', { + webhookDeliver(webhook, "unfollow", { user: packed, }); } @@ -126,7 +144,9 @@ async function unFollow(follower: User, followee: User) { // リモートにフォローをしていたらUndoFollow送信 if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); + const content = renderActivity( + renderUndo(renderFollow(follower, followee), follower), + ); deliver(follower, content, followee.inbox); } } diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index cb16651bc..67f1e76f0 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -1,21 +1,24 @@ -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { renderBlock } from '@/remote/activitypub/renderer/block.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { deliver } from '@/queue/index.js'; -import Logger from '../logger.js'; -import { CacheableUser, User } from '@/models/entities/user.js'; -import { Blockings, Users } from '@/models/index.js'; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { renderBlock } from "@/remote/activitypub/renderer/block.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { deliver } from "@/queue/index.js"; +import Logger from "../logger.js"; +import type { CacheableUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import { Blockings, Users } from "@/models/index.js"; -const logger = new Logger('blocking/delete'); +const logger = new Logger("blocking/delete"); -export default async function(blocker: CacheableUser, blockee: CacheableUser) { +export default async function (blocker: CacheableUser, blockee: CacheableUser) { const blocking = await Blockings.findOneBy({ blockerId: blocker.id, blockeeId: blockee.id, }); if (blocking == null) { - logger.warn('ブロック解除がリクエストされましたがブロックしていませんでした'); + logger.warn( + "ブロック解除がリクエストされましたがブロックしていませんでした", + ); return; } diff --git a/packages/backend/src/services/chart/charts/active-users.ts b/packages/backend/src/services/chart/charts/active-users.ts index d952ea53b..9035a784b 100644 --- a/packages/backend/src/services/chart/charts/active-users.ts +++ b/packages/backend/src/services/chart/charts/active-users.ts @@ -1,7 +1,8 @@ -import Chart, { KVs } from '../core.js'; -import { User } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; -import { name, schema } from './entities/active-users.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import type { User } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; +import { name, schema } from "./entities/active-users.js"; const week = 1000 * 60 * 60 * 24 * 7; const month = 1000 * 60 * 60 * 24 * 30; @@ -24,21 +25,35 @@ export default class ActiveUsersChart extends Chart { return {}; } - public async read(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { + public async read(user: { + id: User["id"]; + host: null; + createdAt: User["createdAt"]; + }): Promise { await this.commit({ - 'read': [user.id], - 'registeredWithinWeek': (Date.now() - user.createdAt.getTime() < week) ? [user.id] : [], - 'registeredWithinMonth': (Date.now() - user.createdAt.getTime() < month) ? [user.id] : [], - 'registeredWithinYear': (Date.now() - user.createdAt.getTime() < year) ? [user.id] : [], - 'registeredOutsideWeek': (Date.now() - user.createdAt.getTime() > week) ? [user.id] : [], - 'registeredOutsideMonth': (Date.now() - user.createdAt.getTime() > month) ? [user.id] : [], - 'registeredOutsideYear': (Date.now() - user.createdAt.getTime() > year) ? [user.id] : [], + read: [user.id], + registeredWithinWeek: + Date.now() - user.createdAt.getTime() < week ? [user.id] : [], + registeredWithinMonth: + Date.now() - user.createdAt.getTime() < month ? [user.id] : [], + registeredWithinYear: + Date.now() - user.createdAt.getTime() < year ? [user.id] : [], + registeredOutsideWeek: + Date.now() - user.createdAt.getTime() > week ? [user.id] : [], + registeredOutsideMonth: + Date.now() - user.createdAt.getTime() > month ? [user.id] : [], + registeredOutsideYear: + Date.now() - user.createdAt.getTime() > year ? [user.id] : [], }); } - public async write(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise { + public async write(user: { + id: User["id"]; + host: null; + createdAt: User["createdAt"]; + }): Promise { await this.commit({ - 'write': [user.id], + write: [user.id], }); } } diff --git a/packages/backend/src/services/chart/charts/ap-request.ts b/packages/backend/src/services/chart/charts/ap-request.ts index e9e42ade7..d587b3288 100644 --- a/packages/backend/src/services/chart/charts/ap-request.ts +++ b/packages/backend/src/services/chart/charts/ap-request.ts @@ -1,5 +1,6 @@ -import Chart, { KVs } from '../core.js'; -import { name, schema } from './entities/ap-request.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { name, schema } from "./entities/ap-request.js"; /** * Chart about ActivityPub requests @@ -20,19 +21,19 @@ export default class ApRequestChart extends Chart { public async deliverSucc(): Promise { await this.commit({ - 'deliverSucceeded': 1, + deliverSucceeded: 1, }); } public async deliverFail(): Promise { await this.commit({ - 'deliverFailed': 1, + deliverFailed: 1, }); } public async inbox(): Promise { await this.commit({ - 'inboxReceived': 1, + inboxReceived: 1, }); } } diff --git a/packages/backend/src/services/chart/charts/drive.ts b/packages/backend/src/services/chart/charts/drive.ts index 0eeba90dd..964ef9fec 100644 --- a/packages/backend/src/services/chart/charts/drive.ts +++ b/packages/backend/src/services/chart/charts/drive.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { DriveFiles } from '@/models/index.js'; -import { Not, IsNull } from 'typeorm'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { name, schema } from './entities/drive.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { DriveFiles } from "@/models/index.js"; +import { Not, IsNull } from "typeorm"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { name, schema } from "./entities/drive.js"; /** * ドライブに関するチャート @@ -23,16 +24,20 @@ export default class DriveChart extends Chart { public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; - await this.commit(file.userHost === null ? { - 'local.incCount': isAdditional ? 1 : 0, - 'local.incSize': isAdditional ? fileSizeKb : 0, - 'local.decCount': isAdditional ? 0 : 1, - 'local.decSize': isAdditional ? 0 : fileSizeKb, - } : { - 'remote.incCount': isAdditional ? 1 : 0, - 'remote.incSize': isAdditional ? fileSizeKb : 0, - 'remote.decCount': isAdditional ? 0 : 1, - 'remote.decSize': isAdditional ? 0 : fileSizeKb, - }); + await this.commit( + file.userHost === null + ? { + "local.incCount": isAdditional ? 1 : 0, + "local.incSize": isAdditional ? fileSizeKb : 0, + "local.decCount": isAdditional ? 0 : 1, + "local.decSize": isAdditional ? 0 : fileSizeKb, + } + : { + "remote.incCount": isAdditional ? 1 : 0, + "remote.incSize": isAdditional ? fileSizeKb : 0, + "remote.decCount": isAdditional ? 0 : 1, + "remote.decSize": isAdditional ? 0 : fileSizeKb, + }, + ); } } diff --git a/packages/backend/src/services/chart/charts/entities/active-users.ts b/packages/backend/src/services/chart/charts/entities/active-users.ts index 5767b76f8..4e5fa37a2 100644 --- a/packages/backend/src/services/chart/charts/entities/active-users.ts +++ b/packages/backend/src/services/chart/charts/entities/active-users.ts @@ -1,17 +1,17 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'activeUsers'; +export const name = "activeUsers"; export const schema = { - 'readWrite': { intersection: ['read', 'write'], range: 'small' }, - 'read': { uniqueIncrement: true, range: 'small' }, - 'write': { uniqueIncrement: true, range: 'small' }, - 'registeredWithinWeek': { uniqueIncrement: true, range: 'small' }, - 'registeredWithinMonth': { uniqueIncrement: true, range: 'small' }, - 'registeredWithinYear': { uniqueIncrement: true, range: 'small' }, - 'registeredOutsideWeek': { uniqueIncrement: true, range: 'small' }, - 'registeredOutsideMonth': { uniqueIncrement: true, range: 'small' }, - 'registeredOutsideYear': { uniqueIncrement: true, range: 'small' }, + readWrite: { intersection: ["read", "write"], range: "small" }, + read: { uniqueIncrement: true, range: "small" }, + write: { uniqueIncrement: true, range: "small" }, + registeredWithinWeek: { uniqueIncrement: true, range: "small" }, + registeredWithinMonth: { uniqueIncrement: true, range: "small" }, + registeredWithinYear: { uniqueIncrement: true, range: "small" }, + registeredOutsideWeek: { uniqueIncrement: true, range: "small" }, + registeredOutsideMonth: { uniqueIncrement: true, range: "small" }, + registeredOutsideYear: { uniqueIncrement: true, range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/ap-request.ts b/packages/backend/src/services/chart/charts/entities/ap-request.ts index 3a9f3dacf..eb0220174 100644 --- a/packages/backend/src/services/chart/charts/entities/ap-request.ts +++ b/packages/backend/src/services/chart/charts/entities/ap-request.ts @@ -1,11 +1,11 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'apRequest'; +export const name = "apRequest"; export const schema = { - 'deliverFailed': { }, - 'deliverSucceeded': { }, - 'inboxReceived': { }, + deliverFailed: {}, + deliverSucceeded: {}, + inboxReceived: {}, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/drive.ts b/packages/backend/src/services/chart/charts/entities/drive.ts index 4bf5bb729..0280ec655 100644 --- a/packages/backend/src/services/chart/charts/entities/drive.ts +++ b/packages/backend/src/services/chart/charts/entities/drive.ts @@ -1,16 +1,16 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'drive'; +export const name = "drive"; export const schema = { - 'local.incCount': {}, - 'local.incSize': {}, // in kilobyte - 'local.decCount': {}, - 'local.decSize': {}, // in kilobyte - 'remote.incCount': {}, - 'remote.incSize': {}, // in kilobyte - 'remote.decCount': {}, - 'remote.decSize': {}, // in kilobyte + "local.incCount": {}, + "local.incSize": {}, // in kilobyte + "local.decCount": {}, + "local.decSize": {}, // in kilobyte + "remote.incCount": {}, + "remote.incSize": {}, // in kilobyte + "remote.decCount": {}, + "remote.decSize": {}, // in kilobyte } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/federation.ts b/packages/backend/src/services/chart/charts/entities/federation.ts index a8466b0b4..b77e02096 100644 --- a/packages/backend/src/services/chart/charts/entities/federation.ts +++ b/packages/backend/src/services/chart/charts/entities/federation.ts @@ -1,16 +1,16 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'federation'; +export const name = "federation"; export const schema = { - 'deliveredInstances': { uniqueIncrement: true, range: 'small' }, - 'inboxInstances': { uniqueIncrement: true, range: 'small' }, - 'stalled': { uniqueIncrement: true, range: 'small' }, - 'sub': { accumulate: true, range: 'small' }, - 'pub': { accumulate: true, range: 'small' }, - 'pubsub': { accumulate: true, range: 'small' }, - 'subActive': { accumulate: true, range: 'small' }, - 'pubActive': { accumulate: true, range: 'small' }, + deliveredInstances: { uniqueIncrement: true, range: "small" }, + inboxInstances: { uniqueIncrement: true, range: "small" }, + stalled: { uniqueIncrement: true, range: "small" }, + sub: { accumulate: true, range: "small" }, + pub: { accumulate: true, range: "small" }, + pubsub: { accumulate: true, range: "small" }, + subActive: { accumulate: true, range: "small" }, + pubActive: { accumulate: true, range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/hashtag.ts b/packages/backend/src/services/chart/charts/entities/hashtag.ts index 4d0403904..77964b4ca 100644 --- a/packages/backend/src/services/chart/charts/entities/hashtag.ts +++ b/packages/backend/src/services/chart/charts/entities/hashtag.ts @@ -1,10 +1,10 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'hashtag'; +export const name = "hashtag"; export const schema = { - 'local.users': { uniqueIncrement: true }, - 'remote.users': { uniqueIncrement: true }, + "local.users": { uniqueIncrement: true }, + "remote.users": { uniqueIncrement: true }, } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/instance.ts b/packages/backend/src/services/chart/charts/entities/instance.ts index 06962120e..a75dea475 100644 --- a/packages/backend/src/services/chart/charts/entities/instance.ts +++ b/packages/backend/src/services/chart/charts/entities/instance.ts @@ -1,32 +1,32 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'instance'; +export const name = "instance"; export const schema = { - 'requests.failed': { range: 'small' }, - 'requests.succeeded': { range: 'small' }, - 'requests.received': { range: 'small' }, - 'notes.total': { accumulate: true }, - 'notes.inc': {}, - 'notes.dec': {}, - 'notes.diffs.normal': {}, - 'notes.diffs.reply': {}, - 'notes.diffs.renote': {}, - 'notes.diffs.withFile': {}, - 'users.total': { accumulate: true }, - 'users.inc': { range: 'small' }, - 'users.dec': { range: 'small' }, - 'following.total': { accumulate: true }, - 'following.inc': { range: 'small' }, - 'following.dec': { range: 'small' }, - 'followers.total': { accumulate: true }, - 'followers.inc': { range: 'small' }, - 'followers.dec': { range: 'small' }, - 'drive.totalFiles': { accumulate: true }, - 'drive.incFiles': {}, - 'drive.decFiles': {}, - 'drive.incUsage': {}, // in kilobyte - 'drive.decUsage': {}, // in kilobyte + "requests.failed": { range: "small" }, + "requests.succeeded": { range: "small" }, + "requests.received": { range: "small" }, + "notes.total": { accumulate: true }, + "notes.inc": {}, + "notes.dec": {}, + "notes.diffs.normal": {}, + "notes.diffs.reply": {}, + "notes.diffs.renote": {}, + "notes.diffs.withFile": {}, + "users.total": { accumulate: true }, + "users.inc": { range: "small" }, + "users.dec": { range: "small" }, + "following.total": { accumulate: true }, + "following.inc": { range: "small" }, + "following.dec": { range: "small" }, + "followers.total": { accumulate: true }, + "followers.inc": { range: "small" }, + "followers.dec": { range: "small" }, + "drive.totalFiles": { accumulate: true }, + "drive.incFiles": {}, + "drive.decFiles": {}, + "drive.incUsage": {}, // in kilobyte + "drive.decUsage": {}, // in kilobyte } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/notes.ts b/packages/backend/src/services/chart/charts/entities/notes.ts index 9387dbfb2..04e75a2f2 100644 --- a/packages/backend/src/services/chart/charts/entities/notes.ts +++ b/packages/backend/src/services/chart/charts/entities/notes.ts @@ -1,22 +1,22 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'notes'; +export const name = "notes"; export const schema = { - 'local.total': { accumulate: true }, - 'local.inc': {}, - 'local.dec': {}, - 'local.diffs.normal': {}, - 'local.diffs.reply': {}, - 'local.diffs.renote': {}, - 'local.diffs.withFile': {}, - 'remote.total': { accumulate: true }, - 'remote.inc': {}, - 'remote.dec': {}, - 'remote.diffs.normal': {}, - 'remote.diffs.reply': {}, - 'remote.diffs.renote': {}, - 'remote.diffs.withFile': {}, + "local.total": { accumulate: true }, + "local.inc": {}, + "local.dec": {}, + "local.diffs.normal": {}, + "local.diffs.reply": {}, + "local.diffs.renote": {}, + "local.diffs.withFile": {}, + "remote.total": { accumulate: true }, + "remote.inc": {}, + "remote.dec": {}, + "remote.diffs.normal": {}, + "remote.diffs.reply": {}, + "remote.diffs.renote": {}, + "remote.diffs.withFile": {}, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/per-user-drive.ts b/packages/backend/src/services/chart/charts/entities/per-user-drive.ts index 6111640ea..d9dcd3d35 100644 --- a/packages/backend/src/services/chart/charts/entities/per-user-drive.ts +++ b/packages/backend/src/services/chart/charts/entities/per-user-drive.ts @@ -1,14 +1,14 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'perUserDrive'; +export const name = "perUserDrive"; export const schema = { - 'totalCount': { accumulate: true }, - 'totalSize': { accumulate: true }, // in kilobyte - 'incCount': { range: 'small' }, - 'incSize': {}, // in kilobyte - 'decCount': { range: 'small' }, - 'decSize': {}, // in kilobyte + totalCount: { accumulate: true }, + totalSize: { accumulate: true }, // in kilobyte + incCount: { range: "small" }, + incSize: {}, // in kilobyte + decCount: { range: "small" }, + decSize: {}, // in kilobyte } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/per-user-following.ts b/packages/backend/src/services/chart/charts/entities/per-user-following.ts index 4118daa47..3cbeec111 100644 --- a/packages/backend/src/services/chart/charts/entities/per-user-following.ts +++ b/packages/backend/src/services/chart/charts/entities/per-user-following.ts @@ -1,20 +1,20 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'perUserFollowing'; +export const name = "perUserFollowing"; export const schema = { - 'local.followings.total': { accumulate: true }, - 'local.followings.inc': { range: 'small' }, - 'local.followings.dec': { range: 'small' }, - 'local.followers.total': { accumulate: true }, - 'local.followers.inc': { range: 'small' }, - 'local.followers.dec': { range: 'small' }, - 'remote.followings.total': { accumulate: true }, - 'remote.followings.inc': { range: 'small' }, - 'remote.followings.dec': { range: 'small' }, - 'remote.followers.total': { accumulate: true }, - 'remote.followers.inc': { range: 'small' }, - 'remote.followers.dec': { range: 'small' }, + "local.followings.total": { accumulate: true }, + "local.followings.inc": { range: "small" }, + "local.followings.dec": { range: "small" }, + "local.followers.total": { accumulate: true }, + "local.followers.inc": { range: "small" }, + "local.followers.dec": { range: "small" }, + "remote.followings.total": { accumulate: true }, + "remote.followings.inc": { range: "small" }, + "remote.followings.dec": { range: "small" }, + "remote.followers.total": { accumulate: true }, + "remote.followers.inc": { range: "small" }, + "remote.followers.dec": { range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/per-user-notes.ts b/packages/backend/src/services/chart/charts/entities/per-user-notes.ts index c1fa17445..30c22e2f4 100644 --- a/packages/backend/src/services/chart/charts/entities/per-user-notes.ts +++ b/packages/backend/src/services/chart/charts/entities/per-user-notes.ts @@ -1,15 +1,15 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'perUserNotes'; +export const name = "perUserNotes"; export const schema = { - 'total': { accumulate: true }, - 'inc': { range: 'small' }, - 'dec': { range: 'small' }, - 'diffs.normal': { range: 'small' }, - 'diffs.reply': { range: 'small' }, - 'diffs.renote': { range: 'small' }, - 'diffs.withFile': { range: 'small' }, + total: { accumulate: true }, + inc: { range: "small" }, + dec: { range: "small" }, + "diffs.normal": { range: "small" }, + "diffs.reply": { range: "small" }, + "diffs.renote": { range: "small" }, + "diffs.withFile": { range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/per-user-reactions.ts b/packages/backend/src/services/chart/charts/entities/per-user-reactions.ts index 5e1a6c7b3..f281531c0 100644 --- a/packages/backend/src/services/chart/charts/entities/per-user-reactions.ts +++ b/packages/backend/src/services/chart/charts/entities/per-user-reactions.ts @@ -1,10 +1,10 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'perUserReaction'; +export const name = "perUserReaction"; export const schema = { - 'local.count': { range: 'small' }, - 'remote.count': { range: 'small' }, + "local.count": { range: "small" }, + "remote.count": { range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/test-grouped.ts b/packages/backend/src/services/chart/charts/entities/test-grouped.ts index 66b6e8e86..428f2bb36 100644 --- a/packages/backend/src/services/chart/charts/entities/test-grouped.ts +++ b/packages/backend/src/services/chart/charts/entities/test-grouped.ts @@ -1,11 +1,11 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'testGrouped'; +export const name = "testGrouped"; export const schema = { - 'foo.total': { accumulate: true }, - 'foo.inc': {}, - 'foo.dec': {}, + "foo.total": { accumulate: true }, + "foo.inc": {}, + "foo.dec": {}, } as const; export const entity = Chart.schemaToEntity(name, schema, true); diff --git a/packages/backend/src/services/chart/charts/entities/test-intersection.ts b/packages/backend/src/services/chart/charts/entities/test-intersection.ts index a3bdcb367..30d8753d7 100644 --- a/packages/backend/src/services/chart/charts/entities/test-intersection.ts +++ b/packages/backend/src/services/chart/charts/entities/test-intersection.ts @@ -1,11 +1,11 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'testIntersection'; +export const name = "testIntersection"; export const schema = { - 'a': { uniqueIncrement: true }, - 'b': { uniqueIncrement: true }, - 'aAndB': { intersection: ['a', 'b'] }, + a: { uniqueIncrement: true }, + b: { uniqueIncrement: true }, + aAndB: { intersection: ["a", "b"] }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/test-unique.ts b/packages/backend/src/services/chart/charts/entities/test-unique.ts index b2cfb71b0..03b8a7653 100644 --- a/packages/backend/src/services/chart/charts/entities/test-unique.ts +++ b/packages/backend/src/services/chart/charts/entities/test-unique.ts @@ -1,9 +1,9 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'testUnique'; +export const name = "testUnique"; export const schema = { - 'foo': { uniqueIncrement: true }, + foo: { uniqueIncrement: true }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/test.ts b/packages/backend/src/services/chart/charts/entities/test.ts index 7cba21e16..a11d53e32 100644 --- a/packages/backend/src/services/chart/charts/entities/test.ts +++ b/packages/backend/src/services/chart/charts/entities/test.ts @@ -1,11 +1,11 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'test'; +export const name = "test"; export const schema = { - 'foo.total': { accumulate: true }, - 'foo.inc': {}, - 'foo.dec': {}, + "foo.total": { accumulate: true }, + "foo.inc": {}, + "foo.dec": {}, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/entities/users.ts b/packages/backend/src/services/chart/charts/entities/users.ts index c0b83094a..a9544d77b 100644 --- a/packages/backend/src/services/chart/charts/entities/users.ts +++ b/packages/backend/src/services/chart/charts/entities/users.ts @@ -1,14 +1,14 @@ -import Chart from '../../core.js'; +import Chart from "../../core.js"; -export const name = 'users'; +export const name = "users"; export const schema = { - 'local.total': { accumulate: true }, - 'local.inc': { range: 'small' }, - 'local.dec': { range: 'small' }, - 'remote.total': { accumulate: true }, - 'remote.inc': { range: 'small' }, - 'remote.dec': { range: 'small' }, + "local.total": { accumulate: true }, + "local.inc": { range: "small" }, + "local.dec": { range: "small" }, + "remote.total": { accumulate: true }, + "remote.inc": { range: "small" }, + "remote.dec": { range: "small" }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/federation.ts b/packages/backend/src/services/chart/charts/federation.ts index 10221ee1e..41674a11a 100644 --- a/packages/backend/src/services/chart/charts/federation.ts +++ b/packages/backend/src/services/chart/charts/federation.ts @@ -1,7 +1,8 @@ -import Chart, { KVs } from '../core.js'; -import { Followings, Instances } from '@/models/index.js'; -import { name, schema } from './entities/federation.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { Followings, Instances } from "@/models/index.js"; +import { name, schema } from "./entities/federation.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; /** * フェデレーションに関するチャート @@ -13,91 +14,129 @@ export default class FederationChart extends Chart { } protected async tickMajor(): Promise>> { - return { - }; + return {}; } protected async tickMinor(): Promise>> { const meta = await fetchMeta(); - const suspendedInstancesQuery = Instances.createQueryBuilder('instance') - .select('instance.host') - .where('instance.isSuspended = true'); + const suspendedInstancesQuery = Instances.createQueryBuilder("instance") + .select("instance.host") + .where("instance.isSuspended = true"); - const pubsubSubQuery = Followings.createQueryBuilder('f') - .select('f.followerHost') - .where('f.followerHost IS NOT NULL'); + const pubsubSubQuery = Followings.createQueryBuilder("f") + .select("f.followerHost") + .where("f.followerHost IS NOT NULL"); - const subInstancesQuery = Followings.createQueryBuilder('f') - .select('f.followeeHost') - .where('f.followeeHost IS NOT NULL'); + const subInstancesQuery = Followings.createQueryBuilder("f") + .select("f.followeeHost") + .where("f.followeeHost IS NOT NULL"); - const pubInstancesQuery = Followings.createQueryBuilder('f') - .select('f.followerHost') - .where('f.followerHost IS NOT NULL'); + const pubInstancesQuery = Followings.createQueryBuilder("f") + .select("f.followerHost") + .where("f.followerHost IS NOT NULL"); const [sub, pub, pubsub, subActive, pubActive] = await Promise.all([ - Followings.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(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) + Followings.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( + `following.followeeHost NOT IN (${suspendedInstancesQuery.getQuery()})`, + ) .getRawOne() - .then(x => parseInt(x.count, 10)), - Followings.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(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) + .then((x) => parseInt(x.count, 10)), + Followings.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( + `following.followerHost NOT IN (${suspendedInstancesQuery.getQuery()})`, + ) .getRawOne() - .then(x => parseInt(x.count, 10)), - Followings.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(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) - .andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`) + .then((x) => parseInt(x.count, 10)), + Followings.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( + `following.followeeHost NOT IN (${suspendedInstancesQuery.getQuery()})`, + ) + .andWhere(`following.followeeHost IN (${pubsubSubQuery.getQuery()})`) .setParameters(pubsubSubQuery.getParameters()) .getRawOne() - .then(x => parseInt(x.count, 10)), - Instances.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(`instance.isSuspended = false`) - .andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) + .then((x) => parseInt(x.count, 10)), + Instances.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("instance.isSuspended = false") + .andWhere("instance.lastCommunicatedAt > :gt", { + gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), + }) .getRawOne() - .then(x => parseInt(x.count, 10)), - Instances.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(`instance.isSuspended = false`) - .andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) + .then((x) => parseInt(x.count, 10)), + Instances.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("instance.isSuspended = false") + .andWhere("instance.lastCommunicatedAt > :gt", { + gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), + }) .getRawOne() - .then(x => parseInt(x.count, 10)), + .then((x) => parseInt(x.count, 10)), ]); return { - 'sub': sub, - 'pub': pub, - 'pubsub': pubsub, - 'subActive': subActive, - 'pubActive': pubActive, + sub: sub, + pub: pub, + pubsub: pubsub, + subActive: subActive, + pubActive: pubActive, }; } public async deliverd(host: string, succeeded: boolean): Promise { - await this.commit(succeeded ? { - 'deliveredInstances': [host], - } : { - 'stalled': [host], - }); + await this.commit( + succeeded + ? { + deliveredInstances: [host], + } + : { + stalled: [host], + }, + ); } public async inbox(host: string): Promise { await this.commit({ - 'inboxInstances': [host], + inboxInstances: [host], }); } } diff --git a/packages/backend/src/services/chart/charts/hashtag.ts b/packages/backend/src/services/chart/charts/hashtag.ts index 31f7fa95d..4c327cff5 100644 --- a/packages/backend/src/services/chart/charts/hashtag.ts +++ b/packages/backend/src/services/chart/charts/hashtag.ts @@ -1,7 +1,8 @@ -import Chart, { KVs } from '../core.js'; -import { User } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; -import { name, schema } from './entities/hashtag.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import type { User } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; +import { name, schema } from "./entities/hashtag.js"; /** * ハッシュタグに関するチャート @@ -20,10 +21,16 @@ export default class HashtagChart extends Chart { return {}; } - public async update(hashtag: string, user: { id: User['id'], host: User['host'] }): Promise { - await this.commit({ - 'local.users': Users.isLocalUser(user) ? [user.id] : [], - 'remote.users': Users.isLocalUser(user) ? [] : [user.id], - }, hashtag); + public async update( + hashtag: string, + user: { id: User["id"]; host: User["host"] }, + ): Promise { + await this.commit( + { + "local.users": Users.isLocalUser(user) ? [user.id] : [], + "remote.users": Users.isLocalUser(user) ? [] : [user.id], + }, + hashtag, + ); } } diff --git a/packages/backend/src/services/chart/charts/instance.ts b/packages/backend/src/services/chart/charts/instance.ts index fe29ba522..d5bd86a09 100644 --- a/packages/backend/src/services/chart/charts/instance.ts +++ b/packages/backend/src/services/chart/charts/instance.ts @@ -1,9 +1,10 @@ -import Chart, { KVs } from '../core.js'; -import { DriveFiles, Followings, Users, Notes } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { Note } from '@/models/entities/note.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { name, schema } from './entities/instance.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { DriveFiles, Followings, Users, Notes } from "@/models/index.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { Note } from "@/models/entities/note.js"; +import { toPuny } from "@/misc/convert-host.js"; +import { name, schema } from "./entities/instance.js"; /** * インスタンスごとのチャート @@ -14,27 +15,24 @@ export default class InstanceChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { - const [ - notesCount, - usersCount, - followingCount, - followersCount, - driveFiles, - ] = await Promise.all([ - Notes.countBy({ userHost: group }), - Users.countBy({ host: group }), - Followings.countBy({ followerHost: group }), - Followings.countBy({ followeeHost: group }), - DriveFiles.countBy({ userHost: group }), - ]); + protected async tickMajor( + group: string, + ): Promise>> { + const [notesCount, usersCount, followingCount, followersCount, driveFiles] = + await Promise.all([ + Notes.countBy({ userHost: group }), + Users.countBy({ host: group }), + Followings.countBy({ followerHost: group }), + Followings.countBy({ followeeHost: group }), + DriveFiles.countBy({ userHost: group }), + ]); return { - 'notes.total': notesCount, - 'users.total': usersCount, - 'following.total': followingCount, - 'followers.total': followersCount, - 'drive.totalFiles': driveFiles, + "notes.total": notesCount, + "users.total": usersCount, + "following.total": followingCount, + "followers.total": followersCount, + "drive.totalFiles": driveFiles, }; } @@ -43,61 +41,102 @@ export default class InstanceChart extends Chart { } public async requestReceived(host: string): Promise { - await this.commit({ - 'requests.received': 1, - }, toPuny(host)); + await this.commit( + { + "requests.received": 1, + }, + toPuny(host), + ); } public async requestSent(host: string, isSucceeded: boolean): Promise { - await this.commit({ - 'requests.succeeded': isSucceeded ? 1 : 0, - 'requests.failed': isSucceeded ? 0 : 1, - }, toPuny(host)); + await this.commit( + { + "requests.succeeded": isSucceeded ? 1 : 0, + "requests.failed": isSucceeded ? 0 : 1, + }, + toPuny(host), + ); } public async newUser(host: string): Promise { - await this.commit({ - 'users.total': 1, - 'users.inc': 1, - }, toPuny(host)); + await this.commit( + { + "users.total": 1, + "users.inc": 1, + }, + toPuny(host), + ); } - public async updateNote(host: string, note: Note, isAdditional: boolean): Promise { - await this.commit({ - 'notes.total': isAdditional ? 1 : -1, - 'notes.inc': isAdditional ? 1 : 0, - 'notes.dec': isAdditional ? 0 : 1, - 'notes.diffs.normal': note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0, - 'notes.diffs.renote': note.renoteId != null ? (isAdditional ? 1 : -1) : 0, - 'notes.diffs.reply': note.replyId != null ? (isAdditional ? 1 : -1) : 0, - 'notes.diffs.withFile': note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, - }, toPuny(host)); + public async updateNote( + host: string, + note: Note, + isAdditional: boolean, + ): Promise { + await this.commit( + { + "notes.total": isAdditional ? 1 : -1, + "notes.inc": isAdditional ? 1 : 0, + "notes.dec": isAdditional ? 0 : 1, + "notes.diffs.normal": + note.replyId == null && note.renoteId == null + ? isAdditional + ? 1 + : -1 + : 0, + "notes.diffs.renote": + note.renoteId != null ? (isAdditional ? 1 : -1) : 0, + "notes.diffs.reply": note.replyId != null ? (isAdditional ? 1 : -1) : 0, + "notes.diffs.withFile": + note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, + }, + toPuny(host), + ); } - public async updateFollowing(host: string, isAdditional: boolean): Promise { - await this.commit({ - 'following.total': isAdditional ? 1 : -1, - 'following.inc': isAdditional ? 1 : 0, - 'following.dec': isAdditional ? 0 : 1, - }, toPuny(host)); + public async updateFollowing( + host: string, + isAdditional: boolean, + ): Promise { + await this.commit( + { + "following.total": isAdditional ? 1 : -1, + "following.inc": isAdditional ? 1 : 0, + "following.dec": isAdditional ? 0 : 1, + }, + toPuny(host), + ); } - public async updateFollowers(host: string, isAdditional: boolean): Promise { - await this.commit({ - 'followers.total': isAdditional ? 1 : -1, - 'followers.inc': isAdditional ? 1 : 0, - 'followers.dec': isAdditional ? 0 : 1, - }, toPuny(host)); + public async updateFollowers( + host: string, + isAdditional: boolean, + ): Promise { + await this.commit( + { + "followers.total": isAdditional ? 1 : -1, + "followers.inc": isAdditional ? 1 : 0, + "followers.dec": isAdditional ? 0 : 1, + }, + toPuny(host), + ); } - public async updateDrive(file: DriveFile, isAdditional: boolean): Promise { + public async updateDrive( + file: DriveFile, + isAdditional: boolean, + ): Promise { const fileSizeKb = file.size / 1000; - await this.commit({ - 'drive.totalFiles': isAdditional ? 1 : -1, - 'drive.incFiles': isAdditional ? 1 : 0, - 'drive.incUsage': isAdditional ? fileSizeKb : 0, - 'drive.decFiles': isAdditional ? 1 : 0, - 'drive.decUsage': isAdditional ? fileSizeKb : 0, - }, file.userHost); + await this.commit( + { + "drive.totalFiles": isAdditional ? 1 : -1, + "drive.incFiles": isAdditional ? 1 : 0, + "drive.incUsage": isAdditional ? fileSizeKb : 0, + "drive.decFiles": isAdditional ? 1 : 0, + "drive.decUsage": isAdditional ? fileSizeKb : 0, + }, + file.userHost, + ); } } diff --git a/packages/backend/src/services/chart/charts/notes.ts b/packages/backend/src/services/chart/charts/notes.ts index bb14b62f3..181357f5b 100644 --- a/packages/backend/src/services/chart/charts/notes.ts +++ b/packages/backend/src/services/chart/charts/notes.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { Notes } from '@/models/index.js'; -import { Not, IsNull } from 'typeorm'; -import { Note } from '@/models/entities/note.js'; -import { name, schema } from './entities/notes.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { Notes } from "@/models/index.js"; +import { Not, IsNull } from "typeorm"; +import type { Note } from "@/models/entities/note.js"; +import { name, schema } from "./entities/notes.js"; /** * ノートに関するチャート @@ -20,8 +21,8 @@ export default class NotesChart extends Chart { ]); return { - 'local.total': localCount, - 'remote.total': remoteCount, + "local.total": localCount, + "remote.total": remoteCount, }; } @@ -30,16 +31,24 @@ export default class NotesChart extends Chart { } public async update(note: Note, isAdditional: boolean): Promise { - const prefix = note.userHost === null ? 'local' : 'remote'; + const prefix = note.userHost === null ? "local" : "remote"; await this.commit({ [`${prefix}.total`]: isAdditional ? 1 : -1, [`${prefix}.inc`]: isAdditional ? 1 : 0, [`${prefix}.dec`]: isAdditional ? 0 : 1, - [`${prefix}.diffs.normal`]: note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0, - [`${prefix}.diffs.renote`]: note.renoteId != null ? (isAdditional ? 1 : -1) : 0, - [`${prefix}.diffs.reply`]: note.replyId != null ? (isAdditional ? 1 : -1) : 0, - [`${prefix}.diffs.withFile`]: note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, + [`${prefix}.diffs.normal`]: + note.replyId == null && note.renoteId == null + ? isAdditional + ? 1 + : -1 + : 0, + [`${prefix}.diffs.renote`]: + note.renoteId != null ? (isAdditional ? 1 : -1) : 0, + [`${prefix}.diffs.reply`]: + note.replyId != null ? (isAdditional ? 1 : -1) : 0, + [`${prefix}.diffs.withFile`]: + note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, }); } } diff --git a/packages/backend/src/services/chart/charts/per-user-drive.ts b/packages/backend/src/services/chart/charts/per-user-drive.ts index 5f75dc688..c5f002692 100644 --- a/packages/backend/src/services/chart/charts/per-user-drive.ts +++ b/packages/backend/src/services/chart/charts/per-user-drive.ts @@ -1,7 +1,8 @@ -import Chart, { KVs } from '../core.js'; -import { DriveFiles } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { name, schema } from './entities/per-user-drive.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { DriveFiles } from "@/models/index.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { name, schema } from "./entities/per-user-drive.js"; /** * ユーザーごとのドライブに関するチャート @@ -12,15 +13,17 @@ export default class PerUserDriveChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { + protected async tickMajor( + group: string, + ): Promise>> { const [count, size] = await Promise.all([ DriveFiles.countBy({ userId: group }), DriveFiles.calcDriveUsageOf(group), ]); return { - 'totalCount': count, - 'totalSize': size, + totalCount: count, + totalSize: size, }; } @@ -30,13 +33,16 @@ export default class PerUserDriveChart extends Chart { public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; - await this.commit({ - 'totalCount': isAdditional ? 1 : -1, - 'totalSize': isAdditional ? fileSizeKb : -fileSizeKb, - 'incCount': isAdditional ? 1 : 0, - 'incSize': isAdditional ? fileSizeKb : 0, - 'decCount': isAdditional ? 0 : 1, - 'decSize': isAdditional ? 0 : fileSizeKb, - }, file.userId); + await this.commit( + { + totalCount: isAdditional ? 1 : -1, + totalSize: isAdditional ? fileSizeKb : -fileSizeKb, + incCount: isAdditional ? 1 : 0, + incSize: isAdditional ? fileSizeKb : 0, + decCount: isAdditional ? 0 : 1, + decSize: isAdditional ? 0 : fileSizeKb, + }, + file.userId, + ); } } diff --git a/packages/backend/src/services/chart/charts/per-user-following.ts b/packages/backend/src/services/chart/charts/per-user-following.ts index 02b149f52..f21d01c38 100644 --- a/packages/backend/src/services/chart/charts/per-user-following.ts +++ b/packages/backend/src/services/chart/charts/per-user-following.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { Followings, Users } from '@/models/index.js'; -import { Not, IsNull } from 'typeorm'; -import { User } from '@/models/entities/user.js'; -import { name, schema } from './entities/per-user-following.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { Followings, Users } from "@/models/index.js"; +import { Not, IsNull } from "typeorm"; +import type { User } from "@/models/entities/user.js"; +import { name, schema } from "./entities/per-user-following.js"; /** * ユーザーごとのフォローに関するチャート @@ -13,7 +14,9 @@ export default class PerUserFollowingChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { + protected async tickMajor( + group: string, + ): Promise>> { const [ localFollowingsCount, localFollowersCount, @@ -27,10 +30,10 @@ export default class PerUserFollowingChart extends Chart { ]); return { - 'local.followings.total': localFollowingsCount, - 'local.followers.total': localFollowersCount, - 'remote.followings.total': remoteFollowingsCount, - 'remote.followers.total': remoteFollowersCount, + "local.followings.total": localFollowingsCount, + "local.followers.total": localFollowersCount, + "remote.followings.total": remoteFollowingsCount, + "remote.followers.total": remoteFollowersCount, }; } @@ -38,19 +41,29 @@ export default class PerUserFollowingChart extends Chart { return {}; } - public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise { - const prefixFollower = Users.isLocalUser(follower) ? 'local' : 'remote'; - const prefixFollowee = Users.isLocalUser(followee) ? 'local' : 'remote'; + public async update( + follower: { id: User["id"]; host: User["host"] }, + followee: { id: User["id"]; host: User["host"] }, + isFollow: boolean, + ): Promise { + const prefixFollower = Users.isLocalUser(follower) ? "local" : "remote"; + const prefixFollowee = Users.isLocalUser(followee) ? "local" : "remote"; - this.commit({ - [`${prefixFollower}.followings.total`]: isFollow ? 1 : -1, - [`${prefixFollower}.followings.inc`]: isFollow ? 1 : 0, - [`${prefixFollower}.followings.dec`]: isFollow ? 0 : 1, - }, follower.id); - this.commit({ - [`${prefixFollowee}.followers.total`]: isFollow ? 1 : -1, - [`${prefixFollowee}.followers.inc`]: isFollow ? 1 : 0, - [`${prefixFollowee}.followers.dec`]: isFollow ? 0 : 1, - }, followee.id); + this.commit( + { + [`${prefixFollower}.followings.total`]: isFollow ? 1 : -1, + [`${prefixFollower}.followings.inc`]: isFollow ? 1 : 0, + [`${prefixFollower}.followings.dec`]: isFollow ? 0 : 1, + }, + follower.id, + ); + this.commit( + { + [`${prefixFollowee}.followers.total`]: isFollow ? 1 : -1, + [`${prefixFollowee}.followers.inc`]: isFollow ? 1 : 0, + [`${prefixFollowee}.followers.dec`]: isFollow ? 0 : 1, + }, + followee.id, + ); } } diff --git a/packages/backend/src/services/chart/charts/per-user-notes.ts b/packages/backend/src/services/chart/charts/per-user-notes.ts index b9191dd08..c042cc71c 100644 --- a/packages/backend/src/services/chart/charts/per-user-notes.ts +++ b/packages/backend/src/services/chart/charts/per-user-notes.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { User } from '@/models/entities/user.js'; -import { Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { name, schema } from './entities/per-user-notes.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import type { User } from "@/models/entities/user.js"; +import { Notes } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; +import { name, schema } from "./entities/per-user-notes.js"; /** * ユーザーごとのノートに関するチャート @@ -13,10 +14,10 @@ export default class PerUserNotesChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { - const [count] = await Promise.all([ - Notes.countBy({ userId: group }), - ]); + protected async tickMajor( + group: string, + ): Promise>> { + const [count] = await Promise.all([Notes.countBy({ userId: group })]); return { total: count, @@ -27,15 +28,27 @@ export default class PerUserNotesChart extends Chart { return {}; } - public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise { - await this.commit({ - 'total': isAdditional ? 1 : -1, - 'inc': isAdditional ? 1 : 0, - 'dec': isAdditional ? 0 : 1, - 'diffs.normal': note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0, - 'diffs.renote': note.renoteId != null ? (isAdditional ? 1 : -1) : 0, - 'diffs.reply': note.replyId != null ? (isAdditional ? 1 : -1) : 0, - 'diffs.withFile': note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, - }, user.id); + public async update( + user: { id: User["id"] }, + note: Note, + isAdditional: boolean, + ): Promise { + await this.commit( + { + total: isAdditional ? 1 : -1, + inc: isAdditional ? 1 : 0, + dec: isAdditional ? 0 : 1, + "diffs.normal": + note.replyId == null && note.renoteId == null + ? isAdditional + ? 1 + : -1 + : 0, + "diffs.renote": note.renoteId != null ? (isAdditional ? 1 : -1) : 0, + "diffs.reply": note.replyId != null ? (isAdditional ? 1 : -1) : 0, + "diffs.withFile": note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0, + }, + user.id, + ); } } diff --git a/packages/backend/src/services/chart/charts/per-user-reactions.ts b/packages/backend/src/services/chart/charts/per-user-reactions.ts index 3a830e118..f1d3d8680 100644 --- a/packages/backend/src/services/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/services/chart/charts/per-user-reactions.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { Users } from '@/models/index.js'; -import { name, schema } from './entities/per-user-reactions.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import type { User } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import { Users } from "@/models/index.js"; +import { name, schema } from "./entities/per-user-reactions.js"; /** * ユーザーごとのリアクションに関するチャート @@ -13,7 +14,9 @@ export default class PerUserReactionsChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { + protected async tickMajor( + group: string, + ): Promise>> { return {}; } @@ -21,10 +24,16 @@ export default class PerUserReactionsChart extends Chart { return {}; } - public async update(user: { id: User['id'], host: User['host'] }, note: Note): Promise { - const prefix = Users.isLocalUser(user) ? 'local' : 'remote'; - this.commit({ - [`${prefix}.count`]: 1, - }, note.userId); + public async update( + user: { id: User["id"]; host: User["host"] }, + note: Note, + ): Promise { + const prefix = Users.isLocalUser(user) ? "local" : "remote"; + this.commit( + { + [`${prefix}.count`]: 1, + }, + note.userId, + ); } } diff --git a/packages/backend/src/services/chart/charts/test-grouped.ts b/packages/backend/src/services/chart/charts/test-grouped.ts index d01c9fcbd..744547af5 100644 --- a/packages/backend/src/services/chart/charts/test-grouped.ts +++ b/packages/backend/src/services/chart/charts/test-grouped.ts @@ -1,5 +1,6 @@ -import Chart, { KVs } from '../core.js'; -import { name, schema } from './entities/test-grouped.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { name, schema } from "./entities/test-grouped.js"; /** * For testing @@ -12,9 +13,11 @@ export default class TestGroupedChart extends Chart { super(name, schema, true); } - protected async tickMajor(group: string): Promise>> { + protected async tickMajor( + group: string, + ): Promise>> { return { - 'foo.total': this.total[group], + "foo.total": this.total[group], }; } @@ -27,9 +30,12 @@ export default class TestGroupedChart extends Chart { this.total[group]++; - await this.commit({ - 'foo.total': 1, - 'foo.inc': 1, - }, group); + await this.commit( + { + "foo.total": 1, + "foo.inc": 1, + }, + group, + ); } } diff --git a/packages/backend/src/services/chart/charts/test-intersection.ts b/packages/backend/src/services/chart/charts/test-intersection.ts index 88b5a715c..d4de290c1 100644 --- a/packages/backend/src/services/chart/charts/test-intersection.ts +++ b/packages/backend/src/services/chart/charts/test-intersection.ts @@ -1,5 +1,6 @@ -import Chart, { KVs } from '../core.js'; -import { name, schema } from './entities/test-intersection.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { name, schema } from "./entities/test-intersection.js"; /** * For testing diff --git a/packages/backend/src/services/chart/charts/test-unique.ts b/packages/backend/src/services/chart/charts/test-unique.ts index d714f1d40..fdbf74528 100644 --- a/packages/backend/src/services/chart/charts/test-unique.ts +++ b/packages/backend/src/services/chart/charts/test-unique.ts @@ -1,5 +1,6 @@ -import Chart, { KVs } from '../core.js'; -import { name, schema } from './entities/test-unique.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { name, schema } from "./entities/test-unique.js"; /** * For testing diff --git a/packages/backend/src/services/chart/charts/test.ts b/packages/backend/src/services/chart/charts/test.ts index adb2b18c8..bec149a53 100644 --- a/packages/backend/src/services/chart/charts/test.ts +++ b/packages/backend/src/services/chart/charts/test.ts @@ -1,5 +1,6 @@ -import Chart, { KVs } from '../core.js'; -import { name, schema } from './entities/test.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { name, schema } from "./entities/test.js"; /** * For testing @@ -14,7 +15,7 @@ export default class TestChart extends Chart { protected async tickMajor(): Promise>> { return { - 'foo.total': this.total, + "foo.total": this.total, }; } @@ -26,8 +27,8 @@ export default class TestChart extends Chart { this.total++; await this.commit({ - 'foo.total': 1, - 'foo.inc': 1, + "foo.total": 1, + "foo.inc": 1, }); } @@ -35,8 +36,8 @@ export default class TestChart extends Chart { this.total--; await this.commit({ - 'foo.total': -1, - 'foo.dec': 1, + "foo.total": -1, + "foo.dec": 1, }); } } diff --git a/packages/backend/src/services/chart/charts/users.ts b/packages/backend/src/services/chart/charts/users.ts index acb16ead8..29cd9abe9 100644 --- a/packages/backend/src/services/chart/charts/users.ts +++ b/packages/backend/src/services/chart/charts/users.ts @@ -1,8 +1,9 @@ -import Chart, { KVs } from '../core.js'; -import { Users } from '@/models/index.js'; -import { Not, IsNull } from 'typeorm'; -import { User } from '@/models/entities/user.js'; -import { name, schema } from './entities/users.js'; +import type { KVs } from "../core.js"; +import Chart from "../core.js"; +import { Users } from "@/models/index.js"; +import { Not, IsNull } from "typeorm"; +import type { User } from "@/models/entities/user.js"; +import { name, schema } from "./entities/users.js"; /** * ユーザー数に関するチャート @@ -20,8 +21,8 @@ export default class UsersChart extends Chart { ]); return { - 'local.total': localCount, - 'remote.total': remoteCount, + "local.total": localCount, + "remote.total": remoteCount, }; } @@ -29,8 +30,11 @@ export default class UsersChart extends Chart { return {}; } - public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise { - const prefix = Users.isLocalUser(user) ? 'local' : 'remote'; + public async update( + user: { id: User["id"]; host: User["host"] }, + isAdditional: boolean, + ): Promise { + const prefix = Users.isLocalUser(user) ? "local" : "remote"; await this.commit({ [`${prefix}.total`]: isAdditional ? 1 : -1, diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index 2960bac8f..1ad8e6584 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -4,38 +4,54 @@ * Tests located in test/chart */ -import * as nestedProperty from 'nested-property'; -import Logger from '../logger.js'; -import { EntitySchema, Repository, LessThan, Between } from 'typeorm'; -import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time.js'; -import { getChartInsertLock } from '@/misc/app-lock.js'; -import { db } from '@/db/postgre.js'; +import * as nestedProperty from "nested-property"; +import Logger from "../logger.js"; +import type { Repository } from "typeorm"; +import { EntitySchema, LessThan, Between } from "typeorm"; +import { + dateUTC, + isTimeSame, + isTimeBefore, + subtractTime, + addTime, +} from "@/prelude/time.js"; +import { getChartInsertLock } from "@/misc/app-lock.js"; +import { db } from "@/db/postgre.js"; -const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test'); +const logger = new Logger("chart", "white", process.env.NODE_ENV !== "test"); -const columnPrefix = '___' as const; -const uniqueTempColumnPrefix = 'unique_temp___' as const; -const columnDot = '_' as const; +const columnPrefix = "___" as const; +const uniqueTempColumnPrefix = "unique_temp___" as const; +const columnDot = "_" as const; -type Schema = Record; + intersection?: string[] | ReadonlyArray; - range?: 'big' | 'small' | 'medium'; + range?: "big" | "small" | "medium"; - // previousな値を引き継ぐかどうか - accumulate?: boolean; -}>; + // previousな値を引き継ぐかどうか + accumulate?: boolean; + } +>; -type KeyToColumnName = T extends `${infer R1}.${infer R2}` ? `${R1}${typeof columnDot}${KeyToColumnName}` : T; +type KeyToColumnName = T extends `${infer R1}.${infer R2}` + ? `${R1}${typeof columnDot}${KeyToColumnName}` + : T; type Columns = { - [K in keyof S as `${typeof columnPrefix}${KeyToColumnName}`]: number; + [K in + keyof S as `${typeof columnPrefix}${KeyToColumnName}`]: number; }; type TempColumnsForUnique = { - [K in keyof S as `${typeof uniqueTempColumnPrefix}${KeyToColumnName}`]: S[K]['uniqueIncrement'] extends true ? string[] : never; + [K in + keyof S as `${typeof uniqueTempColumnPrefix}${KeyToColumnName< + string & K + >}`]: S[K]["uniqueIncrement"] extends true ? string[] : never; }; type RawRecord = { @@ -50,16 +66,17 @@ type RawRecord = { * 集計日時のUnixタイムスタンプ(秒) */ date: number; -} & TempColumnsForUnique & Columns; +} & TempColumnsForUnique & + Columns; const camelToSnake = (str: string): string => { - return str.replace(/([A-Z])/g, s => '_' + s.charAt(0).toLowerCase()); + return str.replace(/([A-Z])/g, (s) => `_${s.charAt(0).toLowerCase()}`); }; const removeDuplicates = (array: any[]) => Array.from(new Set(array)); type Commit = { - [K in keyof S]?: S[K]['uniqueIncrement'] extends true ? string[] : number; + [K in keyof S]?: S[K]["uniqueIncrement"] extends true ? string[] : number; }; export type KVs = { @@ -70,11 +87,19 @@ type ChartResult = { [P in keyof T]: number[]; }; -type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never; +type UnionToIntersection = (T extends any ? (x: T) => any : never) extends ( + x: infer R, +) => any + ? R + : never; type UnflattenSingleton = K extends `${infer A}.${infer B}` - ? { [_ in A]: UnflattenSingleton; } - : { [_ in K]: V; }; + ? { + [_ in A]: UnflattenSingleton; + } + : { + [_ in K]: V; + }; type Unflatten> = UnionToIntersection< { @@ -83,24 +108,28 @@ type Unflatten> = UnionToIntersection< >; type ToJsonSchema = { - type: 'object'; + type: "object"; properties: { - [K in keyof S]: S[K] extends number[] ? { type: 'array'; items: { type: 'number'; }; } : ToJsonSchema; - }, + [K in keyof S]: S[K] extends number[] + ? { type: "array"; items: { type: "number" } } + : ToJsonSchema; + }; required: (keyof S)[]; }; -export function getJsonSchema(schema: S): ToJsonSchema>> { +export function getJsonSchema( + schema: S, +): ToJsonSchema>> { const jsonSchema = { - type: 'object', + type: "object", properties: {} as Record, required: [], }; for (const k in schema) { jsonSchema.properties[k] = { - type: 'array', - items: { type: 'number' }, + type: "array", + items: { type: "number" }, }; } @@ -122,8 +151,16 @@ export default abstract class Chart { // ↓にしたいけどfindOneとかで型エラーになる //private repositoryForHour: Repository>; //private repositoryForDay: Repository>; - private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>; - private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }>; + private repositoryForHour: Repository<{ + id: number; + group?: string | null; + date: number; + }>; + private repositoryForDay: Repository<{ + id: number; + group?: string | null; + date: number; + }>; /** * 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用) @@ -135,16 +172,26 @@ export default abstract class Chart { */ protected abstract tickMinor(group: string | null): Promise>>; - private static convertSchemaToColumnDefinitions(schema: Schema): Record { - const columns = {} as Record; + private static convertSchemaToColumnDefinitions( + schema: Schema, + ): Record { + const columns = {} as Record< + string, + { type: string; array?: boolean; default?: any } + >; for (const [k, v] of Object.entries(schema)) { - const name = k.replaceAll('.', columnDot); - const type = v.range === 'big' ? 'bigint' : v.range === 'small' ? 'smallint' : 'integer'; + const name = k.replaceAll(".", columnDot); + const type = + v.range === "big" + ? "bigint" + : v.range === "small" + ? "smallint" + : "integer"; if (v.uniqueIncrement) { columns[uniqueTempColumnPrefix + name] = { - type: 'varchar', + type: "varchar", array: true, - default: '{}', + default: "{}", }; columns[columnPrefix + name] = { type, @@ -164,7 +211,9 @@ export default abstract class Chart { return Math.floor(x.getTime() / 1000); } - private static parseDate(date: Date): [number, number, number, number, number, number, number] { + private static parseDate( + date: Date, + ): [number, number, number, number, number, number, number] { const y = date.getUTCFullYear(); const m = date.getUTCMonth(); const d = date.getUTCDate(); @@ -180,53 +229,66 @@ export default abstract class Chart { return Chart.parseDate(new Date()); } - public static schemaToEntity(name: string, schema: Schema, grouped = false): { - hour: EntitySchema, - day: EntitySchema, + public static schemaToEntity( + name: string, + schema: Schema, + grouped = false, + ): { + hour: EntitySchema; + day: EntitySchema; } { - const createEntity = (span: 'hour' | 'day'): EntitySchema => new EntitySchema({ - name: - span === 'hour' ? `__chart__${camelToSnake(name)}` : - span === 'day' ? `__chart_day__${camelToSnake(name)}` : - new Error('not happen') as never, - columns: { - id: { - type: 'integer', - primary: true, - generated: true, - }, - date: { - type: 'integer', - }, - ...(grouped ? { - group: { - type: 'varchar', - length: 128, + const createEntity = (span: "hour" | "day"): EntitySchema => + new EntitySchema({ + name: + span === "hour" + ? `__chart__${camelToSnake(name)}` + : span === "day" + ? `__chart_day__${camelToSnake(name)}` + : (new Error("not happen") as never), + columns: { + id: { + type: "integer", + primary: true, + generated: true, }, - } : {}), - ...Chart.convertSchemaToColumnDefinitions(schema), - }, - indices: [{ - columns: grouped ? ['date', 'group'] : ['date'], - unique: true, - }], - uniques: [{ - columns: grouped ? ['date', 'group'] : ['date'], - }], - relations: { - /* TODO + date: { + type: "integer", + }, + ...(grouped + ? { + group: { + type: "varchar", + length: 128, + }, + } + : {}), + ...Chart.convertSchemaToColumnDefinitions(schema), + }, + indices: [ + { + columns: grouped ? ["date", "group"] : ["date"], + unique: true, + }, + ], + uniques: [ + { + columns: grouped ? ["date", "group"] : ["date"], + }, + ], + relations: { + /* TODO group: { target: () => Foo, type: 'many-to-one', onDelete: 'CASCADE', }, */ - }, - }); + }, + }); return { - hour: createEntity('hour'), - day: createEntity('day'), + hour: createEntity("hour"), + day: createEntity("day"), }; } @@ -235,21 +297,36 @@ export default abstract class Chart { this.schema = schema; const { hour, day } = Chart.schemaToEntity(name, schema, grouped); - this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); - this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); + this.repositoryForHour = db.getRepository<{ + id: number; + group?: string | null; + date: number; + }>(hour); + this.repositoryForDay = db.getRepository<{ + id: number; + group?: string | null; + date: number; + }>(day); } private convertRawRecord(x: RawRecord): KVs { const kvs = {} as Record; - for (const k of Object.keys(x).filter((k) => k.startsWith(columnPrefix)) as (keyof Columns)[]) { - kvs[(k as string).substr(columnPrefix.length).split(columnDot).join('.')] = x[k]; + for (const k of Object.keys(x).filter((k) => + k.startsWith(columnPrefix), + ) as (keyof Columns)[]) { + kvs[ + (k as string).substr(columnPrefix.length).split(columnDot).join(".") + ] = x[k]; } return kvs as KVs; } 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]])[]) { + for (const [k, v] of Object.entries(this.schema) as [ + keyof typeof this["schema"], + this["schema"][string], + ][]) { if (v.accumulate && latest) { log[k] = latest[k]; } else { @@ -259,43 +336,60 @@ export default abstract class Chart { return log as KVs; } - private getLatestLog(group: string | null, span: 'hour' | 'day'): Promise | null> { + private getLatestLog( + group: string | null, + span: "hour" | "day", + ): Promise | null> { const repository = - span === 'hour' ? this.repositoryForHour : - span === 'day' ? this.repositoryForDay : - new Error('not happen') as never; + span === "hour" + ? this.repositoryForHour + : span === "day" + ? this.repositoryForDay + : (new Error("not happen") as never); - return repository.findOne({ - where: group ? { - group: group, - } : {}, - order: { - date: -1, - }, - }).then(x => x ?? null) as Promise | null>; + return repository + .findOne({ + where: group + ? { + group: group, + } + : {}, + order: { + date: -1, + }, + }) + .then((x) => x ?? null) as Promise | null>; } /** * 現在(=今のHour or Day)のログをデータベースから探して、あればそれを返し、なければ作成して返します。 */ - private async claimCurrentLog(group: string | null, span: 'hour' | 'day'): Promise> { + private async claimCurrentLog( + group: string | null, + span: "hour" | "day", + ): Promise> { const [y, m, d, h] = Chart.getCurrentDate(); const current = dateUTC( - span === 'hour' ? [y, m, d, h] : - span === 'day' ? [y, m, d] : - new Error('not happen') as never); + span === "hour" + ? [y, m, d, h] + : span === "day" + ? [y, m, d] + : (new Error("not happen") as never), + ); const repository = - span === 'hour' ? this.repositoryForHour : - span === 'day' ? this.repositoryForDay : - new Error('not happen') as never; + span === "hour" + ? this.repositoryForHour + : span === "day" + ? this.repositoryForDay + : (new Error("not happen") as never); // 現在(=今のHour or Day)のログ - const currentLog = await repository.findOneBy({ + const currentLog = (await repository.findOneBy({ date: Chart.dateToTimestamp(current), ...(group ? { group: group } : {}), - }) as RawRecord | undefined; + })) as RawRecord | undefined; // ログがあればそれを返して終了 if (currentLog != null) { @@ -323,37 +417,51 @@ export default abstract class Chart { // 初期ログデータを作成 data = this.getNewLog(null); - logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): Initial commit created`); + logger.info( + `${ + this.name + (group ? `:${group}` : "") + }(${span}): Initial commit created`, + ); } const date = Chart.dateToTimestamp(current); - const lockKey = group ? `${this.name}:${date}:${span}:${group}` : `${this.name}:${date}:${span}`; + const lockKey = group + ? `${this.name}:${date}:${span}:${group}` + : `${this.name}:${date}:${span}`; const unlock = await getChartInsertLock(lockKey); try { // ロック内でもう1回チェックする - const currentLog = await repository.findOneBy({ + const currentLog = (await repository.findOneBy({ date: date, ...(group ? { group: group } : {}), - }) as RawRecord | undefined; + })) as RawRecord | undefined; // ログがあればそれを返して終了 if (currentLog != null) return currentLog; const columns = {} as Record; for (const [k, v] of Object.entries(data)) { - const name = k.replaceAll('.', columnDot); + const name = k.replaceAll(".", columnDot); columns[columnPrefix + name] = v; } // 新規ログ挿入 - log = await repository.insert({ - date: date, - ...(group ? { group: group } : {}), - ...columns, - }).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord; + log = (await repository + .insert({ + date: date, + ...(group ? { group: group } : {}), + ...columns, + }) + .then((x) => + repository.findOneByOrFail(x.identifiers[0]), + )) as RawRecord; - logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`); + logger.info( + `${ + this.name + (group ? `:${group}` : "") + }(${span}): New commit created`, + ); return log; } finally { @@ -363,10 +471,12 @@ export default abstract class Chart { protected commit(diff: Commit, group: string | null = null): void { for (const [k, v] of Object.entries(diff)) { - if (v == null || v === 0 || (Array.isArray(v) && v.length === 0)) delete diff[k]; + if (v == null || v === 0 || (Array.isArray(v) && v.length === 0)) + diff[k] = undefined; } this.buffer.push({ - diff, group, + diff, + group, }); } @@ -382,49 +492,81 @@ export default abstract class Chart { // そのログは本来は 01:00~ のログとしてDBに保存されて欲しいのに、02:00~ のログ扱いになってしまう。 // これを回避するための実装は複雑になりそうなため、一旦保留。 - const update = async (logHour: RawRecord, logDay: RawRecord): Promise => { + const update = async ( + logHour: RawRecord, + logDay: RawRecord, + ): Promise => { const finalDiffs = {} as Record; - for (const diff of this.buffer.filter(q => q.group == null || (q.group === logHour.group)).map(q => q.diff)) { + for (const diff of this.buffer + .filter((q) => q.group == null || q.group === logHour.group) + .map((q) => q.diff)) { for (const [k, v] of Object.entries(diff)) { if (finalDiffs[k] == null) { finalDiffs[k] = v; } else { - if (typeof finalDiffs[k] === 'number') { + if (typeof finalDiffs[k] === "number") { (finalDiffs[k] as number) += v as number; } else { - (finalDiffs[k] as string[]) = (finalDiffs[k] as string[]).concat(v); + (finalDiffs[k] as string[]) = (finalDiffs[k] as string[]).concat( + v, + ); } } } } - const queryForHour: Record, number | (() => string)> = {} as any; - const queryForDay: Record, number | (() => string)> = {} as any; + const queryForHour: Record, number | (() => string)> = + {} as any; + const queryForDay: Record, number | (() => string)> = + {} as any; for (const [k, v] of Object.entries(finalDiffs)) { - if (typeof v === 'number') { - const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns; + if (typeof v === "number") { + const name = (columnPrefix + + k.replaceAll(".", columnDot)) as keyof Columns; if (v > 0) queryForHour[name] = () => `"${name}" + ${v}`; if (v < 0) queryForHour[name] = () => `"${name}" - ${Math.abs(v)}`; if (v > 0) queryForDay[name] = () => `"${name}" + ${v}`; if (v < 0) queryForDay[name] = () => `"${name}" - ${Math.abs(v)}`; - } else if (Array.isArray(v) && v.length > 0) { // ユニークインクリメント - const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique; + } else if (Array.isArray(v) && v.length > 0) { + // ユニークインクリメント + const tempColumnName = (uniqueTempColumnPrefix + + k.replaceAll(".", columnDot)) as keyof TempColumnsForUnique; // TODO: item をSQLエスケープ - const itemsForHour = v.filter(item => !logHour[tempColumnName].includes(item)).map(item => `"${item}"`); - const itemsForDay = v.filter(item => !logDay[tempColumnName].includes(item)).map(item => `"${item}"`); - if (itemsForHour.length > 0) queryForHour[tempColumnName] = () => `array_cat("${tempColumnName}", '{${itemsForHour.join(',')}}'::varchar[])`; - if (itemsForDay.length > 0) queryForDay[tempColumnName] = () => `array_cat("${tempColumnName}", '{${itemsForDay.join(',')}}'::varchar[])`; + const itemsForHour = v + .filter((item) => !logHour[tempColumnName].includes(item)) + .map((item) => `"${item}"`); + const itemsForDay = v + .filter((item) => !logDay[tempColumnName].includes(item)) + .map((item) => `"${item}"`); + if (itemsForHour.length > 0) + queryForHour[tempColumnName] = () => + `array_cat("${tempColumnName}", '{${itemsForHour.join( + ",", + )}}'::varchar[])`; + if (itemsForDay.length > 0) + queryForDay[tempColumnName] = () => + `array_cat("${tempColumnName}", '{${itemsForDay.join( + ",", + )}}'::varchar[])`; } } // bake unique count for (const [k, v] of Object.entries(finalDiffs)) { if (this.schema[k].uniqueIncrement) { - const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns; - const tempColumnName = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique; - queryForHour[name] = new Set([...(v as string[]), ...logHour[tempColumnName]]).size; - queryForDay[name] = new Set([...(v as string[]), ...logDay[tempColumnName]]).size; + const name = (columnPrefix + + k.replaceAll(".", columnDot)) as keyof Columns; + const tempColumnName = (uniqueTempColumnPrefix + + k.replaceAll(".", columnDot)) as keyof TempColumnsForUnique; + queryForHour[name] = new Set([ + ...(v as string[]), + ...logHour[tempColumnName], + ]).size; + queryForDay[name] = new Set([ + ...(v as string[]), + ...logDay[tempColumnName], + ]).size; } } @@ -433,22 +575,43 @@ export default abstract class Chart { for (const [k, v] of Object.entries(this.schema)) { const intersection = v.intersection; if (intersection) { - const name = columnPrefix + k.replaceAll('.', columnDot) as keyof Columns; + const name = (columnPrefix + + k.replaceAll(".", columnDot)) as keyof Columns; const firstKey = intersection[0]; - const firstTempColumnName = uniqueTempColumnPrefix + firstKey.replaceAll('.', columnDot) as keyof TempColumnsForUnique; + const firstTempColumnName = (uniqueTempColumnPrefix + + firstKey.replaceAll( + ".", + columnDot, + )) as keyof TempColumnsForUnique; const firstValues = finalDiffs[firstKey] as string[] | undefined; - const currentValuesForHour = new Set([...(firstValues ?? []), ...logHour[firstTempColumnName]]); - const currentValuesForDay = new Set([...(firstValues ?? []), ...logDay[firstTempColumnName]]); + const currentValuesForHour = new Set([ + ...(firstValues ?? []), + ...logHour[firstTempColumnName], + ]); + const currentValuesForDay = new Set([ + ...(firstValues ?? []), + ...logDay[firstTempColumnName], + ]); for (let i = 1; i < intersection.length; i++) { const targetKey = intersection[i]; - const targetTempColumnName = uniqueTempColumnPrefix + targetKey.replaceAll('.', columnDot) as keyof TempColumnsForUnique; + const targetTempColumnName = (uniqueTempColumnPrefix + + targetKey.replaceAll( + ".", + columnDot, + )) as keyof TempColumnsForUnique; const targetValues = finalDiffs[targetKey] as string[] | undefined; - const targetValuesForHour = new Set([...(targetValues ?? []), ...logHour[targetTempColumnName]]); - const targetValuesForDay = new Set([...(targetValues ?? []), ...logDay[targetTempColumnName]]); - currentValuesForHour.forEach(v => { + const targetValuesForHour = new Set([ + ...(targetValues ?? []), + ...logHour[targetTempColumnName], + ]); + const targetValuesForDay = new Set([ + ...(targetValues ?? []), + ...logDay[targetTempColumnName], + ]); + currentValuesForHour.forEach((v) => { if (!targetValuesForHour.has(v)) currentValuesForHour.delete(v); }); - currentValuesForDay.forEach(v => { + currentValuesForDay.forEach((v) => { if (!targetValuesForDay.has(v)) currentValuesForDay.delete(v); }); } @@ -459,41 +622,57 @@ export default abstract class Chart { // ログ更新 await Promise.all([ - this.repositoryForHour.createQueryBuilder() + this.repositoryForHour + .createQueryBuilder() .update() .set(queryForHour as any) - .where('id = :id', { id: logHour.id }) + .where("id = :id", { id: logHour.id }) .execute(), - this.repositoryForDay.createQueryBuilder() + this.repositoryForDay + .createQueryBuilder() .update() .set(queryForDay as any) - .where('id = :id', { id: logDay.id }) + .where("id = :id", { id: logDay.id }) .execute(), ]); - logger.info(`${this.name + (logHour.group ? `:${logHour.group}` : '')}: Updated`); + logger.info( + `${this.name + (logHour.group ? `:${logHour.group}` : "")}: Updated`, + ); // TODO: この一連の処理が始まった後に新たにbufferに入ったものは消さないようにする - this.buffer = this.buffer.filter(q => q.group != null && (q.group !== logHour.group)); + this.buffer = this.buffer.filter( + (q) => q.group != null && q.group !== logHour.group, + ); }; - const groups = removeDuplicates(this.buffer.map(log => log.group)); + const groups = removeDuplicates(this.buffer.map((log) => log.group)); await Promise.all( - groups.map(group => + groups.map((group) => Promise.all([ - this.claimCurrentLog(group, 'hour'), - this.claimCurrentLog(group, 'day'), - ]).then(([logHour, logDay]) => - update(logHour, logDay)))); + this.claimCurrentLog(group, "hour"), + this.claimCurrentLog(group, "day"), + ]).then(([logHour, logDay]) => update(logHour, logDay)), + ), + ); } - public async tick(major: boolean, group: string | null = null): Promise { - const data = major ? await this.tickMajor(group) : await this.tickMinor(group); + public async tick( + major: boolean, + group: string | null = null, + ): Promise { + const data = major + ? await this.tickMajor(group) + : await this.tickMinor(group); const columns = {} as Record, number>; - for (const [k, v] of Object.entries(data) as ([keyof typeof data, number])[]) { - const name = columnPrefix + (k as string).replaceAll('.', columnDot) as keyof Columns; + for (const [k, v] of Object.entries(data) as [ + keyof typeof data, + number, + ][]) { + const name = (columnPrefix + + (k as string).replaceAll(".", columnDot)) as keyof Columns; columns[name] = v; } @@ -501,26 +680,30 @@ export default abstract class Chart { return; } - const update = async (logHour: RawRecord, logDay: RawRecord): Promise => { + const update = async ( + logHour: RawRecord, + logDay: RawRecord, + ): Promise => { await Promise.all([ - this.repositoryForHour.createQueryBuilder() + this.repositoryForHour + .createQueryBuilder() .update() .set(columns) - .where('id = :id', { id: logHour.id }) + .where("id = :id", { id: logHour.id }) .execute(), - this.repositoryForDay.createQueryBuilder() + this.repositoryForDay + .createQueryBuilder() .update() .set(columns) - .where('id = :id', { id: logDay.id }) + .where("id = :id", { id: logDay.id }) .execute(), ]); }; return Promise.all([ - this.claimCurrentLog(group, 'hour'), - this.claimCurrentLog(group, 'day'), - ]).then(([logHour, logDay]) => - update(logHour, logDay)); + this.claimCurrentLog(group, "hour"), + this.claimCurrentLog(group, "day"), + ]).then(([logHour, logDay]) => update(logHour, logDay)); } public resync(group: string | null = null): Promise { @@ -531,13 +714,14 @@ export default abstract class Chart { const current = dateUTC(Chart.getCurrentDate()); // 一日以上前かつ三日以内 - const gt = Chart.dateToTimestamp(current) - (60 * 60 * 24 * 3); - const lt = Chart.dateToTimestamp(current) - (60 * 60 * 24); + const gt = Chart.dateToTimestamp(current) - 60 * 60 * 24 * 3; + const lt = Chart.dateToTimestamp(current) - 60 * 60 * 24; const columns = {} as Record, []>; for (const [k, v] of Object.entries(this.schema)) { if (v.uniqueIncrement) { - const name = uniqueTempColumnPrefix + k.replaceAll('.', columnDot) as keyof TempColumnsForUnique; + const name = (uniqueTempColumnPrefix + + k.replaceAll(".", columnDot)) as keyof TempColumnsForUnique; columns[name] = []; } } @@ -547,39 +731,62 @@ export default abstract class Chart { } await Promise.all([ - this.repositoryForHour.createQueryBuilder() + this.repositoryForHour + .createQueryBuilder() .update() .set(columns) - .where('date > :gt', { gt }) - .andWhere('date < :lt', { lt }) + .where("date > :gt", { gt }) + .andWhere("date < :lt", { lt }) .execute(), - this.repositoryForDay.createQueryBuilder() + this.repositoryForDay + .createQueryBuilder() .update() .set(columns) - .where('date > :gt', { gt }) - .andWhere('date < :lt', { lt }) + .where("date > :gt", { gt }) + .andWhere("date < :lt", { lt }) .execute(), ]); } - 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; + 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); const lt = dateUTC([y, m, d, h, _m, _s, _ms]); const gt = - span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') : - span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') : - new Error('not happen') as never; + span === "day" + ? subtractTime( + cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), + amount - 1, + "day", + ) + : span === "hour" + ? subtractTime( + cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), + amount - 1, + "hour", + ) + : (new Error("not happen") as never); const repository = - span === 'hour' ? this.repositoryForHour : - span === 'day' ? this.repositoryForDay : - new Error('not happen') as never; + span === "hour" + ? this.repositoryForHour + : span === "day" + ? this.repositoryForDay + : (new Error("not happen") as never); // ログ取得 - let logs = await repository.find({ + let logs = (await repository.find({ where: { date: Between(Chart.dateToTimestamp(gt), Chart.dateToTimestamp(lt)), ...(group ? { group: group } : {}), @@ -587,30 +794,32 @@ export default abstract class Chart { order: { date: -1, }, - }) as RawRecord[]; + })) as RawRecord[]; // 要求された範囲にログがひとつもなかったら if (logs.length === 0) { // もっとも新しいログを持ってくる // (すくなくともひとつログが無いと隙間埋めできないため) - const recentLog = await repository.findOne({ - where: group ? { - group: group, - } : {}, + const recentLog = (await repository.findOne({ + where: group + ? { + group: group, + } + : {}, order: { date: -1, }, - }) as RawRecord | undefined; + })) as RawRecord | undefined; if (recentLog) { logs = [recentLog]; } - // 要求された範囲の最も古い箇所に位置するログが存在しなかったら + // 要求された範囲の最も古い箇所に位置するログが存在しなかったら } else if (!isTimeSame(new Date(logs[logs.length - 1].date * 1000), gt)) { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する // (隙間埋めできないため) - const outdatedLog = await repository.findOne({ + const outdatedLog = (await repository.findOne({ where: { date: LessThan(Chart.dateToTimestamp(gt)), ...(group ? { group: group } : {}), @@ -618,7 +827,7 @@ export default abstract class Chart { order: { date: -1, }, - }) as RawRecord | undefined; + })) as RawRecord | undefined; if (outdatedLog) { logs.push(outdatedLog); @@ -627,19 +836,25 @@ export default abstract class Chart { const chart: KVs[] = []; - for (let i = (amount - 1); i >= 0; i--) { + for (let i = amount - 1; i >= 0; i--) { const current = - span === 'hour' ? subtractTime(dateUTC([y, m, d, h]), i, 'hour') : - span === 'day' ? subtractTime(dateUTC([y, m, d]), i, 'day') : - new Error('not happen') as never; + span === "hour" + ? subtractTime(dateUTC([y, m, d, h]), i, "hour") + : span === "day" + ? subtractTime(dateUTC([y, m, d]), i, "day") + : (new Error("not happen") as never); - const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current)); + const log = logs.find((l) => + isTimeSame(new Date(l.date * 1000), current), + ); if (log) { chart.unshift(this.convertRawRecord(log)); } else { // 隙間埋め - const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current)); + const latest = logs.find((l) => + isTimeBefore(new Date(l.date * 1000), current), + ); const data = latest ? this.convertRawRecord(latest) : null; chart.unshift(this.getNewLog(data)); } @@ -654,7 +869,10 @@ export default abstract class Chart { * にする */ for (const record of chart) { - for (const [k, v] of Object.entries(record) as ([keyof typeof record, number])[]) { + for (const [k, v] of Object.entries(record) as [ + keyof typeof record, + number, + ][]) { if (res[k]) { res[k].push(v); } else { @@ -666,7 +884,12 @@ export default abstract class Chart { return res; } - public async getChart(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise>> { + 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 = {}; for (const [k, v] of Object.entries(result)) { diff --git a/packages/backend/src/services/chart/entities.ts b/packages/backend/src/services/chart/entities.ts index a9eeabd63..e203dffdf 100644 --- a/packages/backend/src/services/chart/entities.ts +++ b/packages/backend/src/services/chart/entities.ts @@ -1,39 +1,57 @@ -import { entity as FederationChart } from './charts/entities/federation.js'; -import { entity as NotesChart } from './charts/entities/notes.js'; -import { entity as UsersChart } from './charts/entities/users.js'; -import { entity as ActiveUsersChart } from './charts/entities/active-users.js'; -import { entity as InstanceChart } from './charts/entities/instance.js'; -import { entity as PerUserNotesChart } from './charts/entities/per-user-notes.js'; -import { entity as DriveChart } from './charts/entities/drive.js'; -import { entity as PerUserReactionsChart } from './charts/entities/per-user-reactions.js'; -import { entity as HashtagChart } from './charts/entities/hashtag.js'; -import { entity as PerUserFollowingChart } from './charts/entities/per-user-following.js'; -import { entity as PerUserDriveChart } from './charts/entities/per-user-drive.js'; -import { entity as ApRequestChart } from './charts/entities/ap-request.js'; +import { entity as FederationChart } from "./charts/entities/federation.js"; +import { entity as NotesChart } from "./charts/entities/notes.js"; +import { entity as UsersChart } from "./charts/entities/users.js"; +import { entity as ActiveUsersChart } from "./charts/entities/active-users.js"; +import { entity as InstanceChart } from "./charts/entities/instance.js"; +import { entity as PerUserNotesChart } from "./charts/entities/per-user-notes.js"; +import { entity as DriveChart } from "./charts/entities/drive.js"; +import { entity as PerUserReactionsChart } from "./charts/entities/per-user-reactions.js"; +import { entity as HashtagChart } from "./charts/entities/hashtag.js"; +import { entity as PerUserFollowingChart } from "./charts/entities/per-user-following.js"; +import { entity as PerUserDriveChart } from "./charts/entities/per-user-drive.js"; +import { entity as ApRequestChart } from "./charts/entities/ap-request.js"; -import { entity as TestChart } from './charts/entities/test.js'; -import { entity as TestGroupedChart } from './charts/entities/test-grouped.js'; -import { entity as TestUniqueChart } from './charts/entities/test-unique.js'; -import { entity as TestIntersectionChart } from './charts/entities/test-intersection.js'; +import { entity as TestChart } from "./charts/entities/test.js"; +import { entity as TestGroupedChart } from "./charts/entities/test-grouped.js"; +import { entity as TestUniqueChart } from "./charts/entities/test-unique.js"; +import { entity as TestIntersectionChart } from "./charts/entities/test-intersection.js"; export const entities = [ - FederationChart.hour, FederationChart.day, - NotesChart.hour, NotesChart.day, - UsersChart.hour, UsersChart.day, - ActiveUsersChart.hour, ActiveUsersChart.day, - InstanceChart.hour, InstanceChart.day, - PerUserNotesChart.hour, PerUserNotesChart.day, - DriveChart.hour, DriveChart.day, - PerUserReactionsChart.hour, PerUserReactionsChart.day, - HashtagChart.hour, HashtagChart.day, - PerUserFollowingChart.hour, PerUserFollowingChart.day, - PerUserDriveChart.hour, PerUserDriveChart.day, - ApRequestChart.hour, ApRequestChart.day, + FederationChart.hour, + FederationChart.day, + NotesChart.hour, + NotesChart.day, + UsersChart.hour, + UsersChart.day, + ActiveUsersChart.hour, + ActiveUsersChart.day, + InstanceChart.hour, + InstanceChart.day, + PerUserNotesChart.hour, + PerUserNotesChart.day, + DriveChart.hour, + DriveChart.day, + PerUserReactionsChart.hour, + PerUserReactionsChart.day, + HashtagChart.hour, + HashtagChart.day, + PerUserFollowingChart.hour, + PerUserFollowingChart.day, + PerUserDriveChart.hour, + PerUserDriveChart.day, + ApRequestChart.hour, + ApRequestChart.day, - ...(process.env.NODE_ENV === 'test' ? [ - TestChart.hour, TestChart.day, - TestGroupedChart.hour, TestGroupedChart.day, - TestUniqueChart.hour, TestUniqueChart.day, - TestIntersectionChart.hour, TestIntersectionChart.day, - ] : []), + ...(process.env.NODE_ENV === "test" + ? [ + TestChart.hour, + TestChart.day, + TestGroupedChart.hour, + TestGroupedChart.day, + TestUniqueChart.hour, + TestUniqueChart.day, + TestIntersectionChart.hour, + TestIntersectionChart.day, + ] + : []), ]; diff --git a/packages/backend/src/services/chart/index.ts b/packages/backend/src/services/chart/index.ts index 8bf2d8f65..969cdab6d 100644 --- a/packages/backend/src/services/chart/index.ts +++ b/packages/backend/src/services/chart/index.ts @@ -1,17 +1,17 @@ -import { beforeShutdown } from '@/misc/before-shutdown.js'; +import { beforeShutdown } from "@/misc/before-shutdown.js"; -import FederationChart from './charts/federation.js'; -import NotesChart from './charts/notes.js'; -import UsersChart from './charts/users.js'; -import ActiveUsersChart from './charts/active-users.js'; -import InstanceChart from './charts/instance.js'; -import PerUserNotesChart from './charts/per-user-notes.js'; -import DriveChart from './charts/drive.js'; -import PerUserReactionsChart from './charts/per-user-reactions.js'; -import HashtagChart from './charts/hashtag.js'; -import PerUserFollowingChart from './charts/per-user-following.js'; -import PerUserDriveChart from './charts/per-user-drive.js'; -import ApRequestChart from './charts/ap-request.js'; +import FederationChart from "./charts/federation.js"; +import NotesChart from "./charts/notes.js"; +import UsersChart from "./charts/users.js"; +import ActiveUsersChart from "./charts/active-users.js"; +import InstanceChart from "./charts/instance.js"; +import PerUserNotesChart from "./charts/per-user-notes.js"; +import DriveChart from "./charts/drive.js"; +import PerUserReactionsChart from "./charts/per-user-reactions.js"; +import HashtagChart from "./charts/hashtag.js"; +import PerUserFollowingChart from "./charts/per-user-following.js"; +import PerUserDriveChart from "./charts/per-user-drive.js"; +import ApRequestChart from "./charts/ap-request.js"; export const federationChart = new FederationChart(); export const notesChart = new NotesChart(); @@ -48,4 +48,4 @@ setInterval(() => { } }, 1000 * 60 * 20); -beforeShutdown(() => Promise.all(charts.map(chart => chart.save()))); +beforeShutdown(() => Promise.all(charts.map((chart) => chart.save()))); diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index 646e2a2cb..f6545b131 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -1,17 +1,23 @@ -import { publishMainStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { Notifications, Mutings, NoteThreadMutings, UserProfiles, Users } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { User } from '@/models/entities/user.js'; -import { Notification } from '@/models/entities/notification.js'; -import { sendEmailNotification } from './send-email-notification.js'; +import { publishMainStream } from "@/services/stream.js"; +import { pushNotification } from "@/services/push-notification.js"; +import { + Notifications, + Mutings, + NoteThreadMutings, + UserProfiles, + Users, +} from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { User } from "@/models/entities/user.js"; +import type { Notification } from "@/models/entities/notification.js"; +import { sendEmailNotification } from "./send-email-notification.js"; export async function createNotification( - notifieeId: User['id'], - type: Notification['type'], - data: Partial + notifieeId: User["id"], + type: Notification["type"], + data: Partial, ) { - if (data.notifierId && (notifieeId === data.notifierId)) { + if (data.notifierId && notifieeId === data.notifierId) { return null; } @@ -39,13 +45,14 @@ export async function createNotification( // 相手がこの通知をミュートしているようなら、既読を予めつけておく isRead: isMuted, ...data, - } as Partial) - .then(x => Notifications.findOneByOrFail(x.identifiers[0])); + } as Partial).then((x) => + Notifications.findOneByOrFail(x.identifiers[0]), + ); const packed = await Notifications.pack(notification, {}); // Publish notification event - publishMainStream(notifieeId, 'notification', packed); + publishMainStream(notifieeId, "notification", packed); // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { @@ -57,16 +64,27 @@ export async function createNotification( const mutings = await Mutings.findBy({ muterId: notifieeId, }); - if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) { + if ( + data.notifierId && + mutings.map((m) => m.muteeId).includes(data.notifierId) + ) { return; } //#endregion - publishMainStream(notifieeId, 'unreadNotification', packed); - pushNotification(notifieeId, 'notification', packed); + publishMainStream(notifieeId, "unreadNotification", packed); + pushNotification(notifieeId, "notification", packed); - if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); - if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); + if (type === "follow") + sendEmailNotification.follow( + notifieeId, + await Users.findOneByOrFail({ id: data.notifierId! }), + ); + if (type === "receiveFollowRequest") + sendEmailNotification.receiveFollowRequest( + notifieeId, + await Users.findOneByOrFail({ id: data.notifierId! }), + ); }, 2000); return notification; diff --git a/packages/backend/src/services/create-system-user.ts b/packages/backend/src/services/create-system-user.ts index bae91ec4c..def3ee98f 100644 --- a/packages/backend/src/services/create-system-user.ts +++ b/packages/backend/src/services/create-system-user.ts @@ -1,14 +1,14 @@ -import bcrypt from 'bcryptjs'; -import { v4 as uuid } from 'uuid'; -import generateNativeUserToken from '../server/api/common/generate-native-user-token.js'; -import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; -import { User } from '@/models/entities/user.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { IsNull } from 'typeorm'; -import { genId } from '@/misc/gen-id.js'; -import { UserKeypair } from '@/models/entities/user-keypair.js'; -import { UsedUsername } from '@/models/entities/used-username.js'; -import { db } from '@/db/postgre.js'; +import bcrypt from "bcryptjs"; +import { v4 as uuid } from "uuid"; +import generateNativeUserToken from "../server/api/common/generate-native-user-token.js"; +import { genRsaKeyPair } from "@/misc/gen-key-pair.js"; +import { User } from "@/models/entities/user.js"; +import { UserProfile } from "@/models/entities/user-profile.js"; +import { IsNull } from "typeorm"; +import { genId } from "@/misc/gen-id.js"; +import { UserKeypair } from "@/models/entities/user-keypair.js"; +import { UsedUsername } from "@/models/entities/used-username.js"; +import { db } from "@/db/postgre.js"; export async function createSystemUser(username: string) { const password = uuid(); @@ -25,26 +25,30 @@ export async function createSystemUser(username: string) { let account!: User; // Start transaction - await db.transaction(async transactionalEntityManager => { + await db.transaction(async (transactionalEntityManager) => { const exist = await transactionalEntityManager.findOneBy(User, { usernameLower: username.toLowerCase(), host: IsNull(), }); - if (exist) throw new Error('the user is already exists'); + if (exist) throw new Error("the user is already exists"); - account = await transactionalEntityManager.insert(User, { - id: genId(), - createdAt: new Date(), - username: username, - usernameLower: username.toLowerCase(), - host: null, - token: secret, - isAdmin: false, - isLocked: true, - isExplorable: false, - isBot: true, - }).then(x => transactionalEntityManager.findOneByOrFail(User, x.identifiers[0])); + account = await transactionalEntityManager + .insert(User, { + id: genId(), + createdAt: new Date(), + username: username, + usernameLower: username.toLowerCase(), + host: null, + token: secret, + isAdmin: false, + isLocked: true, + isExplorable: false, + isBot: true, + }) + .then((x) => + transactionalEntityManager.findOneByOrFail(User, x.identifiers[0]), + ); await transactionalEntityManager.insert(UserKeypair, { publicKey: keyPair.publicKey, diff --git a/packages/backend/src/services/delete-account.ts b/packages/backend/src/services/delete-account.ts index 0fdceb671..927776199 100644 --- a/packages/backend/src/services/delete-account.ts +++ b/packages/backend/src/services/delete-account.ts @@ -1,14 +1,14 @@ -import { Users } from '@/models/index.js'; -import { createDeleteAccountJob } from '@/queue/index.js'; -import { publishUserEvent } from './stream.js'; -import { doPostSuspend } from './suspend-user.js'; +import { Users } from "@/models/index.js"; +import { createDeleteAccountJob } from "@/queue/index.js"; +import { publishUserEvent } from "./stream.js"; +import { doPostSuspend } from "./suspend-user.js"; export async function deleteAccount(user: { id: string; host: string | null; }): Promise { // 物理削除する前にDelete activityを送信する - await doPostSuspend(user).catch(e => {}); + await doPostSuspend(user).catch((e) => {}); createDeleteAccountJob(user, { soft: false, @@ -19,5 +19,5 @@ export async function deleteAccount(user: { }); // Terminate streaming - publishUserEvent(user.id, 'terminate', {}); + publishUserEvent(user.id, "terminate", {}); } diff --git a/packages/backend/src/services/detect-sensitive.ts b/packages/backend/src/services/detect-sensitive.ts index 2ade39d52..df695e86d 100644 --- a/packages/backend/src/services/detect-sensitive.ts +++ b/packages/backend/src/services/detect-sensitive.ts @@ -1,35 +1,42 @@ -import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import * as nsfw from 'nsfwjs'; -import si from 'systeminformation'; +import * as fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import * as nsfw from "nsfwjs"; +import si from "systeminformation"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; +const REQUIRED_CPU_FLAGS = ["avx2", "fma"]; let isSupportedCpu: undefined | boolean = undefined; let model: nsfw.NSFWJS; -export async function detectSensitive(path: string): Promise { +export async function detectSensitive( + path: string, +): Promise { try { if (isSupportedCpu === undefined) { const cpuFlags = await getCpuFlags(); - isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); + isSupportedCpu = REQUIRED_CPU_FLAGS.every((required) => + cpuFlags.includes(required), + ); } if (!isSupportedCpu) { - console.error('This CPU cannot use TensorFlow.'); + console.error("This CPU cannot use TensorFlow."); return null; } - const tf = await import('@tensorflow/tfjs-node'); + const tf = await import("@tensorflow/tfjs-node"); - if (model == null) model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 }); + if (model == null) + 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; + const image = (await tf.node.decodeImage(buffer, 3)) as any; try { const predictions = await model.classify(image); return predictions; diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 47b19e23a..da6994024 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -1,30 +1,45 @@ -import * as fs from 'node:fs'; +import * as fs from "node:fs"; -import { v4 as uuid } from 'uuid'; +import { v4 as uuid } from "uuid"; -import S3 from 'aws-sdk/clients/s3.js'; -import sharp from 'sharp'; -import { IsNull } from 'typeorm'; -import { publishMainStream, publishDriveStream } from '@/services/stream.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { contentDisposition } from '@/misc/content-disposition.js'; -import { getFileInfo } from '@/misc/get-file-info.js'; -import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { IRemoteUser, User } from '@/models/entities/user.js'; -import { driveChart, perUserDriveChart, instanceChart } from '@/services/chart/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { getS3 } from './s3.js'; -import { InternalStorage } from './internal-storage.js'; -import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js'; -import { driveLogger } from './logger.js'; -import { GenerateVideoThumbnail } from './generate-video-thumbnail.js'; -import { deleteFile } from './delete-file.js'; +import type S3 from "aws-sdk/clients/s3.js"; +import sharp from "sharp"; +import { IsNull } from "typeorm"; +import { publishMainStream, publishDriveStream } from "@/services/stream.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { contentDisposition } from "@/misc/content-disposition.js"; +import { getFileInfo } from "@/misc/get-file-info.js"; +import { + DriveFiles, + DriveFolders, + Users, + Instances, + UserProfiles, +} from "@/models/index.js"; +import { DriveFile } from "@/models/entities/drive-file.js"; +import type { IRemoteUser, User } from "@/models/entities/user.js"; +import { + driveChart, + perUserDriveChart, + instanceChart, +} from "@/services/chart/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; +import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import { getS3 } from "./s3.js"; +import { InternalStorage } from "./internal-storage.js"; +import type { IImage } from "./image-processor.js"; +import { + convertSharpToJpeg, + convertSharpToWebp, + convertSharpToPng, +} from "./image-processor.js"; +import { driveLogger } from "./logger.js"; +import { GenerateVideoThumbnail } from "./generate-video-thumbnail.js"; +import { deleteFile } from "./delete-file.js"; -const logger = driveLogger.createSubLogger('register', 'yellow'); +const logger = driveLogger.createSubLogger("register", "yellow"); /*** * Save file @@ -34,7 +49,14 @@ const logger = driveLogger.createSubLogger('register', 'yellow'); * @param hash Hash for original * @param size Size for original */ -async function save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { +async function save( + file: DriveFile, + path: string, + name: string, + type: string, + hash: string, + size: number, +): Promise { // thunbnail, webpublic を必要なら生成 const alts = await generateAlts(path, type, !file.uri); @@ -42,29 +64,34 @@ async function save(file: DriveFile, path: string, name: string, type: string, h if (meta.useObjectStorage) { //#region ObjectStorage params - let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']); + let [ext] = name.match(/\.([a-zA-Z0-9_-]+)$/) || [""]; - if (ext === '') { - if (type === 'image/jpeg') ext = '.jpg'; - if (type === 'image/png') ext = '.png'; - if (type === 'image/webp') ext = '.webp'; - if (type === 'image/apng') ext = '.apng'; - if (type === 'image/avif') ext = '.avif'; - if (type === 'image/vnd.mozilla.apng') ext = '.apng'; + if (ext === "") { + if (type === "image/jpeg") ext = ".jpg"; + if (type === "image/png") ext = ".png"; + if (type === "image/webp") ext = ".webp"; + if (type === "image/apng") ext = ".apng"; + if (type === "image/avif") ext = ".avif"; + if (type === "image/vnd.mozilla.apng") ext = ".apng"; } // 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、 // 許可されているファイル形式でしか拡張子をつけない if (!FILE_TYPE_BROWSERSAFE.includes(type)) { - ext = ''; + ext = ""; } - const baseUrl = meta.objectStorageBaseUrl - || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; + const baseUrl = + meta.objectStorageBaseUrl || + `${meta.objectStorageUseSSL ? "https" : "http"}://${ + meta.objectStorageEndpoint + }${meta.objectStoragePort ? `:${meta.objectStoragePort}` : ""}/${ + meta.objectStorageBucket + }`; // for original const key = `${meta.objectStoragePrefix}/${uuid()}${ext}`; - const url = `${ baseUrl }/${ key }`; + const url = `${baseUrl}/${key}`; // for alts let webpublicKey: string | null = null; @@ -75,24 +102,30 @@ async function save(file: DriveFile, path: string, name: string, type: string, h //#region Uploads logger.info(`uploading original: ${key}`); - const uploads = [ - upload(key, fs.createReadStream(path), type, name), - ]; + const uploads = [upload(key, fs.createReadStream(path), type, name)]; if (alts.webpublic) { - webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`; - webpublicUrl = `${ baseUrl }/${ webpublicKey }`; + webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${ + alts.webpublic.ext + }`; + webpublicUrl = `${baseUrl}/${webpublicKey}`; logger.info(`uploading webpublic: ${webpublicKey}`); - uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); + uploads.push( + upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name), + ); } if (alts.thumbnail) { - thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`; - thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; + thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${ + alts.thumbnail.ext + }`; + thumbnailUrl = `${baseUrl}/${thumbnailKey}`; logger.info(`uploading thumbnail: ${thumbnailKey}`); - uploads.push(upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); + uploads.push( + upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type), + ); } await Promise.all(uploads); @@ -111,11 +144,14 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.size = size; file.storedInternal = false; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); - } else { // use internal storage + return await DriveFiles.insert(file).then((x) => + DriveFiles.findOneByOrFail(x.identifiers[0]), + ); + } else { + // use internal storage const accessKey = uuid(); - const thumbnailAccessKey = 'thumbnail-' + uuid(); - const webpublicAccessKey = 'webpublic-' + uuid(); + const thumbnailAccessKey = `thumbnail-${uuid()}`; + const webpublicAccessKey = `webpublic-${uuid()}`; const url = InternalStorage.saveFromPath(accessKey, path); @@ -123,12 +159,18 @@ async function save(file: DriveFile, path: string, name: string, type: string, h let webpublicUrl: string | null = null; if (alts.thumbnail) { - thumbnailUrl = InternalStorage.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); + thumbnailUrl = InternalStorage.saveFromBuffer( + thumbnailAccessKey, + alts.thumbnail.data, + ); logger.info(`thumbnail stored: ${thumbnailAccessKey}`); } if (alts.webpublic) { - webpublicUrl = InternalStorage.saveFromBuffer(webpublicAccessKey, alts.webpublic.data); + webpublicUrl = InternalStorage.saveFromBuffer( + webpublicAccessKey, + alts.webpublic.data, + ); logger.info(`web stored: ${webpublicAccessKey}`); } @@ -145,7 +187,9 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.md5 = hash; file.size = size; - return await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); + return await DriveFiles.insert(file).then((x) => + DriveFiles.findOneByOrFail(x.identifiers[0]), + ); } } @@ -155,8 +199,12 @@ async function save(file: DriveFile, path: string, name: string, type: string, h * @param type Content-Type for original * @param generateWeb Generate webpublic or not */ -export async function generateAlts(path: string, type: string, generateWeb: boolean) { - if (type.startsWith('video/')) { +export async function generateAlts( + path: string, + type: string, + generateWeb: boolean, +) { + if (type.startsWith("video/")) { try { const thumbnail = await GenerateVideoThumbnail(path); return { @@ -172,8 +220,16 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } } - if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml', 'image/avif'].includes(type)) { - logger.debug('web image and thumbnail not created (not an required file)'); + if ( + ![ + "image/jpeg", + "image/png", + "image/webp", + "image/svg+xml", + "image/avif", + ].includes(type) + ) { + logger.debug("web image and thumbnail not created (not an required file)"); return { webpublic: null, thumbnail: null, @@ -197,10 +253,18 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } satisfyWebpublic = !!( - type !== 'image/svg+xml' && type !== 'image/webp' && - !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) && - metadata.width && metadata.width <= 2048 && - metadata.height && metadata.height <= 2048 + type !== "image/svg+xml" && + type !== "image/webp" && + !( + metadata.exif || + metadata.iptc || + metadata.xmp || + metadata.tifftagPhotoshop + ) && + metadata.width && + metadata.width <= 2048 && + metadata.height && + metadata.height <= 2048 ); } catch (err) { logger.warn(`sharp failed: ${err}`); @@ -214,26 +278,27 @@ export async function generateAlts(path: string, type: string, generateWeb: bool let webpublic: IImage | null = null; if (generateWeb && !satisfyWebpublic) { - logger.info('creating web image'); + logger.info("creating web image"); try { - if (['image/jpeg'].includes(type)) { + if (["image/jpeg"].includes(type)) { webpublic = await convertSharpToJpeg(img, 2048, 2048); - } else if (['image/webp'].includes(type)) { + } else if (["image/webp"].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); - } else if (['image/png'].includes(type)) { + } else if (["image/png"].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); - } else if (['image/svg+xml'].includes(type)) { + } else if (["image/svg+xml"].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); } else { - logger.debug('web image not created (not an required image)'); + logger.debug("web image not created (not an required image)"); } } catch (err) { - logger.warn('web image not created (an error occured)', err as Error); + logger.warn("web image not created (an error occured)", err as Error); } } else { - if (satisfyWebpublic) logger.info('web image not created (original satisfies webpublic)'); - else logger.info('web image not created (from remote)'); + if (satisfyWebpublic) + logger.info("web image not created (original satisfies webpublic)"); + else logger.info("web image not created (from remote)"); } // #endregion webpublic @@ -241,13 +306,21 @@ export async function generateAlts(path: string, type: string, generateWeb: bool let thumbnail: IImage | null = null; try { - if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml', 'image/avif'].includes(type)) { + if ( + [ + "image/jpeg", + "image/webp", + "image/png", + "image/svg+xml", + "image/avif", + ].includes(type) + ) { thumbnail = await convertSharpToWebp(img, 498, 280); } else { - logger.debug('thumbnail not created (not an required file)'); + logger.debug("thumbnail not created (not an required file)"); } } catch (err) { - logger.warn('thumbnail not created (an error occured)', err as Error); + logger.warn("thumbnail not created (an error occured)", err as Error); } // #endregion thumbnail @@ -260,9 +333,14 @@ export async function generateAlts(path: string, type: string, generateWeb: bool /** * Upload to ObjectStorage */ -async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { - if (type === 'image/apng') type = 'image/png'; - if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; +async function 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"; const meta = await fetchMeta(); @@ -271,36 +349,43 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, Key: key, Body: stream, ContentType: type, - CacheControl: 'max-age=31536000, immutable', + CacheControl: "max-age=31536000, immutable", } as S3.PutObjectRequest; - if (filename) params.ContentDisposition = contentDisposition('inline', filename); - if (meta.objectStorageSetPublicRead) params.ACL = 'public-read'; + if (filename) + params.ContentDisposition = contentDisposition("inline", filename); + if (meta.objectStorageSetPublicRead) params.ACL = "public-read"; const s3 = getS3(meta); const upload = s3.upload(params, { - partSize: s3.endpoint.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024, + partSize: + s3.endpoint.hostname === "storage.googleapis.com" + ? 500 * 1024 * 1024 + : 8 * 1024 * 1024, }); const result = await upload.promise(); - if (result) logger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); + if (result) + logger.debug( + `Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`, + ); } async function deleteOldFile(user: IRemoteUser) { - const q = DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .andWhere('file.isLink = FALSE'); + const q = DriveFiles.createQueryBuilder("file") + .where("file.userId = :userId", { userId: user.id }) + .andWhere("file.isLink = FALSE"); if (user.avatarId) { - q.andWhere('file.id != :avatarId', { avatarId: user.avatarId }); + q.andWhere("file.id != :avatarId", { avatarId: user.avatarId }); } if (user.bannerId) { - q.andWhere('file.id != :bannerId', { bannerId: user.bannerId }); + q.andWhere("file.id != :bannerId", { bannerId: user.bannerId }); } - q.orderBy('file.id', 'ASC'); + q.orderBy("file.id", "ASC"); const oldFile = await q.getOne(); @@ -311,7 +396,11 @@ async function deleteOldFile(user: IRemoteUser) { type AddFileArgs = { /** User who wish to add file */ - user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null; + user: { + id: User["id"]; + host: User["host"]; + driveCapacityOverrideMb: User["driveCapacityOverrideMb"]; + } | null; /** File path */ path: string; /** Name */ @@ -356,20 +445,35 @@ export async function addFile({ let skipNsfwCheck = false; const instance = await fetchMeta(); if (user == null) skipNsfwCheck = true; - if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'local' && Users.isRemoteUser(user)) skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'remote' && Users.isLocalUser(user)) skipNsfwCheck = true; + if (instance.sensitiveMediaDetection === "none") skipNsfwCheck = true; + if ( + user && + instance.sensitiveMediaDetection === "local" && + Users.isRemoteUser(user) + ) + skipNsfwCheck = true; + if ( + user && + instance.sensitiveMediaDetection === "remote" && + Users.isLocalUser(user) + ) + skipNsfwCheck = true; const info = await getFileInfo(path, { skipSensitiveDetection: skipNsfwCheck, sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる - instance.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : - instance.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : - instance.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : - instance.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : - 0.5, + instance.sensitiveMediaDetectionSensitivity === "veryHigh" + ? 0.1 + : instance.sensitiveMediaDetectionSensitivity === "high" + ? 0.3 + : instance.sensitiveMediaDetectionSensitivity === "low" + ? 0.7 + : instance.sensitiveMediaDetectionSensitivity === "veryLow" + ? 0.9 + : 0.5, sensitiveThresholdForPorn: 0.75, - enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, + enableSensitiveMediaDetectionForVideos: + instance.enableSensitiveMediaDetectionForVideos, }); logger.info(`${JSON.stringify(info)}`); @@ -379,7 +483,8 @@ export async function addFile({ //} // 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 @@ -400,12 +505,21 @@ export async function addFile({ const u = await Users.findOneBy({ id: user.id }); const instance = await fetchMeta(); - let driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); + let driveCapacity = + 1024 * + 1024 * + (Users.isLocalUser(user) + ? instance.localDriveCapacityMb + : instance.remoteDriveCapacityMb); if (Users.isLocalUser(user) && u?.driveCapacityOverrideMb != null) { driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb; - logger.debug('drive capacity override applied'); - logger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); + logger.debug("drive capacity override applied"); + logger.debug( + `overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${ + usage + info.size + }bytes`, + ); } logger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); @@ -413,10 +527,15 @@ export async function addFile({ // If usage limit exceeded if (usage + info.size > driveCapacity) { if (Users.isLocalUser(user)) { - throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); + throw new IdentifiableError( + "c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6", + "No free space.", + ); } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(await Users.findOneByOrFail({ id: user.id }) as IRemoteUser); + deleteOldFile( + (await Users.findOneByOrFail({ id: user.id })) as IRemoteUser, + ); } } } @@ -432,7 +551,7 @@ export async function addFile({ userId: user ? user.id : IsNull(), }); - if (driveFolder == null) throw new Error('folder-not-found'); + if (driveFolder == null) throw new Error("folder-not-found"); return driveFolder; }; @@ -444,14 +563,16 @@ export async function addFile({ } = {}; if (info.width) { - properties['width'] = info.width; - properties['height'] = info.height; + properties["width"] = info.width; + properties["height"] = info.height; } if (info.orientation != null) { - properties['orientation'] = info.orientation; + properties["orientation"] = info.orientation; } - const profile = user ? await UserProfiles.findOneBy({ userId: user.id }) : null; + const profile = user + ? await UserProfiles.findOneBy({ userId: user.id }) + : null; const folder = await fetchFolder(); @@ -470,14 +591,16 @@ export async function addFile({ file.maybeSensitive = info.sensitive; file.maybePorn = info.porn; file.isSensitive = user - ? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : - (sensitive !== null && sensitive !== undefined) + ? Users.isLocalUser(user) && profile!.alwaysMarkNsfw + ? true + : sensitive !== null && sensitive !== undefined ? sensitive : false : false; if (info.sensitive && profile!.autoSensitive) file.isSensitive = true; - if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true; + if (info.sensitive && instance.setSensitiveFlagAutomatically) + file.isSensitive = true; if (url !== null) { file.src = url; @@ -486,8 +609,8 @@ export async function addFile({ file.url = url; // ローカルプロキシ用 file.accessKey = uuid(); - file.thumbnailAccessKey = 'thumbnail-' + uuid(); - file.webpublicAccessKey = 'webpublic-' + uuid(); + file.thumbnailAccessKey = `thumbnail-${uuid()}`; + file.webpublicAccessKey = `webpublic-${uuid()}`; } } @@ -503,32 +626,41 @@ export async function addFile({ file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.insert(file).then(x => DriveFiles.findOneByOrFail(x.identifiers[0])); + file = await DriveFiles.insert(file).then((x) => + DriveFiles.findOneByOrFail(x.identifiers[0]), + ); } catch (err) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(err)) { logger.info(`already registered ${file.uri}`); - file = await DriveFiles.findOneBy({ + file = (await DriveFiles.findOneBy({ uri: file.uri!, userId: user ? user.id : IsNull(), - }) as DriveFile; + })) as DriveFile; } else { logger.error(err as Error); throw err; } } } else { - file = await (save(file, path, detectedName, info.type.mime, info.md5, info.size)); + file = await save( + file, + path, + detectedName, + info.type.mime, + info.md5, + info.size, + ); } logger.succ(`drive file has been created ${file.id}`); if (user) { - DriveFiles.pack(file, { self: true }).then(packedFile => { + DriveFiles.pack(file, { self: true }).then((packedFile) => { // Publish driveFileCreated event - publishMainStream(user.id, 'driveFileCreated', packedFile); - publishDriveStream(user.id, 'fileCreated', packedFile); + publishMainStream(user.id, "driveFileCreated", packedFile); + publishDriveStream(user.id, "fileCreated", packedFile); }); } diff --git a/packages/backend/src/services/drive/delete-file.ts b/packages/backend/src/services/drive/delete-file.ts index 4816a3a31..215270df6 100644 --- a/packages/backend/src/services/drive/delete-file.ts +++ b/packages/backend/src/services/drive/delete-file.ts @@ -1,11 +1,15 @@ -import { DriveFile } from '@/models/entities/drive-file.js'; -import { InternalStorage } from './internal-storage.js'; -import { DriveFiles, Instances } from '@/models/index.js'; -import { driveChart, perUserDriveChart, instanceChart } from '@/services/chart/index.js'; -import { createDeleteObjectStorageFileJob } from '@/queue/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { getS3 } from './s3.js'; -import { v4 as uuid } from 'uuid'; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { InternalStorage } from "./internal-storage.js"; +import { DriveFiles, Instances } from "@/models/index.js"; +import { + driveChart, + perUserDriveChart, + instanceChart, +} from "@/services/chart/index.js"; +import { createDeleteObjectStorageFileJob } from "@/queue/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import { getS3 } from "./s3.js"; +import { v4 as uuid } from "uuid"; export async function deleteFile(file: DriveFile, isExpired = false) { if (file.storedInternal) { @@ -74,8 +78,8 @@ async function postProcess(file: DriveFile, isExpired = false) { storedInternal: false, // ローカルプロキシ用 accessKey: uuid(), - thumbnailAccessKey: 'thumbnail-' + uuid(), - webpublicAccessKey: 'webpublic-' + uuid(), + thumbnailAccessKey: `thumbnail-${uuid()}`, + webpublicAccessKey: `webpublic-${uuid()}`, }); } else { DriveFiles.delete(file.id); @@ -94,8 +98,10 @@ export async function deleteObjectStorageFile(key: string) { const s3 = getS3(meta); - await s3.deleteObject({ - Bucket: meta.objectStorageBucket!, - Key: key, - }).promise(); + await s3 + .deleteObject({ + Bucket: meta.objectStorageBucket!, + Key: key, + }) + .promise(); } diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts index 6e6666481..e12d00936 100644 --- a/packages/backend/src/services/drive/generate-video-thumbnail.ts +++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts @@ -1,7 +1,8 @@ -import * as fs from 'node:fs'; -import { createTempDir } from '@/misc/create-temp.js'; -import { IImage, convertToJpeg } from './image-processor.js'; -import FFmpeg from 'fluent-ffmpeg'; +import * as fs from "node:fs"; +import { createTempDir } from "@/misc/create-temp.js"; +import type { IImage } from "./image-processor.js"; +import { convertToJpeg } from "./image-processor.js"; +import FFmpeg from "fluent-ffmpeg"; export async function GenerateVideoThumbnail(source: string): Promise { const [dir, cleanup] = await createTempDir(); @@ -11,14 +12,14 @@ export async function GenerateVideoThumbnail(source: string): Promise { FFmpeg({ source, }) - .on('end', res) - .on('error', rej) - .screenshot({ - folder: dir, - filename: 'out.png', // must have .png extension - count: 1, - timestamps: ['5%'], - }); + .on("end", res) + .on("error", rej) + .screenshot({ + folder: dir, + filename: "out.png", // must have .png extension + count: 1, + timestamps: ["5%"], + }); }); // JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる) diff --git a/packages/backend/src/services/drive/image-processor.ts b/packages/backend/src/services/drive/image-processor.ts index 2c564ea59..23404139b 100644 --- a/packages/backend/src/services/drive/image-processor.ts +++ b/packages/backend/src/services/drive/image-processor.ts @@ -1,4 +1,4 @@ -import sharp from 'sharp'; +import sharp from "sharp"; export type IImage = { data: Buffer; @@ -10,14 +10,22 @@ export type IImage = { * Convert to JPEG * with resize, remove metadata, resolve orientation, stop animation */ -export async function convertToJpeg(path: string, width: number, height: number): Promise { +export async function convertToJpeg( + path: string, + width: number, + height: number, +): Promise { return convertSharpToJpeg(await sharp(path), width, height); } -export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise { +export async function convertSharpToJpeg( + sharp: sharp.Sharp, + width: number, + height: number, +): Promise { const data = await sharp .resize(width, height, { - fit: 'inside', + fit: "inside", withoutEnlargement: true, }) .rotate() @@ -29,8 +37,8 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig return { data, - ext: 'jpg', - type: 'image/jpeg', + ext: "jpg", + type: "image/jpeg", }; } @@ -38,14 +46,24 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ -export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise { +export async function convertToWebp( + path: string, + width: number, + height: number, + quality: number = 85, +): Promise { return convertSharpToWebp(await sharp(path), width, height, quality); } -export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise { +export async function convertSharpToWebp( + sharp: sharp.Sharp, + width: number, + height: number, + quality: number = 85, +): Promise { const data = await sharp .resize(width, height, { - fit: 'inside', + fit: "inside", withoutEnlargement: true, }) .rotate() @@ -56,8 +74,8 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig return { data, - ext: 'webp', - type: 'image/webp', + ext: "webp", + type: "image/webp", }; } @@ -65,14 +83,22 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig * Convert to PNG * with resize, remove metadata, resolve orientation, stop animation */ -export async function convertToPng(path: string, width: number, height: number): Promise { +export async function convertToPng( + path: string, + width: number, + height: number, +): Promise { return convertSharpToPng(await sharp(path), width, height); } -export async function convertSharpToPng(sharp: sharp.Sharp, width: number, height: number): Promise { +export async function convertSharpToPng( + sharp: sharp.Sharp, + width: number, + height: number, +): Promise { const data = await sharp .resize(width, height, { - fit: 'inside', + fit: "inside", withoutEnlargement: true, }) .rotate() @@ -81,7 +107,7 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh return { data, - ext: 'png', - type: 'image/png', + ext: "png", + type: "image/png", }; } diff --git a/packages/backend/src/services/drive/internal-storage.ts b/packages/backend/src/services/drive/internal-storage.ts index 8f76c81ca..bccb123be 100644 --- a/packages/backend/src/services/drive/internal-storage.ts +++ b/packages/backend/src/services/drive/internal-storage.ts @@ -1,16 +1,17 @@ -import * as fs from 'node:fs'; -import * as Path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import config from '@/config/index.js'; +import * as fs from "node:fs"; +import * as Path from "node:path"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import config from "@/config/index.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); export class InternalStorage { - private static readonly path = Path.resolve(_dirname, '../../../../../files'); + private static readonly path = Path.resolve(_dirname, "../../../../../files"); - public static resolvePath = (key: string) => Path.resolve(InternalStorage.path, key); + public static resolvePath = (key: string) => + Path.resolve(InternalStorage.path, key); public static read(key: string) { return fs.createReadStream(InternalStorage.resolvePath(key)); diff --git a/packages/backend/src/services/drive/logger.ts b/packages/backend/src/services/drive/logger.ts index 917a8317e..ebde2d705 100644 --- a/packages/backend/src/services/drive/logger.ts +++ b/packages/backend/src/services/drive/logger.ts @@ -1,3 +1,3 @@ -import Logger from '../logger.js'; +import Logger from "../logger.js"; -export const driveLogger = new Logger('drive', 'blue'); +export const driveLogger = new Logger("drive", "blue"); diff --git a/packages/backend/src/services/drive/s3.ts b/packages/backend/src/services/drive/s3.ts index 80e34be95..ca356e912 100644 --- a/packages/backend/src/services/drive/s3.ts +++ b/packages/backend/src/services/drive/s3.ts @@ -1,12 +1,15 @@ -import { URL } from 'node:url'; -import S3 from 'aws-sdk/clients/s3.js'; -import { Meta } from '@/models/entities/meta.js'; -import { getAgentByUrl } from '@/misc/fetch.js'; +import { URL } from "node:url"; +import S3 from "aws-sdk/clients/s3.js"; +import type { Meta } from "@/models/entities/meta.js"; +import { getAgentByUrl } from "@/misc/fetch.js"; export function getS3(meta: Meta) { - const u = meta.objectStorageEndpoint != null - ? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}` - : `${meta.objectStorageUseSSL ? 'https://' : 'http://'}example.net`; + const u = + meta.objectStorageEndpoint != null + ? `${meta.objectStorageUseSSL ? "https://" : "http://"}${ + meta.objectStorageEndpoint + }` + : `${meta.objectStorageUseSSL ? "https://" : "http://"}example.net`; return new S3({ endpoint: meta.objectStorageEndpoint || undefined, @@ -14,7 +17,7 @@ export function getS3(meta: Meta) { secretAccessKey: meta.objectStorageSecretKey!, region: meta.objectStorageRegion || undefined, sslEnabled: meta.objectStorageUseSSL, - s3ForcePathStyle: !meta.objectStorageEndpoint // AWS with endPoint omitted + s3ForcePathStyle: !meta.objectStorageEndpoint // AWS with endPoint omitted ? false : meta.objectStorageS3ForcePathStyle, httpOptions: { diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 3c5e1aa5c..9d71757e3 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -1,19 +1,19 @@ -import { URL } from 'node:url'; -import { User } from '@/models/entities/user.js'; -import { createTemp } from '@/misc/create-temp.js'; -import { downloadUrl } from '@/misc/download-url.js'; -import { DriveFolder } from '@/models/entities/drive-folder.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { DriveFiles } from '@/models/index.js'; -import { driveLogger } from './logger.js'; -import { addFile } from './add-file.js'; +import { URL } from "node:url"; +import type { User } from "@/models/entities/user.js"; +import { createTemp } from "@/misc/create-temp.js"; +import { downloadUrl } from "@/misc/download-url.js"; +import type { DriveFolder } from "@/models/entities/drive-folder.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { DriveFiles } from "@/models/index.js"; +import { driveLogger } from "./logger.js"; +import { addFile } from "./add-file.js"; -const logger = driveLogger.createSubLogger('downloader'); +const logger = driveLogger.createSubLogger("downloader"); type Args = { url: string; - user: { id: User['id']; host: User['host'] } | null; - folderId?: DriveFolder['id'] | null; + user: { id: User["id"]; host: User["host"] } | null; + folderId?: DriveFolder["id"] | null; uri?: string | null; sensitive?: boolean; force?: boolean; @@ -35,7 +35,7 @@ export async function uploadFromUrl({ requestIp = null, requestHeaders = null, }: Args): Promise { - let name = new URL(url).pathname.split('/').pop() || null; + let name = new URL(url).pathname.split("/").pop() || null; if (name == null || !DriveFiles.validateFileName(name)) { name = null; } @@ -53,7 +53,20 @@ export async function uploadFromUrl({ // write content at URL to temp file await downloadUrl(url, path); - const driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive, requestIp, requestHeaders }); + const driveFile = await addFile({ + user, + path, + name, + comment, + folderId, + force, + isLink, + url, + uri, + sensitive, + requestIp, + requestHeaders, + }); logger.succ(`Got: ${driveFile.id}`); return driveFile!; } catch (e) { diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index d3f674d6f..9a1919551 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -1,23 +1,29 @@ -import { URL } from 'node:url'; -import { JSDOM } from 'jsdom'; -import fetch from 'node-fetch'; -import tinycolor from 'tinycolor2'; -import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch.js'; -import type { Instance } from '@/models/entities/instance.js'; -import { Instances } from '@/models/index.js'; -import { getFetchInstanceMetadataLock } from '@/misc/app-lock.js'; -import Logger from './logger.js'; -import type { DOMWindow } from 'jsdom'; +import { URL } from "node:url"; +import { JSDOM } from "jsdom"; +import fetch from "node-fetch"; +import tinycolor from "tinycolor2"; +import { getJson, getHtml, getAgentByUrl } from "@/misc/fetch.js"; +import type { Instance } from "@/models/entities/instance.js"; +import { Instances } from "@/models/index.js"; +import { getFetchInstanceMetadataLock } from "@/misc/app-lock.js"; +import Logger from "./logger.js"; +import type { DOMWindow } from "jsdom"; -const logger = new Logger('metadata', 'cyan'); +const logger = new Logger("metadata", "cyan"); -export async function fetchInstanceMetadata(instance: Instance, force = false): Promise { +export async function fetchInstanceMetadata( + instance: Instance, + force = false, +): Promise { const unlock = await getFetchInstanceMetadataLock(instance.host); if (!force) { const _instance = await Instances.findOneBy({ host: instance.host }); const now = Date.now(); - if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) { + if ( + _instance?.infoUpdatedAt && + now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24 + ) { unlock(); return; } @@ -50,8 +56,16 @@ export async function fetchInstanceMetadata(instance: Instance, force = false): updates.softwareName = 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; - updates.maintainerEmail = info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.email || null) : null : null; + updates.maintainerName = info.metadata + ? info.metadata.maintainer + ? info.metadata.maintainer.name || null + : null + : null; + updates.maintainerEmail = info.metadata + ? info.metadata.maintainer + ? info.metadata.maintainer.email || null + : null + : null; } if (name) updates.name = name; @@ -92,34 +106,40 @@ async function fetchNodeinfo(instance: Instance): Promise { logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { - const wellknown = await getJson('https://' + instance.host + '/.well-known/nodeinfo') - .catch(e => { - if (e.statusCode === 404) { - throw new Error('No nodeinfo provided'); - } else { - throw new Error(e.statusCode || e.message); - } - }) as Record; + const wellknown = (await getJson( + `https://${instance.host}/.well-known/nodeinfo`, + ).catch((e) => { + if (e.statusCode === 404) { + throw new Error("No nodeinfo provided"); + } else { + throw new Error(e.statusCode || e.message); + } + })) as Record; if (wellknown.links == null || !Array.isArray(wellknown.links)) { - throw new Error('No wellknown links'); + throw new Error("No wellknown links"); } const links = wellknown.links as any[]; - const lnik1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0'); - const lnik2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0'); - const lnik2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1'); + const lnik1_0 = links.find( + (link) => link.rel === "http://nodeinfo.diaspora.software/ns/schema/1.0", + ); + const lnik2_0 = links.find( + (link) => link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0", + ); + const lnik2_1 = links.find( + (link) => link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1", + ); const link = lnik2_1 || lnik2_0 || lnik1_0; if (link == null) { - throw new Error('No nodeinfo link provided'); + throw new Error("No nodeinfo link provided"); } - const info = await getJson(link.href) - .catch(e => { - throw new Error(e.statusCode || e.message); - }); + const info = await getJson(link.href).catch((e) => { + throw new Error(e.statusCode || e.message); + }); logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); @@ -131,10 +151,10 @@ async function fetchNodeinfo(instance: Instance): Promise { } } -async function fetchDom(instance: Instance): Promise { +async function fetchDom(instance: Instance): Promise { logger.info(`Fetching HTML of ${instance.host} ...`); - const url = 'https://' + instance.host; + const url = `https://${instance.host}`; const html = await getHtml(url); @@ -144,29 +164,36 @@ async function fetchDom(instance: Instance): Promise { return doc; } -async function fetchManifest(instance: Instance): Promise | null> { - const url = 'https://' + instance.host; +async function fetchManifest( + instance: Instance, +): Promise | null> { + const url = `https://${instance.host}`; - const manifestUrl = url + '/manifest.json'; + const manifestUrl = `${url}/manifest.json`; - const manifest = await getJson(manifestUrl) as Record; + const manifest = (await getJson(manifestUrl)) as Record; return manifest; } -async function fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | null): Promise { - const url = 'https://' + instance.host; +async function fetchFaviconUrl( + instance: Instance, + doc: DOMWindow["document"] | null, +): Promise { + const url = `https://${instance.host}`; if (doc) { // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 - const href = Array.from(doc.getElementsByTagName('link')).reverse().find(link => link.relList.contains('icon'))?.href; + const href = Array.from(doc.getElementsByTagName("link")) + .reverse() + .find((link) => link.relList.contains("icon"))?.href; if (href) { - return (new URL(href, url)).href; + return new URL(href, url).href; } } - const faviconUrl = url + '/favicon.ico'; + const faviconUrl = `${url}/favicon.ico`; const favicon = await fetch(faviconUrl, { // TODO @@ -181,36 +208,51 @@ async function fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | return null; } -async function 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; +async function fetchIconUrl( + instance: Instance, + doc: DOMWindow["document"] | null, + manifest: Record | null, +): Promise { + if ( + manifest?.icons && + manifest.icons.length > 0 && + manifest.icons[0].src + ) { + const url = `https://${instance.host}`; + return new URL(manifest.icons[0].src, url).href; } if (doc) { - const url = 'https://' + instance.host; + const url = `https://${instance.host}`; // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 - const links = Array.from(doc.getElementsByTagName('link')).reverse(); + const links = Array.from(doc.getElementsByTagName("link")).reverse(); // https://github.com/misskey-dev/misskey/pull/8220/files/0ec4eba22a914e31b86874f12448f88b3e58dd5a#r796487559 - const href = - [ - links.find(link => link.relList.contains('apple-touch-icon-precomposed'))?.href, - links.find(link => link.relList.contains('apple-touch-icon'))?.href, - links.find(link => link.relList.contains('icon'))?.href, - ] - .find(href => href); + const href = [ + links.find((link) => + link.relList.contains("apple-touch-icon-precomposed"), + )?.href, + links.find((link) => link.relList.contains("apple-touch-icon"))?.href, + links.find((link) => link.relList.contains("icon"))?.href, + ].find((href) => href); if (href) { - return (new URL(href, url)).href; + return new URL(href, url).href; } } return null; } -async function 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; +async function 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) { const color = new tinycolor(themeColor); @@ -220,15 +262,21 @@ async function getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | return null; } -async function getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { - if (info && info.metadata) { +async function getSiteName( + info: NodeInfo | null, + doc: DOMWindow["document"] | null, + manifest: Record | null, +): Promise { + if (info?.metadata) { if (info.metadata.nodeName || info.metadata.name) { return info.metadata.nodeName || info.metadata.name; } } if (doc) { - const og = doc.querySelector('meta[property="og:title"]')?.getAttribute('content'); + const og = doc + .querySelector('meta[property="og:title"]') + ?.getAttribute("content"); if (og) { return og; @@ -242,20 +290,28 @@ async function getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | n return null; } -async function getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { - if (info && info.metadata) { +async function getDescription( + info: NodeInfo | null, + doc: DOMWindow["document"] | null, + manifest: Record | null, +): Promise { + if (info?.metadata) { if (info.metadata.nodeDescription || info.metadata.description) { return info.metadata.nodeDescription || info.metadata.description; } } if (doc) { - const meta = doc.querySelector('meta[name="description"]')?.getAttribute('content'); + const meta = doc + .querySelector('meta[name="description"]') + ?.getAttribute("content"); if (meta) { return meta; } - const og = doc.querySelector('meta[property="og:description"]')?.getAttribute('content'); + const og = doc + .querySelector('meta[property="og:description"]') + ?.getAttribute("content"); if (og) { return og; } diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index ec6d2e6c9..635d706fc 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -1,26 +1,51 @@ -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderAccept from '@/remote/activitypub/renderer/accept.js'; -import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { deliver } from '@/queue/index.js'; -import createFollowRequest from './requests/create.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; -import Logger from '../logger.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User } from '@/models/entities/user.js'; -import { Followings, Users, FollowRequests, Blockings, Instances, UserProfiles } from '@/models/index.js'; -import { instanceChart, perUserFollowingChart } from '@/services/chart/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { createNotification } from '../create-notification.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { Packed } from '@/misc/schema.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; -import { webhookDeliver } from '@/queue/index.js'; +import { publishMainStream, publishUserEvent } from "@/services/stream.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderAccept from "@/remote/activitypub/renderer/accept.js"; +import renderReject from "@/remote/activitypub/renderer/reject.js"; +import { deliver } from "@/queue/index.js"; +import createFollowRequest from "./requests/create.js"; +import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js"; +import Logger from "../logger.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import type { User } from "@/models/entities/user.js"; +import { + Followings, + Users, + FollowRequests, + Blockings, + Instances, + UserProfiles, +} from "@/models/index.js"; +import { + instanceChart, + perUserFollowingChart, +} from "@/services/chart/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { createNotification } from "../create-notification.js"; +import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; +import type { Packed } from "@/misc/schema.js"; +import { getActiveWebhooks } from "@/misc/webhook-cache.js"; +import { webhookDeliver } from "@/queue/index.js"; -const logger = new Logger('following/create'); +const logger = new Logger("following/create"); -export async function insertFollowingDoc(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] }, follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'] }) { +export async function insertFollowingDoc( + followee: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + follower: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, +) { if (follower.id === followee.id) return; let alreadyFollowed = false; @@ -34,12 +59,20 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ // 非正規化 followerHost: follower.host, followerInbox: Users.isRemoteUser(follower) ? follower.inbox : null, - followerSharedInbox: Users.isRemoteUser(follower) ? follower.sharedInbox : null, + followerSharedInbox: Users.isRemoteUser(follower) + ? follower.sharedInbox + : null, followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : null, - followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : null, - }).catch(e => { - if (isDuplicateKeyValueError(e) && Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { + followeeSharedInbox: Users.isRemoteUser(followee) + ? followee.sharedInbox + : null, + }).catch((e) => { + if ( + isDuplicateKeyValueError(e) && + Users.isRemoteUser(follower) && + Users.isLocalUser(followee) + ) { logger.info(`Insert duplicated ignore. ${follower.id} => ${followee.id}`); alreadyFollowed = true; } else { @@ -59,7 +92,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ }); // Create notification that request was accepted. - createNotification(follower.id, 'followRequestAccepted', { + createNotification(follower.id, "followRequestAccepted", { notifierId: followee.id, }); } @@ -68,20 +101,20 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ //#region Increment counts await Promise.all([ - Users.increment({ id: follower.id }, 'followingCount', 1), - Users.increment({ id: followee.id }, 'followersCount', 1), + Users.increment({ id: follower.id }, "followingCount", 1), + Users.increment({ id: followee.id }, "followersCount", 1), ]); //#endregion //#region Update instance stats if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - registerOrFetchInstanceDoc(follower.host).then(i => { - Instances.increment({ id: i.id }, 'followingCount', 1); + registerOrFetchInstanceDoc(follower.host).then((i) => { + Instances.increment({ id: i.id }, "followingCount", 1); instanceChart.updateFollowing(i.host, true); }); } else if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - registerOrFetchInstanceDoc(followee.host).then(i => { - Instances.increment({ id: i.id }, 'followersCount', 1); + registerOrFetchInstanceDoc(followee.host).then((i) => { + Instances.increment({ id: i.id }, "followersCount", 1); instanceChart.updateFollowers(i.host, true); }); } @@ -93,13 +126,23 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ if (Users.isLocalUser(follower)) { Users.pack(followee.id, follower, { detail: true, - }).then(async packed => { - publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); - publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); + }).then(async (packed) => { + publishUserEvent( + follower.id, + "follow", + packed as Packed<"UserDetailedNotMe">, + ); + publishMainStream( + follower.id, + "follow", + packed as Packed<"UserDetailedNotMe">, + ); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === follower.id && x.on.includes("follow"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'follow', { + webhookDeliver(webhook, "follow", { user: packed, }); } @@ -108,25 +151,31 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ // Publish followed event if (Users.isLocalUser(followee)) { - Users.pack(follower.id, followee).then(async packed => { - publishMainStream(followee.id, 'followed', packed); + Users.pack(follower.id, followee).then(async (packed) => { + publishMainStream(followee.id, "followed", packed); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === followee.id && x.on.includes("followed"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'followed', { + webhookDeliver(webhook, "followed", { user: packed, }); } }); // 通知を作成 - createNotification(followee.id, 'follow', { + createNotification(followee.id, "follow", { notifierId: follower.id, }); } } -export default async function(_follower: { id: User['id'] }, _followee: { id: User['id'] }, requestId?: string) { +export default async function ( + _follower: { id: User["id"] }, + _followee: { id: User["id"] }, + requestId?: string, +) { const [follower, followee] = await Promise.all([ Users.findOneByOrFail({ id: _follower.id }), Users.findOneByOrFail({ id: _followee.id }), @@ -146,25 +195,45 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocked) { // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 - const content = renderActivity(renderReject(renderFollow(follower, followee, requestId), followee)); - deliver(followee , content, follower.inbox); + const content = renderActivity( + renderReject(renderFollow(follower, followee, requestId), followee), + ); + deliver(followee, content, follower.inbox); return; - } else if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocking) { + } else if ( + Users.isRemoteUser(follower) && + Users.isLocalUser(followee) && + blocking + ) { // リモートフォローを受けてブロックされているはずの場合だったら、ブロック解除しておく。 await Blockings.delete(blocking.id); } else { // それ以外は単純に例外 - if (blocking != null) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking'); - if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); + if (blocking != null) + throw new IdentifiableError( + "710e8fb0-b8c3-4922-be49-d5d93d8e6a6e", + "blocking", + ); + if (blocked != null) + throw new IdentifiableError( + "3338392a-f764-498d-8855-db939dcf8c48", + "blocked", + ); } - const followeeProfile = await UserProfiles.findOneByOrFail({ userId: followee.id }); + const followeeProfile = await UserProfiles.findOneByOrFail({ + userId: followee.id, + }); // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく - if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) { + if ( + followee.isLocked || + (followeeProfile.carefulBot && follower.isBot) || + (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) + ) { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー @@ -177,7 +246,11 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us } // フォローしているユーザーは自動承認オプション - if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { + if ( + !autoAccept && + Users.isLocalUser(followee) && + followeeProfile.autoAcceptFollowed + ) { const followed = await Followings.findOneBy({ followerId: followee.id, followeeId: follower.id, @@ -195,7 +268,9 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us await insertFollowingDoc(followee, follower); if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - const content = renderActivity(renderAccept(renderFollow(follower, followee, requestId), followee)); + const content = renderActivity( + renderAccept(renderFollow(follower, followee, requestId), followee), + ); deliver(followee, content, follower.inbox); } } diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index 91b5a3d61..fae4bd3ce 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -1,26 +1,47 @@ -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { deliver, webhookDeliver } from '@/queue/index.js'; -import Logger from '../logger.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; -import { User } from '@/models/entities/user.js'; -import { Followings, Users, Instances } from '@/models/index.js'; -import { instanceChart, perUserFollowingChart } from '@/services/chart/index.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { publishMainStream, publishUserEvent } from "@/services/stream.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import renderReject from "@/remote/activitypub/renderer/reject.js"; +import { deliver, webhookDeliver } from "@/queue/index.js"; +import Logger from "../logger.js"; +import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js"; +import type { User } from "@/models/entities/user.js"; +import { Followings, Users, Instances } from "@/models/index.js"; +import { + instanceChart, + perUserFollowingChart, +} from "@/services/chart/index.js"; +import { getActiveWebhooks } from "@/misc/webhook-cache.js"; -const logger = new Logger('following/delete'); +const logger = new Logger("following/delete"); -export default async function(follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, silent = false) { +export default async function ( + follower: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + followee: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + silent = false, +) { const following = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, }); if (following == null) { - logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした'); + logger.warn( + "フォロー解除がリクエストされましたがフォローしていませんでした", + ); return; } @@ -32,13 +53,15 @@ export default async function(follower: { id: User['id']; host: User['host']; ur if (!silent && Users.isLocalUser(follower)) { Users.pack(followee.id, follower, { detail: true, - }).then(async packed => { - publishUserEvent(follower.id, 'unfollow', packed); - publishMainStream(follower.id, 'unfollow', packed); + }).then(async (packed) => { + publishUserEvent(follower.id, "unfollow", packed); + publishMainStream(follower.id, "unfollow", packed); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === follower.id && x.on.includes("unfollow"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'unfollow', { + webhookDeliver(webhook, "unfollow", { user: packed, }); } @@ -46,34 +69,41 @@ export default async function(follower: { id: User['id']; host: User['host']; ur } if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); + const content = renderActivity( + renderUndo(renderFollow(follower, followee), follower), + ); deliver(follower, content, followee.inbox); } if (Users.isLocalUser(followee) && Users.isRemoteUser(follower)) { // local user has null host - const content = renderActivity(renderReject(renderFollow(follower, followee), followee)); + const content = renderActivity( + renderReject(renderFollow(follower, followee), followee), + ); deliver(followee, content, follower.inbox); } } -export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) { +export async function decrementFollowing( + follower: { id: User["id"]; host: User["host"] }, + followee: { id: User["id"]; host: User["host"] }, +) { //#region Decrement following / followers counts await Promise.all([ - Users.decrement({ id: follower.id }, 'followingCount', 1), - Users.decrement({ id: followee.id }, 'followersCount', 1), + Users.decrement({ id: follower.id }, "followingCount", 1), + Users.decrement({ id: followee.id }, "followersCount", 1), ]); //#endregion //#region Update instance stats if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - registerOrFetchInstanceDoc(follower.host).then(i => { - Instances.decrement({ id: i.id }, 'followingCount', 1); + registerOrFetchInstanceDoc(follower.host).then((i) => { + Instances.decrement({ id: i.id }, "followingCount", 1); instanceChart.updateFollowing(i.host, false); }); } else if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - registerOrFetchInstanceDoc(followee.host).then(i => { - Instances.decrement({ id: i.id }, 'followersCount', 1); + registerOrFetchInstanceDoc(followee.host).then((i) => { + Instances.decrement({ id: i.id }, "followersCount", 1); instanceChart.updateFollowers(i.host, false); }); } diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 691fca245..7464219bf 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -1,24 +1,29 @@ -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderReject from '@/remote/activitypub/renderer/reject.js'; -import { deliver, webhookDeliver } from '@/queue/index.js'; -import { publishMainStream, publishUserEvent } from '@/services/stream.js'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { Users, FollowRequests, Followings } from '@/models/index.js'; -import { decrementFollowing } from './delete.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderReject from "@/remote/activitypub/renderer/reject.js"; +import { deliver, webhookDeliver } from "@/queue/index.js"; +import { publishMainStream, publishUserEvent } from "@/services/stream.js"; +import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import { Users, FollowRequests, Followings } from "@/models/index.js"; +import { decrementFollowing } from "./delete.js"; +import { getActiveWebhooks } from "@/misc/webhook-cache.js"; -type Local = ILocalUser | { - id: ILocalUser['id']; - host: ILocalUser['host']; - uri: ILocalUser['uri'] -}; -type Remote = IRemoteUser | { - id: IRemoteUser['id']; - host: IRemoteUser['host']; - uri: IRemoteUser['uri']; - inbox: IRemoteUser['inbox']; -}; +type Local = + | ILocalUser + | { + id: ILocalUser["id"]; + host: ILocalUser["host"]; + uri: ILocalUser["uri"]; + }; +type Remote = + | IRemoteUser + | { + id: IRemoteUser["id"]; + host: IRemoteUser["host"]; + uri: IRemoteUser["uri"]; + inbox: IRemoteUser["inbox"]; + }; type Both = Local | Remote; /** @@ -98,7 +103,12 @@ async function deliverReject(followee: Local, follower: Remote) { followerId: follower.id, }); - const content = renderActivity(renderReject(renderFollow(follower, followee, request?.requestId || undefined), followee)); + const content = renderActivity( + renderReject( + renderFollow(follower, followee, request?.requestId || undefined), + followee, + ), + ); deliver(followee, content, follower.inbox); } @@ -110,12 +120,14 @@ async function publishUnfollow(followee: Both, follower: Local) { detail: true, }); - publishUserEvent(follower.id, 'unfollow', packedFollowee); - publishMainStream(follower.id, 'unfollow', packedFollowee); + publishUserEvent(follower.id, "unfollow", packedFollowee); + publishMainStream(follower.id, "unfollow", packedFollowee); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === follower.id && x.on.includes("unfollow"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'unfollow', { + webhookDeliver(webhook, "unfollow", { user: packedFollowee, }); } diff --git a/packages/backend/src/services/following/requests/accept-all.ts b/packages/backend/src/services/following/requests/accept-all.ts index 31f3926c0..269243379 100644 --- a/packages/backend/src/services/following/requests/accept-all.ts +++ b/packages/backend/src/services/following/requests/accept-all.ts @@ -1,12 +1,18 @@ -import accept from './accept.js'; -import { User } from '@/models/entities/user.js'; -import { FollowRequests, Users } from '@/models/index.js'; +import accept from "./accept.js"; +import type { User } from "@/models/entities/user.js"; +import { FollowRequests, Users } from "@/models/index.js"; /** * Approve all follow requests for the specified user * @param user User. */ -export default async function(user: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }) { +export default async function (user: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; +}) { const requests = await FollowRequests.findBy({ followeeId: user.id, }); diff --git a/packages/backend/src/services/following/requests/accept.ts b/packages/backend/src/services/following/requests/accept.ts index 20829f70c..6aa17b09a 100644 --- a/packages/backend/src/services/following/requests/accept.ts +++ b/packages/backend/src/services/following/requests/accept.ts @@ -1,31 +1,49 @@ -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderAccept from '@/remote/activitypub/renderer/accept.js'; -import { deliver } from '@/queue/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { insertFollowingDoc } from '../create.js'; -import { User, ILocalUser, CacheableUser } from '@/models/entities/user.js'; -import { FollowRequests, Users } from '@/models/index.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderAccept from "@/remote/activitypub/renderer/accept.js"; +import { deliver } from "@/queue/index.js"; +import { publishMainStream } from "@/services/stream.js"; +import { insertFollowingDoc } from "../create.js"; +import type { User, CacheableUser } from "@/models/entities/user.js"; +import { ILocalUser } from "@/models/entities/user.js"; +import { FollowRequests, Users } from "@/models/index.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; -export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: CacheableUser) { +export default async function ( + followee: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + follower: CacheableUser, +) { const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, }); if (request == null) { - throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); + throw new IdentifiableError( + "8884c2dd-5795-4ac9-b27e-6a01d38190f9", + "No follow request.", + ); } await insertFollowingDoc(followee, follower); if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId!), followee)); + const content = renderActivity( + renderAccept( + renderFollow(follower, followee, request.requestId!), + followee, + ), + ); deliver(followee, content, follower.inbox); } Users.pack(followee.id, followee, { detail: true, - }).then(packed => publishMainStream(followee.id, 'meUpdated', packed)); + }).then((packed) => publishMainStream(followee.id, "meUpdated", packed)); } diff --git a/packages/backend/src/services/following/requests/cancel.ts b/packages/backend/src/services/following/requests/cancel.ts index 56531fa1f..00daae380 100644 --- a/packages/backend/src/services/following/requests/cancel.ts +++ b/packages/backend/src/services/following/requests/cancel.ts @@ -1,17 +1,29 @@ -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { deliver } from '@/queue/index.js'; -import { publishMainStream } from '@/services/stream.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User, ILocalUser } from '@/models/entities/user.js'; -import { Users, FollowRequests } from '@/models/index.js'; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { deliver } from "@/queue/index.js"; +import { publishMainStream } from "@/services/stream.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import type { User } from "@/models/entities/user.js"; +import { ILocalUser } from "@/models/entities/user.js"; +import { Users, FollowRequests } from "@/models/index.js"; -export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox'] }, follower: { id: User['id']; host: User['host']; uri: User['host'] }) { +export default async function ( + followee: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + }, + follower: { id: User["id"]; host: User["host"]; uri: User["host"] }, +) { if (Users.isRemoteUser(followee)) { - const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); + const content = renderActivity( + renderUndo(renderFollow(follower, followee), follower), + ); - if (Users.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので + if (Users.isLocalUser(follower)) { + // 本来このチェックは不要だけどTSに怒られるので deliver(follower, content, followee.inbox); } } @@ -22,7 +34,10 @@ export default async function(followee: { id: User['id']; host: User['host']; ur }); if (request == null) { - throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'request not found'); + throw new IdentifiableError( + "17447091-ce07-46dd-b331-c1fd4f15b1e7", + "request not found", + ); } await FollowRequests.delete({ @@ -32,5 +47,5 @@ export default async function(followee: { id: User['id']; host: User['host']; ur Users.pack(followee.id, followee, { detail: true, - }).then(packed => publishMainStream(followee.id, 'meUpdated', packed)); + }).then((packed) => publishMainStream(followee.id, "meUpdated", packed)); } diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index bda2f8f92..0833f2aeb 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -1,13 +1,29 @@ -import { publishMainStream } from '@/services/stream.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderFollow from '@/remote/activitypub/renderer/follow.js'; -import { deliver } from '@/queue/index.js'; -import { User } from '@/models/entities/user.js'; -import { Blockings, FollowRequests, Users } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { createNotification } from '../../create-notification.js'; +import { publishMainStream } from "@/services/stream.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderFollow from "@/remote/activitypub/renderer/follow.js"; +import { deliver } from "@/queue/index.js"; +import type { User } from "@/models/entities/user.js"; +import { Blockings, FollowRequests, Users } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { createNotification } from "../../create-notification.js"; -export default async function(follower: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, requestId?: string) { +export default async function ( + follower: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + followee: { + id: User["id"]; + host: User["host"]; + uri: User["host"]; + inbox: User["inbox"]; + sharedInbox: User["sharedInbox"]; + }, + requestId?: string, +) { if (follower.id === followee.id) return; // check blocking @@ -22,8 +38,8 @@ export default async function(follower: { id: User['id']; host: User['host']; ur }), ]); - if (blocking != null) throw new Error('blocking'); - if (blocked != null) throw new Error('blocked'); + if (blocking != null) throw new Error("blocking"); + if (blocked != null) throw new Error("blocked"); const followRequest = await FollowRequests.insert({ id: genId(), @@ -35,22 +51,28 @@ export default async function(follower: { id: User['id']; host: User['host']; ur // 非正規化 followerHost: follower.host, followerInbox: Users.isRemoteUser(follower) ? follower.inbox : undefined, - followerSharedInbox: Users.isRemoteUser(follower) ? follower.sharedInbox : undefined, + followerSharedInbox: Users.isRemoteUser(follower) + ? follower.sharedInbox + : undefined, followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : undefined, - followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : undefined, - }).then(x => FollowRequests.findOneByOrFail(x.identifiers[0])); + followeeSharedInbox: Users.isRemoteUser(followee) + ? followee.sharedInbox + : undefined, + }).then((x) => FollowRequests.findOneByOrFail(x.identifiers[0])); // Publish receiveRequest event if (Users.isLocalUser(followee)) { - Users.pack(follower.id, followee).then(packed => publishMainStream(followee.id, 'receiveFollowRequest', packed)); + Users.pack(follower.id, followee).then((packed) => + publishMainStream(followee.id, "receiveFollowRequest", packed), + ); Users.pack(followee.id, followee, { detail: true, - }).then(packed => publishMainStream(followee.id, 'meUpdated', packed)); + }).then((packed) => publishMainStream(followee.id, "meUpdated", packed)); // 通知を作成 - createNotification(followee.id, 'receiveFollowRequest', { + createNotification(followee.id, "receiveFollowRequest", { notifierId: follower.id, followRequestId: followRequest.id, }); diff --git a/packages/backend/src/services/i/pin.ts b/packages/backend/src/services/i/pin.ts index f35392a34..97045a9fa 100644 --- a/packages/backend/src/services/i/pin.ts +++ b/packages/backend/src/services/i/pin.ts @@ -1,22 +1,25 @@ -import config from '@/config/index.js'; -import renderAdd from '@/remote/activitypub/renderer/add.js'; -import renderRemove from '@/remote/activitypub/renderer/remove.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { Notes, UserNotePinings, Users } from '@/models/index.js'; -import { UserNotePining } from '@/models/entities/user-note-pining.js'; -import { genId } from '@/misc/gen-id.js'; -import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; -import { deliverToRelays } from '../relay.js'; +import config from "@/config/index.js"; +import renderAdd from "@/remote/activitypub/renderer/add.js"; +import renderRemove from "@/remote/activitypub/renderer/remove.js"; +import { renderActivity } from "@/remote/activitypub/renderer/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 { Notes, UserNotePinings, Users } from "@/models/index.js"; +import type { UserNotePining } from "@/models/entities/user-note-pining.js"; +import { genId } from "@/misc/gen-id.js"; +import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js"; +import { deliverToRelays } from "../relay.js"; /** * 指定した投稿をピン留めします * @param user * @param noteId */ -export async function addPinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { +export async function addPinned( + user: { id: User["id"]; host: User["host"] }, + noteId: Note["id"], +) { // Fetch pinee const note = await Notes.findOneBy({ id: noteId, @@ -24,17 +27,26 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n }); if (note == null) { - throw new IdentifiableError('70c4e51f-5bea-449c-a030-53bee3cce202', 'No such note.'); + throw new IdentifiableError( + "70c4e51f-5bea-449c-a030-53bee3cce202", + "No such note.", + ); } const pinings = await UserNotePinings.findBy({ userId: user.id }); if (pinings.length >= 5) { - throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.'); + throw new IdentifiableError( + "15a018eb-58e5-4da1-93be-330fcc5e4e1a", + "You can not pin notes any more.", + ); } - if (pinings.some(pining => pining.noteId === note.id)) { - throw new IdentifiableError('23f0cf4e-59a3-4276-a91d-61a5891c1514', 'That note has already been pinned.'); + if (pinings.some((pining) => pining.noteId === note.id)) { + throw new IdentifiableError( + "23f0cf4e-59a3-4276-a91d-61a5891c1514", + "That note has already been pinned.", + ); } await UserNotePinings.insert({ @@ -55,7 +67,10 @@ export async function addPinned(user: { id: User['id']; host: User['host']; }, n * @param user * @param noteId */ -export async function removePinned(user: { id: User['id']; host: User['host']; }, noteId: Note['id']) { +export async function removePinned( + user: { id: User["id"]; host: User["host"] }, + noteId: Note["id"], +) { // Fetch unpinee const note = await Notes.findOneBy({ id: noteId, @@ -63,7 +78,10 @@ export async function removePinned(user: { id: User['id']; host: User['host']; } }); if (note == null) { - throw new IdentifiableError('b302d4cf-c050-400a-bbb3-be208681f40c', 'No such note.'); + throw new IdentifiableError( + "b302d4cf-c050-400a-bbb3-be208681f40c", + "No such note.", + ); } UserNotePinings.delete({ @@ -77,15 +95,23 @@ export async function removePinned(user: { id: User['id']; host: User['host']; } } } -export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { +export async function deliverPinnedChange( + userId: User["id"], + noteId: Note["id"], + isAddition: boolean, +) { const user = await Users.findOneBy({ id: userId }); - if (user == null) throw new Error('user not found'); + if (user == null) throw new Error("user not found"); if (!Users.isLocalUser(user)) return; const target = `${config.url}/users/${user.id}/collections/featured`; const item = `${config.url}/notes/${noteId}`; - const content = renderActivity(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item)); + const content = renderActivity( + isAddition + ? renderAdd(user, target, item) + : renderRemove(user, target, item), + ); deliverToFollowers(user, content); deliverToRelays(user, content); diff --git a/packages/backend/src/services/i/update.ts b/packages/backend/src/services/i/update.ts index 27bd38bd3..cc950ac85 100644 --- a/packages/backend/src/services/i/update.ts +++ b/packages/backend/src/services/i/update.ts @@ -1,18 +1,20 @@ -import renderUpdate from '@/remote/activitypub/renderer/update.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { Users } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { renderPerson } from '@/remote/activitypub/renderer/person.js'; -import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; -import { deliverToRelays } from '../relay.js'; +import renderUpdate from "@/remote/activitypub/renderer/update.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { Users } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import { renderPerson } from "@/remote/activitypub/renderer/person.js"; +import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js"; +import { deliverToRelays } from "../relay.js"; -export async function publishToFollowers(userId: User['id']) { +export async function publishToFollowers(userId: User["id"]) { const user = await Users.findOneBy({ id: userId }); - if (user == null) throw new Error('user not found'); + if (user == null) throw new Error("user not found"); // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 if (Users.isLocalUser(user)) { - const content = renderActivity(renderUpdate(await renderPerson(user), user)); + const content = renderActivity( + renderUpdate(await renderPerson(user), user), + ); deliverToFollowers(user, content); deliverToRelays(user, content); } diff --git a/packages/backend/src/services/insert-moderation-log.ts b/packages/backend/src/services/insert-moderation-log.ts index 0a7c472d8..8e2c5b78a 100644 --- a/packages/backend/src/services/insert-moderation-log.ts +++ b/packages/backend/src/services/insert-moderation-log.ts @@ -1,8 +1,12 @@ -import { ModerationLogs } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { User } from '@/models/entities/user.js'; +import { ModerationLogs } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { User } from "@/models/entities/user.js"; -export async function insertModerationLog(moderator: { id: User['id'] }, type: string, info?: Record) { +export async function insertModerationLog( + moderator: { id: User["id"] }, + type: string, + info?: Record, +) { await ModerationLogs.insert({ id: genId(), createdAt: new Date(), diff --git a/packages/backend/src/services/instance-actor.ts b/packages/backend/src/services/instance-actor.ts index bddd0355a..50ce227eb 100644 --- a/packages/backend/src/services/instance-actor.ts +++ b/packages/backend/src/services/instance-actor.ts @@ -1,10 +1,10 @@ -import { createSystemUser } from './create-system-user.js'; -import { ILocalUser } from '@/models/entities/user.js'; -import { Users } from '@/models/index.js'; -import { Cache } from '@/misc/cache.js'; -import { IsNull } from 'typeorm'; +import { createSystemUser } from "./create-system-user.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { Users } from "@/models/index.js"; +import { Cache } from "@/misc/cache.js"; +import { IsNull } from "typeorm"; -const ACTOR_USERNAME = 'instance.actor' as const; +const ACTOR_USERNAME = "instance.actor" as const; const cache = new Cache(Infinity); @@ -12,16 +12,16 @@ export async function getInstanceActor(): Promise { const cached = cache.get(null); if (cached) return cached; - const user = await Users.findOneBy({ + const user = (await Users.findOneBy({ host: IsNull(), username: ACTOR_USERNAME, - }) as ILocalUser | undefined; + })) as ILocalUser | undefined; if (user) { cache.set(null, user); return user; } else { - const created = await createSystemUser(ACTOR_USERNAME) as ILocalUser; + const created = (await createSystemUser(ACTOR_USERNAME)) as ILocalUser; cache.set(null, created); return created; } diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index b57272547..86e1d10d3 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -1,18 +1,18 @@ -import cluster from 'node:cluster'; -import chalk from 'chalk'; -import { default as convertColor } from 'color-convert'; -import { format as dateFormat } from 'date-fns'; -import { envOption } from '../env.js'; -import config from '@/config/index.js'; +import cluster from "node:cluster"; +import chalk from "chalk"; +import { default as convertColor } from "color-convert"; +import { format as dateFormat } from "date-fns"; +import { envOption } from "../env.js"; +import config from "@/config/index.js"; -import * as SyslogPro from 'syslog-pro'; +import * as SyslogPro from "syslog-pro"; type Domain = { name: string; color?: string; }; -type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; +type Level = "error" | "success" | "warning" | "debug" | "info"; export default class Logger { private domain: Domain; @@ -29,7 +29,7 @@ export default class Logger { if (config.syslog) { this.syslogClient = new SyslogPro.RFC5424({ - applacationName: 'Calckey', + applacationName: "Calckey", timestamp: true, encludeStructuredData: true, color: true, @@ -48,81 +48,152 @@ export default class Logger { return logger; } - private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { + 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; - if (level === 'debug') store = false; + if (level === "debug") store = false; if (this.parentLogger) { - this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store); + this.parentLogger.log( + level, + message, + data, + important, + [this.domain].concat(subDomains), + store, + ); return; } - const time = dateFormat(new Date(), 'HH:mm:ss'); - const worker = cluster.isPrimary ? '*' : cluster.worker.id; + const time = dateFormat(new Date(), "HH:mm:ss"); + const worker = cluster.isPrimary ? "*" : cluster.worker.id; const l = - level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') : - level === 'warning' ? chalk.yellow('WARN') : - level === 'success' ? important ? chalk.bgGreen.white('DONE') : chalk.green('DONE') : - level === 'debug' ? chalk.gray('VERB') : - level === 'info' ? chalk.blue('INFO') : - null; - const domains = [this.domain].concat(subDomains).map(d => d.color ? chalk.rgb(...convertColor.keyword.rgb(d.color))(d.name) : chalk.white(d.name)); + level === "error" + ? important + ? chalk.bgRed.white("ERR ") + : chalk.red("ERR ") + : level === "warning" + ? chalk.yellow("WARN") + : level === "success" + ? important + ? chalk.bgGreen.white("DONE") + : chalk.green("DONE") + : level === "debug" + ? chalk.gray("VERB") + : level === "info" + ? chalk.blue("INFO") + : null; + const domains = [this.domain] + .concat(subDomains) + .map((d) => + d.color + ? chalk.rgb(...convertColor.keyword.rgb(d.color))(d.name) + : chalk.white(d.name), + ); const m = - level === 'error' ? chalk.red(message) : - level === 'warning' ? chalk.yellow(message) : - level === 'success' ? chalk.green(message) : - level === 'debug' ? chalk.gray(message) : - level === 'info' ? message : - null; + level === "error" + ? chalk.red(message) + : level === "warning" + ? chalk.yellow(message) + : level === "success" + ? chalk.green(message) + : level === "debug" + ? chalk.gray(message) + : level === "info" + ? message + : null; - let log = `${l} ${worker}\t[${domains.join(' ')}]\t${m}`; - if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log; + let log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`; + if (envOption.withLogTime) log = `${chalk.gray(time)} ${log}`; console.log(important ? chalk.bold(log) : log); if (store) { if (this.syslogClient) { const send = - level === 'error' ? this.syslogClient.error : - level === 'warning' ? this.syslogClient.warning : - level === 'success' ? this.syslogClient.info : - level === 'debug' ? this.syslogClient.info : - level === 'info' ? this.syslogClient.info : - null as never; + level === "error" + ? this.syslogClient.error + : level === "warning" + ? this.syslogClient.warning + : level === "success" + ? this.syslogClient.info + : level === "debug" + ? this.syslogClient.info + : level === "info" + ? this.syslogClient.info + : (null as never); - send.bind(this.syslogClient)(message).catch(() => {}); + send + .bind(this.syslogClient)(message) + .catch(() => {}); } } } - public error(x: string | Error, data?: Record | null, important = false): void { // 実行を継続できない状況で使う + public error( + x: string | Error, + data?: Record | null, + important = false, + ): void { + // 実行を継続できない状況で使う if (x instanceof Error) { data = data || {}; data.e = x; - this.log('error', x.toString(), data, important); - } else if (typeof x === 'object') { - this.log('error', `${(x as any).message || (x as any).name || x}`, data, important); + this.log("error", x.toString(), data, important); + } else if (typeof x === "object") { + this.log( + "error", + `${(x as any).message || (x as any).name || x}`, + data, + important, + ); } else { - this.log('error', `${x}`, data, important); + this.log("error", `${x}`, data, important); } } - public warn(message: string, data?: Record | null, important = false): void { // 実行を継続できるが改善すべき状況で使う - this.log('warning', message, data, important); + public warn( + message: string, + data?: Record | null, + important = false, + ): void { + // 実行を継続できるが改善すべき状況で使う + this.log("warning", message, data, important); } - public succ(message: string, data?: Record | null, important = false): void { // 何かに成功した状況で使う - this.log('success', message, data, important); + public succ( + message: string, + data?: Record | null, + important = false, + ): void { + // 何かに成功した状況で使う + this.log("success", message, data, important); } - public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) - if (process.env.NODE_ENV !== 'production' || envOption.verbose) { - this.log('debug', message, data, important); + public debug( + message: string, + data?: Record | null, + important = false, + ): void { + // デバッグ用に使う(開発者に必要だが利用者に不要な情報) + if (process.env.NODE_ENV !== "production" || envOption.verbose) { + this.log("debug", message, data, important); } } - public info(message: string, data?: Record | null, important = false): void { // それ以外 - this.log('info', message, data, important); + public info( + message: string, + data?: Record | null, + important = false, + ): void { + // それ以外 + this.log("info", message, data, important); } } diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index e6b320492..506f29996 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -1,19 +1,36 @@ -import { CacheableUser, User } from '@/models/entities/user.js'; -import { UserGroup } from '@/models/entities/user-group.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; -import { pushNotification } from '@/services/push-notification.js'; -import { Not } from 'typeorm'; -import { Note } from '@/models/entities/note.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderCreate from '@/remote/activitypub/renderer/create.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { deliver } from '@/queue/index.js'; +import type { CacheableUser, User } from "@/models/entities/user.js"; +import type { UserGroup } from "@/models/entities/user-group.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import { + MessagingMessages, + UserGroupJoinings, + Mutings, + Users, +} from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { + publishMessagingStream, + publishMessagingIndexStream, + publishMainStream, + publishGroupMessagingStream, +} from "@/services/stream.js"; +import { pushNotification } from "@/services/push-notification.js"; +import { Not } from "typeorm"; +import type { Note } from "@/models/entities/note.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import renderCreate from "@/remote/activitypub/renderer/create.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { deliver } from "@/queue/index.js"; -export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) { +export async function 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: genId(), createdAt: new Date(), @@ -34,26 +51,38 @@ export async function createMessage(user: { id: User['id']; host: User['host']; if (recipientUser) { if (Users.isLocalUser(user)) { // 自分のストリーム - publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj); - publishMessagingIndexStream(message.userId, 'message', messageObj); - publishMainStream(message.userId, 'messagingMessage', messageObj); + publishMessagingStream( + message.userId, + recipientUser.id, + "message", + messageObj, + ); + publishMessagingIndexStream(message.userId, "message", messageObj); + publishMainStream(message.userId, "messagingMessage", messageObj); } if (Users.isLocalUser(recipientUser)) { // 相手のストリーム - publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj); - publishMessagingIndexStream(recipientUser.id, 'message', messageObj); - publishMainStream(recipientUser.id, 'messagingMessage', messageObj); + publishMessagingStream( + recipientUser.id, + message.userId, + "message", + messageObj, + ); + publishMessagingIndexStream(recipientUser.id, "message", messageObj); + publishMainStream(recipientUser.id, "messagingMessage", messageObj); } } else if (recipientGroup) { // グループのストリーム - publishGroupMessagingStream(recipientGroup.id, 'message', messageObj); + publishGroupMessagingStream(recipientGroup.id, "message", messageObj); // メンバーのストリーム - const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id }); + const joinings = await UserGroupJoinings.findBy({ + userGroupId: recipientGroup.id, + }); for (const joining of joinings) { - publishMessagingIndexStream(joining.userId, 'message', messageObj); - publishMainStream(joining.userId, 'messagingMessage', messageObj); + publishMessagingIndexStream(joining.userId, "message", messageObj); + publishMainStream(joining.userId, "messagingMessage", messageObj); } } @@ -69,38 +98,49 @@ export async function createMessage(user: { id: User['id']; host: User['host']; const mute = await Mutings.findBy({ muterId: recipientUser.id, }); - if (mute.map(m => m.muteeId).includes(user.id)) return; + if (mute.map((m) => m.muteeId).includes(user.id)) return; //#endregion - publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj); - pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj); + publishMainStream(recipientUser.id, "unreadMessagingMessage", messageObj); + pushNotification(recipientUser.id, "unreadMessagingMessage", messageObj); } else if (recipientGroup) { - const joinings = await UserGroupJoinings.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) }); + const joinings = await UserGroupJoinings.findBy({ + userGroupId: recipientGroup.id, + userId: Not(user.id), + }); for (const joining of joinings) { if (freshMessage.reads.includes(joining.userId)) return; // 既読 - publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj); - pushNotification(joining.userId, 'unreadMessagingMessage', messageObj); + publishMainStream(joining.userId, "unreadMessagingMessage", messageObj); + pushNotification(joining.userId, "unreadMessagingMessage", messageObj); } } }, 2000); - if (recipientUser && Users.isLocalUser(user) && Users.isRemoteUser(recipientUser)) { + if ( + recipientUser && + Users.isLocalUser(user) && + Users.isRemoteUser(recipientUser) + ) { const note = { id: message.id, createdAt: message.createdAt, - fileIds: message.fileId ? [ message.fileId ] : [], + fileIds: message.fileId ? [message.fileId] : [], text: message.text, userId: message.userId, - visibility: 'specified', - mentions: [ recipientUser ].map(u => u.id), - mentionedRemoteUsers: JSON.stringify([ recipientUser ].map(u => ({ - uri: u.uri, - username: u.username, - host: u.host, - }))), + visibility: "specified", + mentions: [recipientUser].map((u) => u.id), + mentionedRemoteUsers: JSON.stringify( + [recipientUser].map((u) => ({ + uri: u.uri, + username: u.username, + host: u.host, + })), + ), } as Note; - const activity = renderActivity(renderCreate(await renderNote(note, false, true), note)); + const activity = renderActivity( + renderCreate(await renderNote(note, false, true), note), + ); deliver(user, activity, recipientUser.inbox); } diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index 1e7ce1981..77caba80c 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -1,11 +1,14 @@ -import config from '@/config/index.js'; -import { MessagingMessages, Users } from '@/models/index.js'; -import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderDelete from '@/remote/activitypub/renderer/delete.js'; -import renderTombstone from '@/remote/activitypub/renderer/tombstone.js'; -import { deliver } from '@/queue/index.js'; +import config from "@/config/index.js"; +import { MessagingMessages, Users } from "@/models/index.js"; +import type { MessagingMessage } from "@/models/entities/messaging-message.js"; +import { + publishGroupMessagingStream, + publishMessagingStream, +} from "@/services/stream.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderDelete from "@/remote/activitypub/renderer/delete.js"; +import renderTombstone from "@/remote/activitypub/renderer/tombstone.js"; +import { deliver } from "@/queue/index.js"; export async function deleteMessage(message: MessagingMessage) { await MessagingMessages.delete(message.id); @@ -17,14 +20,31 @@ async function postDeleteMessage(message: MessagingMessage) { const user = await Users.findOneByOrFail({ id: message.userId }); const recipient = await Users.findOneByOrFail({ id: message.recipientId }); - if (Users.isLocalUser(user)) publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); - if (Users.isLocalUser(recipient)) publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); + if (Users.isLocalUser(user)) + publishMessagingStream( + message.userId, + message.recipientId, + "deleted", + message.id, + ); + if (Users.isLocalUser(recipient)) + publishMessagingStream( + message.recipientId, + message.userId, + "deleted", + message.id, + ); if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) { - const activity = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${message.id}`), user)); + const activity = renderActivity( + renderDelete( + renderTombstone(`${config.url}/notes/${message.id}`), + user, + ), + ); deliver(user, activity, recipient.inbox); } } else if (message.groupId) { - publishGroupMessagingStream(message.groupId, 'deleted', message.id); + publishGroupMessagingStream(message.groupId, "deleted", message.id); } } diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 85faa8e87..5bba98f85 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -1,73 +1,96 @@ -import * as mfm from 'mfm-js'; -import es from '../../db/elasticsearch.js'; -import { publishMainStream, publishNotesStream } from '@/services/stream.js'; -import DeliverManager from '@/remote/activitypub/deliver-manager.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import renderCreate from '@/remote/activitypub/renderer/create.js'; -import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { resolveUser } from '@/remote/resolve-user.js'; -import config from '@/config/index.js'; -import { updateHashtags } from '../update-hashtag.js'; -import { concat } from '@/prelude/array.js'; -import { insertNoteUnread } from '@/services/note/unread.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; -import { extractMentions } from '@/misc/extract-mentions.js'; -import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; -import { extractHashtags } from '@/misc/extract-hashtags.js'; -import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; -import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings, NoteThreadMutings } from '@/models/index.js'; -import { DriveFile } from '@/models/entities/drive-file.js'; -import { App } from '@/models/entities/app.js'; -import { Not, In } from 'typeorm'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { genId } from '@/misc/gen-id.js'; -import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '@/services/chart/index.js'; -import { Poll, IPoll } from '@/models/entities/poll.js'; -import { createNotification } from '../create-notification.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { checkHitAntenna } from '@/misc/check-hit-antenna.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { addNoteToAntenna } from '../add-note-to-antenna.js'; -import { countSameRenotes } from '@/misc/count-same-renotes.js'; -import { deliverToRelays } from '../relay.js'; -import { Channel } from '@/models/entities/channel.js'; -import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { getAntennas } from '@/misc/antenna-cache.js'; -import { endedPollNotificationQueue } from '@/queue/queues.js'; -import { webhookDeliver } from '@/queue/index.js'; -import { Cache } from '@/misc/cache.js'; -import { UserProfile } from '@/models/entities/user-profile.js'; -import { db } from '@/db/postgre.js'; -import { getActiveWebhooks } from '@/misc/webhook-cache.js'; +import * as mfm from "mfm-js"; +import es from "../../db/elasticsearch.js"; +import { publishMainStream, publishNotesStream } from "@/services/stream.js"; +import DeliverManager from "@/remote/activitypub/deliver-manager.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import renderCreate from "@/remote/activitypub/renderer/create.js"; +import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { resolveUser } from "@/remote/resolve-user.js"; +import config from "@/config/index.js"; +import { updateHashtags } from "../update-hashtag.js"; +import { concat } from "@/prelude/array.js"; +import { insertNoteUnread } from "@/services/note/unread.js"; +import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js"; +import { extractMentions } from "@/misc/extract-mentions.js"; +import { extractCustomEmojisFromMfm } from "@/misc/extract-custom-emojis-from-mfm.js"; +import { extractHashtags } from "@/misc/extract-hashtags.js"; +import type { IMentionedRemoteUsers } from "@/models/entities/note.js"; +import { Note } from "@/models/entities/note.js"; +import { + Mutings, + Users, + NoteWatchings, + Notes, + Instances, + UserProfiles, + Antennas, + Followings, + MutedNotes, + Channels, + ChannelFollowings, + Blockings, + NoteThreadMutings, +} from "@/models/index.js"; +import type { DriveFile } from "@/models/entities/drive-file.js"; +import type { App } from "@/models/entities/app.js"; +import { Not, In } from "typeorm"; +import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; +import { genId } from "@/misc/gen-id.js"; +import { + notesChart, + perUserNotesChart, + activeUsersChart, + instanceChart, +} from "@/services/chart/index.js"; +import type { IPoll } from "@/models/entities/poll.js"; +import { Poll } from "@/models/entities/poll.js"; +import { createNotification } from "../create-notification.js"; +import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; +import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; +import { checkWordMute } from "@/misc/check-word-mute.js"; +import { addNoteToAntenna } from "../add-note-to-antenna.js"; +import { countSameRenotes } from "@/misc/count-same-renotes.js"; +import { deliverToRelays } from "../relay.js"; +import type { Channel } from "@/models/entities/channel.js"; +import { normalizeForSearch } from "@/misc/normalize-for-search.js"; +import { getAntennas } from "@/misc/antenna-cache.js"; +import { endedPollNotificationQueue } from "@/queue/queues.js"; +import { webhookDeliver } from "@/queue/index.js"; +import { Cache } from "@/misc/cache.js"; +import type { UserProfile } from "@/models/entities/user-profile.js"; +import { db } from "@/db/postgre.js"; +import { getActiveWebhooks } from "@/misc/webhook-cache.js"; -const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); +const mutedWordsCache = new Cache< + { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] +>(1000 * 60 * 5); -type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; +type NotificationType = "reply" | "renote" | "quote" | "mention"; class NotificationManager { - private notifier: { id: User['id']; }; + private notifier: { id: User["id"] }; private note: Note; private queue: { - target: ILocalUser['id']; + target: ILocalUser["id"]; reason: NotificationType; }[]; - constructor(notifier: { id: User['id']; }, note: Note) { + constructor(notifier: { id: User["id"] }, note: Note) { this.notifier = notifier; this.note = note; this.queue = []; } - public push(notifiee: ILocalUser['id'], reason: NotificationType) { + public push(notifiee: ILocalUser["id"], reason: NotificationType) { // 自分自身へは通知しない if (this.notifier.id === notifiee) return; - const exist = this.queue.find(x => x.target === notifiee); + const exist = this.queue.find((x) => x.target === notifiee); if (exist) { // 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする - if (reason !== 'mention') { + if (reason !== "mention") { exist.reason = reason; } } else { @@ -85,7 +108,7 @@ class NotificationManager { muterId: x.target, }); - const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId); + const mentioneesMutedUserIds = mentioneeMutes.map((m) => m.muteeId); // 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する if (!mentioneesMutedUserIds.includes(this.notifier.id)) { @@ -100,10 +123,10 @@ class NotificationManager { } type MinimumUser = { - id: User['id']; - host: User['host']; - username: User['username']; - uri: User['uri']; + id: User["id"]; + host: User["host"]; + username: User["username"]; + uri: User["uri"]; }; type Option = { @@ -127,390 +150,479 @@ type Option = { app?: App | null; }; -export default async (user: { id: User['id']; username: User['username']; host: User['host']; isSilenced: User['isSilenced']; createdAt: User['createdAt']; }, data: Option, silent = false) => new Promise(async (res, rej) => { - // If you reply outside the channel, match the scope of the target. - // TODO (I think it's a process that could be done on the client side, but it's server side for now.) - if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { - if (data.reply.channelId) { - data.channel = await Channels.findOneBy({ id: data.reply.channelId }); - } else { - data.channel = null; - } - } - - // When you reply in a channel, match the scope of the target - // TODO (I think it's a process that could be done on the client side, but it's server side for now.) - if (data.reply && (data.channel == null) && data.reply.channelId) { - data.channel = await Channels.findOneBy({ id: data.reply.channelId }); - } - - if (data.createdAt == null) data.createdAt = new Date(); - if (data.visibility == null) data.visibility = 'public'; - if (data.localOnly == null) data.localOnly = false; - if (data.channel != null) data.visibility = 'public'; - if (data.channel != null) data.visibleUsers = []; - if (data.channel != null) data.localOnly = true; - - // enforce silent clients on server - if (user.isSilenced && data.visibility === 'public' && data.channel == null) { - data.visibility = 'home'; - } - - // Reject if the target of the renote is a public range other than "Home or Entire". - if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) { - return rej('Renote target is not public or home'); - } - - // If the target of the renote is not public, make it home. - if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') { - data.visibility = 'home'; - } - - // If the target of Renote is followers, make it followers. - if (data.renote && data.renote.visibility === 'followers') { - data.visibility = 'followers'; - } - - // If the reply target is not public, make it home. - if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') { - data.visibility = 'home'; - } - - // Renote local only if you Renote local only. - if (data.renote && data.renote.localOnly && data.channel == null) { - data.localOnly = true; - } - - // If you reply to local only, make it local only. - if (data.reply && data.reply.localOnly && data.channel == null) { - data.localOnly = true; - } - - if (data.text) { - data.text = data.text.trim(); - } else { - data.text = null; - } - - let tags = data.apHashtags; - let emojis = data.apEmojis; - let mentionedUsers = data.apMentions; - - // Parse MFM if needed - if (!tags || !emojis || !mentionedUsers) { - const tokens = data.text ? mfm.parse(data.text)! : []; - const cwTokens = data.cw ? mfm.parse(data.cw)! : []; - const choiceTokens = data.poll && data.poll.choices - ? concat(data.poll.choices.map(choice => mfm.parse(choice)!)) - : []; - - const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); - - tags = data.apHashtags || extractHashtags(combinedTokens); - - emojis = data.apEmojis || extractCustomEmojisFromMfm(combinedTokens); - - mentionedUsers = data.apMentions || await extractMentionedUsers(user, combinedTokens); - } - - tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32); - - if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { - mentionedUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); - } - - if (data.visibility === 'specified') { - if (data.visibleUsers == null) throw new Error('invalid param'); - - for (const u of data.visibleUsers) { - if (!mentionedUsers.some(x => x.id === u.id)) { - mentionedUsers.push(u); +export default async ( + user: { + id: User["id"]; + username: User["username"]; + host: User["host"]; + isSilenced: User["isSilenced"]; + createdAt: User["createdAt"]; + }, + data: Option, + silent = false, +) => + new Promise(async (res, rej) => { + // If you reply outside the channel, match the scope of the target. + // TODO (I think it's a process that could be done on the client side, but it's server side for now.) + if ( + data.reply && + data.channel && + data.reply.channelId !== data.channel.id + ) { + if (data.reply.channelId) { + data.channel = await Channels.findOneBy({ id: data.reply.channelId }); + } else { + data.channel = null; } } - if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) { - data.visibleUsers.push(await Users.findOneByOrFail({ id: data.reply!.userId })); + // When you reply in a channel, match the scope of the target + // TODO (I think it's a process that could be done on the client side, but it's server side for now.) + if (data.reply && data.channel == null && data.reply.channelId) { + data.channel = await Channels.findOneBy({ id: data.reply.channelId }); } - } - const note = await insertNote(user, data, tags, emojis, mentionedUsers); + if (data.createdAt == null) data.createdAt = new Date(); + if (data.visibility == null) data.visibility = "public"; + if (data.localOnly == null) data.localOnly = false; + if (data.channel != null) data.visibility = "public"; + if (data.channel != null) data.visibleUsers = []; + if (data.channel != null) data.localOnly = true; - res(note); + // enforce silent clients on server + if ( + user.isSilenced && + data.visibility === "public" && + data.channel == null + ) { + data.visibility = "home"; + } - // 統計を更新 - notesChart.update(note, true); - perUserNotesChart.update(user, note, true); + // Reject if the target of the renote is a public range other than "Home or Entire". + if ( + data.renote && + data.renote.visibility !== "public" && + data.renote.visibility !== "home" && + data.renote.userId !== user.id + ) { + return rej("Renote target is not public or home"); + } - // Register host - if (Users.isRemoteUser(user)) { - registerOrFetchInstanceDoc(user.host).then(i => { - Instances.increment({ id: i.id }, 'notesCount', 1); - instanceChart.updateNote(i.host, note, true); - }); - } + // If the target of the renote is not public, make it home. + if ( + data.renote && + data.renote.visibility !== "public" && + data.visibility === "public" + ) { + data.visibility = "home"; + } - // ハッシュタグ更新 - if (data.visibility === 'public' || data.visibility === 'home') { - updateHashtags(user, tags); - } + // If the target of Renote is followers, make it followers. + if (data.renote && data.renote.visibility === "followers") { + data.visibility = "followers"; + } - // Increment notes count (user) - incNotesCountOfUser(user); + // If the reply target is not public, make it home. + if ( + data.reply && + data.reply.visibility !== "public" && + data.visibility === "public" + ) { + data.visibility = "home"; + } - // Word mute - mutedWordsCache.fetch(null, () => UserProfiles.find({ - where: { - enableWordMute: true, - }, - select: ['userId', 'mutedWords'], - })).then(us => { - for (const u of us) { - checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => { - if (shouldMute) { - MutedNotes.insert({ - id: genId(), - userId: u.userId, - noteId: note.id, - reason: 'word', - }); + // Renote local only if you Renote local only. + if (data.renote?.localOnly && data.channel == null) { + data.localOnly = true; + } + + // If you reply to local only, make it local only. + if (data.reply?.localOnly && data.channel == null) { + data.localOnly = true; + } + + if (data.text) { + data.text = data.text.trim(); + } else { + data.text = null; + } + + let tags = data.apHashtags; + let emojis = data.apEmojis; + let mentionedUsers = data.apMentions; + + // Parse MFM if needed + if (!((tags && emojis ) && mentionedUsers)) { + const tokens = data.text ? mfm.parse(data.text)! : []; + const cwTokens = data.cw ? mfm.parse(data.cw)! : []; + const choiceTokens = + data.poll?.choices + ? concat(data.poll.choices.map((choice) => mfm.parse(choice)!)) + : []; + + const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); + + tags = data.apHashtags || extractHashtags(combinedTokens); + + emojis = data.apEmojis || extractCustomEmojisFromMfm(combinedTokens); + + mentionedUsers = + data.apMentions || (await extractMentionedUsers(user, combinedTokens)); + } + + tags = tags + .filter((tag) => Array.from(tag || "").length <= 128) + .splice(0, 32); + + if ( + data.reply && + user.id !== data.reply.userId && + !mentionedUsers.some((u) => u.id === data.reply!.userId) + ) { + mentionedUsers.push( + await Users.findOneByOrFail({ id: data.reply!.userId }), + ); + } + + if (data.visibility === "specified") { + if (data.visibleUsers == null) throw new Error("invalid param"); + + for (const u of data.visibleUsers) { + if (!mentionedUsers.some((x) => x.id === u.id)) { + mentionedUsers.push(u); + } + } + + if ( + data.reply && + !data.visibleUsers.some((x) => x.id === data.reply!.userId) + ) { + data.visibleUsers.push( + await Users.findOneByOrFail({ id: data.reply!.userId }), + ); + } + } + + const note = await insertNote(user, data, tags, emojis, mentionedUsers); + + res(note); + + // 統計を更新 + notesChart.update(note, true); + perUserNotesChart.update(user, note, true); + + // Register host + if (Users.isRemoteUser(user)) { + registerOrFetchInstanceDoc(user.host).then((i) => { + Instances.increment({ id: i.id }, "notesCount", 1); + instanceChart.updateNote(i.host, note, true); + }); + } + + // ハッシュタグ更新 + if (data.visibility === "public" || data.visibility === "home") { + updateHashtags(user, tags); + } + + // Increment notes count (user) + incNotesCountOfUser(user); + + // Word mute + mutedWordsCache + .fetch(null, () => + UserProfiles.find({ + where: { + enableWordMute: true, + }, + select: ["userId", "mutedWords"], + }), + ) + .then((us) => { + for (const u of us) { + checkWordMute(note, { id: u.userId }, u.mutedWords).then( + (shouldMute) => { + if (shouldMute) { + MutedNotes.insert({ + id: genId(), + userId: u.userId, + noteId: note.id, + reason: "word", + }); + } + }, + ); + } + }); + + // Antenna + for (const antenna of await getAntennas()) { + checkHitAntenna(antenna, note, user).then((hit) => { + if (hit) { + addNoteToAntenna(antenna, note, user); } }); } - }); - // Antenna - for (const antenna of (await getAntennas())) { - checkHitAntenna(antenna, note, user).then(hit => { - if (hit) { - addNoteToAntenna(antenna, note, user); - } - }); - } - - // Channel - if (note.channelId) { - ChannelFollowings.findBy({ followeeId: note.channelId }).then(followings => { - for (const following of followings) { - insertNoteUnread(following.followerId, note, { - isSpecified: false, - isMentioned: false, - }); - } - }); - } - - if (data.reply) { - saveReply(data.reply, note); - } - - // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき - if (data.renote && (await countSameRenotes(user.id, data.renote.id, note.id) === 0)) { - incRenoteCount(data.renote); - } - - if (data.poll && data.poll.expiresAt) { - const delay = data.poll.expiresAt.getTime() - Date.now(); - endedPollNotificationQueue.add({ - noteId: note.id, - }, { - delay, - removeOnComplete: true, - }); - } - - if (!silent) { - if (Users.isLocalUser(user)) activeUsersChart.write(user); - - // 未読通知を作成 - if (data.visibility === 'specified') { - if (data.visibleUsers == null) throw new Error('invalid param'); - - for (const u of data.visibleUsers) { - // ローカルユーザーのみ - if (!Users.isLocalUser(u)) continue; - - insertNoteUnread(u.id, note, { - isSpecified: true, - isMentioned: false, - }); - } - } else { - for (const u of mentionedUsers) { - // ローカルユーザーのみ - if (!Users.isLocalUser(u)) continue; - - insertNoteUnread(u.id, note, { - isSpecified: false, - isMentioned: true, - }); - } + // Channel + if (note.channelId) { + ChannelFollowings.findBy({ followeeId: note.channelId }).then( + (followings) => { + for (const following of followings) { + insertNoteUnread(following.followerId, note, { + isSpecified: false, + isMentioned: false, + }); + } + }, + ); } - publishNotesStream(note); - - const webhooks = await getActiveWebhooks().then(webhooks => webhooks.filter(x => x.userId === user.id && x.on.includes('note'))); - - for (const webhook of webhooks) { - webhookDeliver(webhook, 'note', { - note: await Notes.pack(note, user), - }); - } - - const nm = new NotificationManager(user, note); - const nmRelatedPromises = []; - - await createMentionedEvents(mentionedUsers, note, nm); - - // If has in reply to note if (data.reply) { - // Fetch watchers - nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm)); + saveReply(data.reply, note); + } - // 通知 - if (data.reply.userHost === null) { - const threadMuted = await NoteThreadMutings.findOneBy({ - userId: data.reply.userId, - threadId: data.reply.threadId || data.reply.id, + // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき + if ( + data.renote && + (await countSameRenotes(user.id, data.renote.id, note.id)) === 0 + ) { + incRenoteCount(data.renote); + } + + if (data.poll?.expiresAt) { + const delay = data.poll.expiresAt.getTime() - Date.now(); + endedPollNotificationQueue.add( + { + noteId: note.id, + }, + { + delay, + removeOnComplete: true, + }, + ); + } + + if (!silent) { + if (Users.isLocalUser(user)) activeUsersChart.write(user); + + // 未読通知を作成 + if (data.visibility === "specified") { + if (data.visibleUsers == null) throw new Error("invalid param"); + + for (const u of data.visibleUsers) { + // ローカルユーザーのみ + if (!Users.isLocalUser(u)) continue; + + insertNoteUnread(u.id, note, { + isSpecified: true, + isMentioned: false, + }); + } + } else { + for (const u of mentionedUsers) { + // ローカルユーザーのみ + if (!Users.isLocalUser(u)) continue; + + insertNoteUnread(u.id, note, { + isSpecified: false, + isMentioned: true, + }); + } + } + + publishNotesStream(note); + + const webhooks = await getActiveWebhooks().then((webhooks) => + webhooks.filter((x) => x.userId === user.id && x.on.includes("note")), + ); + + for (const webhook of webhooks) { + webhookDeliver(webhook, "note", { + note: await Notes.pack(note, user), }); + } - if (!threadMuted) { - nm.push(data.reply.userId, 'reply'); + const nm = new NotificationManager(user, note); + const nmRelatedPromises = []; - const packedReply = await Notes.pack(note, { id: data.reply.userId }); - publishMainStream(data.reply.userId, 'reply', packedReply); + await createMentionedEvents(mentionedUsers, note, nm); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply')); + // If has in reply to note + if (data.reply) { + // Fetch watchers + nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm)); + + // 通知 + if (data.reply.userHost === null) { + const threadMuted = await NoteThreadMutings.findOneBy({ + userId: data.reply.userId, + threadId: data.reply.threadId || data.reply.id, + }); + + if (!threadMuted) { + nm.push(data.reply.userId, "reply"); + + const packedReply = await Notes.pack(note, { + id: data.reply.userId, + }); + publishMainStream(data.reply.userId, "reply", packedReply); + + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === data.reply!.userId && x.on.includes("reply"), + ); + for (const webhook of webhooks) { + webhookDeliver(webhook, "reply", { + note: packedReply, + }); + } + } + } + } + + // If it is renote + if (data.renote) { + const type = data.text ? "quote" : "renote"; + + // Notify + if (data.renote.userHost === null) { + const threadMuted = await NoteThreadMutings.findOneBy({ + userId: data.renote.userId, + threadId: data.renote.threadId || data.renote.id, + }); + + if (!threadMuted) { + nm.push(data.renote.userId, type); + } + } + + // Fetch watchers + nmRelatedPromises.push( + notifyToWatchersOfRenotee(data.renote, user, nm, type), + ); + + // Publish event + if (user.id !== data.renote.userId && data.renote.userHost === null) { + const packedRenote = await Notes.pack(note, { + id: data.renote.userId, + }); + publishMainStream(data.renote.userId, "renote", packedRenote); + + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === data.renote!.userId && x.on.includes("renote"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'reply', { - note: packedReply, + webhookDeliver(webhook, "renote", { + note: packedRenote, }); } } } + + Promise.all(nmRelatedPromises).then(() => { + nm.deliver(); + }); + + //#region AP deliver + if (Users.isLocalUser(user)) { + (async () => { + const noteActivity = await renderNoteOrRenoteActivity(data, note); + const dm = new DeliverManager(user, noteActivity); + + // メンションされたリモートユーザーに配送 + for (const u of mentionedUsers.filter((u) => Users.isRemoteUser(u))) { + dm.addDirectRecipe(u as IRemoteUser); + } + + // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 + if (data.reply?.userHost !== null) { + const u = await Users.findOneBy({ id: data.reply.userId }); + if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); + } + + // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 + if (data.renote?.userHost !== null) { + const u = await Users.findOneBy({ id: data.renote.userId }); + if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); + } + + // フォロワーに配送 + if (["public", "home", "followers"].includes(note.visibility)) { + dm.addFollowersRecipe(); + } + + if (["public"].includes(note.visibility)) { + deliverToRelays(user, noteActivity); + } + + dm.execute(); + })(); + } + //#endregion } - // If it is renote - if (data.renote) { - const type = data.text ? 'quote' : 'renote'; + if (data.channel) { + Channels.increment({ id: data.channel.id }, "notesCount", 1); + Channels.update(data.channel.id, { + lastNotedAt: new Date(), + }); - // Notify - if (data.renote.userHost === null) { - const threadMuted = await NoteThreadMutings.findOneBy({ - userId: data.renote.userId, - threadId: data.renote.threadId || data.renote.id, - }); - - if (!threadMuted) { - nm.push(data.renote.userId, type); + const count = await Notes.countBy({ + userId: user.id, + channelId: data.channel.id, + }).then((count) => { + // この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる + // TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい + if (count === 1) { + Channels.increment({ id: data.channel!.id }, "usersCount", 1); } - } - - // Fetch watchers - nmRelatedPromises.push(notifyToWatchersOfRenotee(data.renote, user, nm, type)); - - // Publish event - if ((user.id !== data.renote.userId) && data.renote.userHost === null) { - const packedRenote = await Notes.pack(note, { id: data.renote.userId }); - publishMainStream(data.renote.userId, 'renote', packedRenote); - - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote')); - for (const webhook of webhooks) { - webhookDeliver(webhook, 'renote', { - note: packedRenote, - }); - } - } + }); } - Promise.all(nmRelatedPromises).then(() => { - nm.deliver(); - }); - - //#region AP deliver - if (Users.isLocalUser(user)) { - (async () => { - const noteActivity = await renderNoteOrRenoteActivity(data, note); - const dm = new DeliverManager(user, noteActivity); - - // メンションされたリモートユーザーに配送 - for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) { - dm.addDirectRecipe(u as IRemoteUser); - } - - // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 - if (data.reply && data.reply.userHost !== null) { - const u = await Users.findOneBy({ id: data.reply.userId }); - if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); - } - - // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 - if (data.renote && data.renote.userHost !== null) { - const u = await Users.findOneBy({ id: data.renote.userId }); - if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u); - } - - // フォロワーに配送 - if (['public', 'home', 'followers'].includes(note.visibility)) { - dm.addFollowersRecipe(); - } - - if (['public'].includes(note.visibility)) { - deliverToRelays(user, noteActivity); - } - - dm.execute(); - })(); - } - //#endregion - } - - if (data.channel) { - Channels.increment({ id: data.channel.id }, 'notesCount', 1); - Channels.update(data.channel.id, { - lastNotedAt: new Date(), - }); - - const count = await Notes.countBy({ - userId: user.id, - channelId: data.channel.id, - }).then(count => { - // この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる - // TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい - if (count === 1) { - Channels.increment({ id: data.channel!.id }, 'usersCount', 1); - } - }); - } - - // Register to search database - index(note); -}); + // Register to search database + index(note); + }); async function 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) - ? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote.id}`, note) - : renderCreate(await renderNote(note, false), note); + const content = + data.renote && + data.text == null && + data.poll == null && + (data.files == null || data.files.length === 0) + ? renderAnnounce( + data.renote.uri + ? data.renote.uri + : `${config.url}/notes/${data.renote.id}`, + note, + ) + : renderCreate(await renderNote(note, false), note); return renderActivity(content); } function incRenoteCount(renote: Note) { - Notes.createQueryBuilder().update() + Notes.createQueryBuilder() + .update() .set({ renoteCount: () => '"renoteCount" + 1', score: () => '"score" + 1', }) - .where('id = :id', { id: renote.id }) + .where("id = :id", { id: renote.id }) .execute(); } -async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { +async function insertNote( + user: { id: User["id"]; host: User["host"] }, + data: Option, + tags: string[], + emojis: string[], + mentionedUsers: MinimumUser[], +) { const insert = new Note({ id: genId(data.createdAt!), createdAt: data.createdAt!, - fileIds: data.files ? data.files.map(file => file.id) : [], + fileIds: data.files ? data.files.map((file) => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, channelId: data.channel ? data.channel.id : null, @@ -523,18 +635,19 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O text: data.text, hasPoll: data.poll != null, cw: data.cw == null ? null : data.cw, - tags: tags.map(tag => normalizeForSearch(tag)), + tags: tags.map((tag) => normalizeForSearch(tag)), emojis, userId: user.id, localOnly: data.localOnly!, visibility: data.visibility as any, - visibleUserIds: data.visibility === 'specified' - ? data.visibleUsers - ? data.visibleUsers.map(u => u.id) - : [] - : [], + visibleUserIds: + data.visibility === "specified" + ? data.visibleUsers + ? data.visibleUsers.map((u) => u.id) + : [] + : [], - attachedFileTypes: data.files ? data.files.map(file => file.type) : [], + attachedFileTypes: data.files ? data.files.map((file) => file.type) : [], // 以下非正規化データ replyUserId: data.reply ? data.reply.userId : null, @@ -549,25 +662,29 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O // Append mentions data if (mentionedUsers.length > 0) { - insert.mentions = mentionedUsers.map(u => u.id); + insert.mentions = mentionedUsers.map((u) => u.id); const profiles = await UserProfiles.findBy({ userId: In(insert.mentions) }); - insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => Users.isRemoteUser(u)).map(u => { - const profile = profiles.find(p => p.userId === u.id); - const url = profile != null ? profile.url : null; - return { - uri: u.uri, - url: url == null ? undefined : url, - username: u.username, - host: u.host, - } as IMentionedRemoteUsers[0]; - })); + insert.mentionedRemoteUsers = JSON.stringify( + mentionedUsers + .filter((u) => Users.isRemoteUser(u)) + .map((u) => { + const profile = profiles.find((p) => p.userId === u.id); + const url = profile != null ? profile.url : null; + return { + uri: u.uri, + url: url == null ? undefined : url, + username: u.username, + host: u.host, + } as IMentionedRemoteUsers[0]; + }), + ); } // 投稿を作成 try { if (insert.hasPoll) { // Start transaction - await db.transaction(async transactionalEntityManager => { + await db.transaction(async (transactionalEntityManager) => { await transactionalEntityManager.insert(Note, insert); const poll = new Poll({ @@ -591,8 +708,8 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { - const err = new Error('Duplicated note'); - err.name = 'duplicated'; + const err = new Error("Duplicated note"); + err.name = "duplicated"; throw err; } @@ -606,7 +723,7 @@ function index(note: Note) { if (note.text == null || config.elasticsearch == null) return; es!.index({ - index: config.elasticsearch.index || 'misskey_note', + index: config.elasticsearch.index || "misskey_note", id: note.id.toString(), body: { text: normalizeForSearch(note.text), @@ -616,7 +733,12 @@ function index(note: Note) { }); } -async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; }, nm: NotificationManager, type: NotificationType) { +async function notifyToWatchersOfRenotee( + renote: Note, + user: { id: User["id"] }, + nm: NotificationManager, + type: NotificationType, +) { const watchers = await NoteWatchings.findBy({ noteId: renote.id, userId: Not(user.id), @@ -627,19 +749,27 @@ async function notifyToWatchersOfRenotee(renote: Note, user: { id: User['id']; } } } -async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, nm: NotificationManager) { +async function notifyToWatchersOfReplyee( + reply: Note, + user: { id: User["id"] }, + nm: NotificationManager, +) { const watchers = await NoteWatchings.findBy({ noteId: reply.id, userId: Not(user.id), }); for (const watcher of watchers) { - nm.push(watcher.userId, 'reply'); + nm.push(watcher.userId, "reply"); } } -async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { - for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { +async function createMentionedEvents( + mentionedUsers: MinimumUser[], + note: Note, + nm: NotificationManager, +) { + for (const u of mentionedUsers.filter((u) => Users.isLocalUser(u))) { const threadMuted = await NoteThreadMutings.findOneBy({ userId: u.id, threadId: note.threadId || note.id, @@ -655,50 +785,60 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, detail: true, }); - publishMainStream(u.id, 'mention', detailPackedNote); + publishMainStream(u.id, "mention", detailPackedNote); - const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); + const webhooks = (await getActiveWebhooks()).filter( + (x) => x.userId === u.id && x.on.includes("mention"), + ); for (const webhook of webhooks) { - webhookDeliver(webhook, 'mention', { + webhookDeliver(webhook, "mention", { note: detailPackedNote, }); } } catch (err) { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') continue; + if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") continue; throw err; } // Create notification - nm.push(u.id, 'mention'); + nm.push(u.id, "mention"); } } function saveReply(reply: Note, note: Note) { - Notes.increment({ id: reply.id }, 'repliesCount', 1); + Notes.increment({ id: reply.id }, "repliesCount", 1); } -function incNotesCountOfUser(user: { id: User['id']; }) { - Users.createQueryBuilder().update() +function incNotesCountOfUser(user: { id: User["id"] }) { + Users.createQueryBuilder() + .update() .set({ updatedAt: new Date(), notesCount: () => '"notesCount" + 1', }) - .where('id = :id', { id: user.id }) + .where("id = :id", { id: user.id }) .execute(); } -async function extractMentionedUsers(user: { host: User['host']; }, tokens: mfm.MfmNode[]): Promise { +async function extractMentionedUsers( + user: { host: User["host"] }, + tokens: mfm.MfmNode[], +): Promise { if (tokens == null) return []; const mentions = extractMentions(tokens); - let mentionedUsers = (await Promise.all(mentions.map(m => - resolveUser(m.username, m.host || user.host).catch(() => null) - ))).filter(x => x != null) as User[]; + let mentionedUsers = ( + await Promise.all( + mentions.map((m) => + resolveUser(m.username, m.host || user.host).catch(() => null), + ), + ) + ).filter((x) => x != null) as User[]; // Drop duplicate users - mentionedUsers = mentionedUsers.filter((u, i, self) => - i === self.findIndex(u2 => u.id === u2.id) + mentionedUsers = mentionedUsers.filter( + (u, i, self) => i === self.findIndex((u2) => u.id === u2.id), ); return mentionedUsers; diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 496320016..392578e2f 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -1,40 +1,54 @@ -import { Brackets, In } from 'typeorm'; -import { publishNoteStream } from '@/services/stream.js'; -import renderDelete from '@/remote/activitypub/renderer/delete.js'; -import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderTombstone from '@/remote/activitypub/renderer/tombstone.js'; -import config from '@/config/index.js'; -import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; -import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; -import { Notes, Users, Instances } from '@/models/index.js'; -import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js'; -import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js'; -import { countSameRenotes } from '@/misc/count-same-renotes.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; -import { deliverToRelays } from '../relay.js'; +import { Brackets, In } from "typeorm"; +import { publishNoteStream } from "@/services/stream.js"; +import renderDelete from "@/remote/activitypub/renderer/delete.js"; +import renderAnnounce from "@/remote/activitypub/renderer/announce.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderTombstone from "@/remote/activitypub/renderer/tombstone.js"; +import config from "@/config/index.js"; +import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; +import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; +import { Notes, Users, Instances } from "@/models/index.js"; +import { + notesChart, + perUserNotesChart, + instanceChart, +} from "@/services/chart/index.js"; +import { + deliverToFollowers, + deliverToUser, +} from "@/remote/activitypub/deliver-manager.js"; +import { countSameRenotes } from "@/misc/count-same-renotes.js"; +import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js"; +import { deliverToRelays } from "../relay.js"; /** * 投稿を削除します。 * @param user 投稿者 * @param note 投稿 */ -export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) { +export default async function ( + user: { id: User["id"]; uri: User["uri"]; host: User["host"] }, + note: Note, + quiet = false, +) { const deletedAt = new Date(); // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき - if (note.renoteId && (await countSameRenotes(user.id, note.renoteId, note.id)) === 0) { - Notes.decrement({ id: note.renoteId }, 'renoteCount', 1); - Notes.decrement({ id: note.renoteId }, 'score', 1); + if ( + note.renoteId && + (await countSameRenotes(user.id, note.renoteId, note.id)) === 0 + ) { + Notes.decrement({ id: note.renoteId }, "renoteCount", 1); + Notes.decrement({ id: note.renoteId }, "score", 1); } if (note.replyId) { - await Notes.decrement({ id: note.replyId }, 'repliesCount', 1); + await Notes.decrement({ id: note.replyId }, "repliesCount", 1); } if (!quiet) { - publishNoteStream(note.id, 'deleted', { + publishNoteStream(note.id, "deleted", { deletedAt: deletedAt, }); @@ -43,25 +57,48 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us let renote: Note | null = null; // if deletd note is renote - if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { + if ( + note.renoteId && + note.text == null && + !note.hasPoll && + (note.fileIds == null || note.fileIds.length === 0) + ) { renote = await Notes.findOneBy({ id: note.renoteId, }); } - const content = renderActivity(renote - ? renderUndo(renderAnnounce(renote.uri || `${config.url}/notes/${renote.id}`, note), user) - : renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user)); + const content = renderActivity( + renote + ? renderUndo( + renderAnnounce( + renote.uri || `${config.url}/notes/${renote.id}`, + note, + ), + user, + ) + : renderDelete( + renderTombstone(`${config.url}/notes/${note.id}`), + user, + ), + ); deliverToConcerned(user, note, content); } // also deliever delete activity to cascaded notes - const cascadingNotes = (await findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes + const cascadingNotes = (await findCascadingNotes(note)).filter( + (note) => !note.localOnly, + ); // filter out local-only notes for (const cascadingNote of cascadingNotes) { if (!cascadingNote.user) continue; if (!Users.isLocalUser(cascadingNote.user)) continue; - const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); + const content = renderActivity( + renderDelete( + renderTombstone(`${config.url}/notes/${cascadingNote.id}`), + cascadingNote.user, + ), + ); deliverToConcerned(cascadingNote.user, cascadingNote, content); } //#endregion @@ -71,8 +108,8 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us perUserNotesChart.update(user, note, false); if (Users.isRemoteUser(user)) { - registerOrFetchInstanceDoc(user.host).then(i => { - Instances.decrement({ id: i.id }, 'notesCount', 1); + registerOrFetchInstanceDoc(user.host).then((i) => { + Instances.decrement({ id: i.id }, "notesCount", 1); instanceChart.updateNote(i.host, note, false); }); } @@ -88,13 +125,16 @@ async function findCascadingNotes(note: Note) { const cascadingNotes: Note[] = []; const recursive = async (noteId: string) => { - const query = Notes.createQueryBuilder('note') - .where('note.replyId = :noteId', { noteId }) - .orWhere(new Brackets(q => { - q.where('note.renoteId = :noteId', { noteId }) - .andWhere('note.text IS NOT NULL'); - })) - .leftJoinAndSelect('note.user', 'user'); + const query = Notes.createQueryBuilder("note") + .where("note.replyId = :noteId", { noteId }) + .orWhere( + new Brackets((q) => { + q.where("note.renoteId = :noteId", { noteId }).andWhere( + "note.text IS NOT NULL", + ); + }), + ) + .leftJoinAndSelect("note.user", "user"); const replies = await query.getMany(); for (const reply of replies) { cascadingNotes.push(reply); @@ -103,18 +143,18 @@ async function findCascadingNotes(note: Note) { }; await recursive(note.id); - return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users + return cascadingNotes.filter((note) => note.userHost === null); // filter out non-local users } async function getMentionedRemoteUsers(note: Note) { const where = [] as any[]; // mention / reply / dm - const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); + const uris = ( + JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers + ).map((x) => x.uri); if (uris.length > 0) { - where.push( - { uri: In(uris) }, - ); + where.push({ uri: In(uris) }); } // renote / quote @@ -126,12 +166,16 @@ async function getMentionedRemoteUsers(note: Note) { if (where.length === 0) return []; - return await Users.find({ + return (await Users.find({ where, - }) as IRemoteUser[]; + })) as IRemoteUser[]; } -async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { +async function deliverToConcerned( + user: { id: ILocalUser["id"]; host: null }, + note: Note, + content: any, +) { deliverToFollowers(user, content); deliverToRelays(user, content); const remoteUsers = await getMentionedRemoteUsers(note); diff --git a/packages/backend/src/services/note/polls/update.ts b/packages/backend/src/services/note/polls/update.ts index 68cbb9835..e02d48d05 100644 --- a/packages/backend/src/services/note/polls/update.ts +++ b/packages/backend/src/services/note/polls/update.ts @@ -1,20 +1,22 @@ -import renderUpdate from '@/remote/activitypub/renderer/update.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderNote from '@/remote/activitypub/renderer/note.js'; -import { Users, Notes } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; -import { deliverToFollowers } from '@/remote/activitypub/deliver-manager.js'; -import { deliverToRelays } from '../../relay.js'; +import renderUpdate from "@/remote/activitypub/renderer/update.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import renderNote from "@/remote/activitypub/renderer/note.js"; +import { Users, Notes } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; +import { deliverToFollowers } from "@/remote/activitypub/deliver-manager.js"; +import { deliverToRelays } from "../../relay.js"; -export async function deliverQuestionUpdate(noteId: Note['id']) { +export async function deliverQuestionUpdate(noteId: Note["id"]) { const note = await Notes.findOneBy({ id: noteId }); - if (note == null) throw new Error('note not found'); + if (note == null) throw new Error("note not found"); const user = await Users.findOneBy({ id: note.userId }); - if (user == null) throw new Error('note not found'); + if (user == null) throw new Error("note not found"); if (Users.isLocalUser(user)) { - const content = renderActivity(renderUpdate(await renderNote(note, false), user)); + const content = renderActivity( + renderUpdate(await renderNote(note, false), user), + ); deliverToFollowers(user, content); deliverToRelays(user, content); } diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts index 84d98769d..582af0b17 100644 --- a/packages/backend/src/services/note/polls/vote.ts +++ b/packages/backend/src/services/note/polls/vote.ts @@ -1,18 +1,23 @@ -import { publishNoteStream } from '@/services/stream.js'; -import { CacheableUser, User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js'; -import { Not } from 'typeorm'; -import { genId } from '@/misc/gen-id.js'; -import { createNotification } from '../../create-notification.js'; +import { publishNoteStream } from "@/services/stream.js"; +import type { CacheableUser } from "@/models/entities/user.js"; +import { User } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import { PollVotes, NoteWatchings, Polls, Blockings } from "@/models/index.js"; +import { Not } from "typeorm"; +import { genId } from "@/misc/gen-id.js"; +import { createNotification } from "../../create-notification.js"; -export default async function(user: CacheableUser, note: Note, choice: number) { +export default async function ( + user: CacheableUser, + note: Note, + choice: number, +) { const poll = await Polls.findOneBy({ noteId: note.id }); - if (poll == null) throw new Error('poll not found'); + if (poll == null) throw new Error("poll not found"); // Check whether is valid choice - if (poll.choices[choice] == null) throw new Error('invalid choice param'); + if (poll.choices[choice] == null) throw new Error("invalid choice param"); // Check blocking if (note.userId !== user.id) { @@ -21,7 +26,7 @@ export default async function(user: CacheableUser, note: Note, choice: number) { blockeeId: user.id, }); if (block) { - throw new Error('blocked'); + throw new Error("blocked"); } } @@ -32,11 +37,11 @@ export default async function(user: CacheableUser, note: Note, choice: number) { }); if (poll.multiple) { - if (exist.some(x => x.choice === choice)) { - throw new Error('already voted'); + if (exist.some((x) => x.choice === choice)) { + throw new Error("already voted"); } } else if (exist.length !== 0) { - throw new Error('already voted'); + throw new Error("already voted"); } // Create vote @@ -50,15 +55,17 @@ export default async function(user: CacheableUser, note: Note, choice: number) { // Increment votes count const index = choice + 1; // In SQL, array index is 1 based - await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); + await Polls.query( + `UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`, + ); - publishNoteStream(note.id, 'pollVoted', { + publishNoteStream(note.id, "pollVoted", { choice: choice, userId: user.id, }); // Notify - createNotification(note.userId, 'pollVote', { + createNotification(note.userId, "pollVote", { notifierId: user.id, noteId: note.id, choice: choice, @@ -68,10 +75,9 @@ export default async function(user: CacheableUser, note: Note, choice: number) { NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), - }) - .then(watchers => { + }).then((watchers) => { for (const watcher of watchers) { - createNotification(watcher.userId, 'pollVote', { + createNotification(watcher.userId, "pollVote", { notifierId: user.id, noteId: note.id, choice: choice, diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 4aca1d043..1a3c52eb5 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -1,21 +1,32 @@ -import { publishNoteStream } from '@/services/stream.js'; -import { renderLike } from '@/remote/activitypub/renderer/like.js'; -import DeliverManager from '@/remote/activitypub/deliver-manager.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import { toDbReaction, decodeReaction } from '@/misc/reaction-lib.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '@/models/index.js'; -import { IsNull, Not } from 'typeorm'; -import { perUserReactionsChart } from '@/services/chart/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { createNotification } from '../../create-notification.js'; -import deleteReaction from './delete.js'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { publishNoteStream } from "@/services/stream.js"; +import { renderLike } from "@/remote/activitypub/renderer/like.js"; +import DeliverManager from "@/remote/activitypub/deliver-manager.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import { toDbReaction, decodeReaction } from "@/misc/reaction-lib.js"; +import type { User, IRemoteUser } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import { + NoteReactions, + Users, + NoteWatchings, + Notes, + Emojis, + Blockings, +} from "@/models/index.js"; +import { IsNull, Not } from "typeorm"; +import { perUserReactionsChart } from "@/services/chart/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { createNotification } from "../../create-notification.js"; +import deleteReaction from "./delete.js"; +import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; +import type { NoteReaction } from "@/models/entities/note-reaction.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; -export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { +export default async ( + user: { id: User["id"]; host: User["host"] }, + note: Note, + reaction?: string, +) => { // Check blocking if (note.userId !== user.id) { const block = await Blockings.findOneBy({ @@ -23,13 +34,16 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, blockeeId: user.id, }); if (block) { - throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7'); + throw new IdentifiableError("e70412a4-7197-4726-8e74-f3e0deb92aa7"); } } // check visibility - if (!await Notes.isVisibleForMe(note, user.id)) { - throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.'); + if (!(await Notes.isVisibleForMe(note, user.id))) { + throw new IdentifiableError( + "68e9d2d1-48bf-42c2-b90a-b20e09fd3d48", + "Note not accessible for you.", + ); } // TODO: cache @@ -59,7 +73,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, await NoteReactions.insert(record); } else { // 同じリアクションがすでにされていたらエラー - throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); + throw new IdentifiableError("51c42bb4-931a-456b-bff7-e5a8a70dd298"); } } else { throw e; @@ -68,12 +82,13 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, // Increment reactions count const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; - await Notes.createQueryBuilder().update() + await Notes.createQueryBuilder() + .update() .set({ reactions: () => sql, score: () => '"score" + 1', }) - .where('id = :id', { id: note.id }) + .where("id = :id", { id: note.id }) .execute(); perUserReactionsChart.update(user, note); @@ -86,21 +101,26 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, name: decodedReaction.name, host: decodedReaction.host ?? IsNull(), }, - select: ['name', 'host', 'originalUrl', 'publicUrl'], + select: ["name", "host", "originalUrl", "publicUrl"], }); - publishNoteStream(note.id, 'reacted', { + publishNoteStream(note.id, "reacted", { reaction: decodedReaction.reaction, - emoji: emoji != null ? { - name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, - url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため - } : null, + emoji: + emoji != null + ? { + name: emoji.host + ? `${emoji.name}@${emoji.host}` + : `${emoji.name}@.`, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため + } + : null, userId: user.id, }); // リアクションされたユーザーがローカルユーザーなら通知を作成 if (note.userHost === null) { - createNotification(note.userId, 'reaction', { + createNotification(note.userId, "reaction", { notifierId: user.id, note: note, noteId: note.id, @@ -112,9 +132,9 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, NoteWatchings.findBy({ noteId: note.id, userId: Not(user.id), - }).then(watchers => { + }).then((watchers) => { for (const watcher of watchers) { - createNotification(watcher.userId, 'reaction', { + createNotification(watcher.userId, "reaction", { notifierId: user.id, note: note, noteId: note.id, @@ -132,11 +152,13 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, dm.addDirectRecipe(reactee as IRemoteUser); } - if (['public', 'home', 'followers'].includes(note.visibility)) { + if (["public", "home", "followers"].includes(note.visibility)) { dm.addFollowersRecipe(); - } else if (note.visibility === 'specified') { - const visibleUsers = await Promise.all(note.visibleUserIds.map(id => Users.findOneBy({ id }))); - for (const u of visibleUsers.filter(u => u && Users.isRemoteUser(u))) { + } else if (note.visibility === "specified") { + const visibleUsers = await Promise.all( + note.visibleUserIds.map((id) => Users.findOneBy({ id })), + ); + for (const u of visibleUsers.filter((u) => u && Users.isRemoteUser(u))) { dm.addDirectRecipe(u as IRemoteUser); } } diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index a7cbcb1c1..82648249e 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -1,15 +1,18 @@ -import { publishNoteStream } from '@/services/stream.js'; -import { renderLike } from '@/remote/activitypub/renderer/like.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import DeliverManager from '@/remote/activitypub/deliver-manager.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { User, IRemoteUser } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { NoteReactions, Users, Notes } from '@/models/index.js'; -import { decodeReaction } from '@/misc/reaction-lib.js'; +import { publishNoteStream } from "@/services/stream.js"; +import { renderLike } from "@/remote/activitypub/renderer/like.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { renderActivity } from "@/remote/activitypub/renderer/index.js"; +import DeliverManager from "@/remote/activitypub/deliver-manager.js"; +import { IdentifiableError } from "@/misc/identifiable-error.js"; +import type { User, IRemoteUser } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import { NoteReactions, Users, Notes } from "@/models/index.js"; +import { decodeReaction } from "@/misc/reaction-lib.js"; -export default async (user: { id: User['id']; host: User['host']; }, note: Note) => { +export default async ( + user: { id: User["id"]; host: User["host"] }, + note: Note, +) => { // if already unreacted const exist = await NoteReactions.findOneBy({ noteId: note.id, @@ -17,35 +20,44 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note) }); if (exist == null) { - throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted'); + throw new IdentifiableError( + "60527ec9-b4cb-4a88-a6bd-32d3ad26817d", + "not reacted", + ); } // Delete reaction const result = await NoteReactions.delete(exist.id); if (result.affected !== 1) { - throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted'); + throw new IdentifiableError( + "60527ec9-b4cb-4a88-a6bd-32d3ad26817d", + "not reacted", + ); } // Decrement reactions count const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`; - await Notes.createQueryBuilder().update() + await Notes.createQueryBuilder() + .update() .set({ reactions: () => sql, }) - .where('id = :id', { id: note.id }) + .where("id = :id", { id: note.id }) .execute(); - Notes.decrement({ id: note.id }, 'score', 1); + Notes.decrement({ id: note.id }, "score", 1); - publishNoteStream(note.id, 'unreacted', { + publishNoteStream(note.id, "unreacted", { reaction: decodeReaction(exist.reaction).reaction, userId: user.id, }); //#region 配信 if (Users.isLocalUser(user) && !note.localOnly) { - const content = renderActivity(renderUndo(await renderLike(exist, note), user)); + const content = renderActivity( + renderUndo(await renderLike(exist, note), user), + ); const dm = new DeliverManager(user, content); if (note.userHost !== null) { const reactee = await Users.findOneBy({ id: note.userId }); diff --git a/packages/backend/src/services/note/read.ts b/packages/backend/src/services/note/read.ts index 915a9e9ee..53188f15f 100644 --- a/packages/backend/src/services/note/read.ts +++ b/packages/backend/src/services/note/read.ts @@ -1,48 +1,66 @@ -import { publishMainStream } from '@/services/stream.js'; -import { Note } from '@/models/entities/note.js'; -import { User } from '@/models/entities/user.js'; -import { NoteUnreads, AntennaNotes, Users, Followings, ChannelFollowings } from '@/models/index.js'; -import { Not, IsNull, In } from 'typeorm'; -import { Channel } from '@/models/entities/channel.js'; -import { checkHitAntenna } from '@/misc/check-hit-antenna.js'; -import { getAntennas } from '@/misc/antenna-cache.js'; -import { readNotificationByQuery } from '@/server/api/common/read-notification.js'; -import { Packed } from '@/misc/schema.js'; +import { publishMainStream } from "@/services/stream.js"; +import type { Note } from "@/models/entities/note.js"; +import type { User } from "@/models/entities/user.js"; +import { + NoteUnreads, + AntennaNotes, + Users, + Followings, + ChannelFollowings, +} from "@/models/index.js"; +import { Not, IsNull, In } from "typeorm"; +import type { Channel } from "@/models/entities/channel.js"; +import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; +import { getAntennas } from "@/misc/antenna-cache.js"; +import { readNotificationByQuery } from "@/server/api/common/read-notification.js"; +import type { Packed } from "@/misc/schema.js"; /** * Mark notes as read */ -export default async function( - userId: User['id'], - notes: (Note | Packed<'Note'>)[], +export default async function ( + userId: User["id"], + notes: (Note | Packed<"Note">)[], info?: { - following: Set; - followingChannels: Set; - } + following: Set; + followingChannels: Set; + }, ) { - const following = info?.following ? info.following : new Set((await Followings.find({ - where: { - followerId: userId, - }, - select: ['followeeId'], - })).map(x => x.followeeId)); - const followingChannels = info?.followingChannels ? info.followingChannels : new Set((await ChannelFollowings.find({ - where: { - followerId: userId, - }, - select: ['followeeId'], - })).map(x => x.followeeId)); + const following = info?.following + ? info.following + : new Set( + ( + await Followings.find({ + where: { + followerId: userId, + }, + select: ["followeeId"], + }) + ).map((x) => x.followeeId), + ); + const followingChannels = info?.followingChannels + ? info.followingChannels + : new Set( + ( + await ChannelFollowings.find({ + where: { + followerId: userId, + }, + select: ["followeeId"], + }) + ).map((x) => x.followeeId), + ); - const myAntennas = (await getAntennas()).filter(a => a.userId === userId); - const readMentions: (Note | Packed<'Note'>)[] = []; - const readSpecifiedNotes: (Note | Packed<'Note'>)[] = []; - const readChannelNotes: (Note | Packed<'Note'>)[] = []; - const readAntennaNotes: (Note | Packed<'Note'>)[] = []; + const myAntennas = (await getAntennas()).filter((a) => a.userId === userId); + const readMentions: (Note | Packed<"Note">)[] = []; + const readSpecifiedNotes: (Note | Packed<"Note">)[] = []; + const readChannelNotes: (Note | Packed<"Note">)[] = []; + const readAntennaNotes: (Note | Packed<"Note">)[] = []; for (const note of notes) { - if (note.mentions && note.mentions.includes(userId)) { + if (note.mentions?.includes(userId)) { readMentions.push(note); - } else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) { + } else if (note.visibleUserIds?.includes(userId)) { readSpecifiedNotes.push(note); } @@ -50,20 +68,37 @@ export default async function( readChannelNotes.push(note); } - if (note.user != null) { // たぶんnullになることは無いはずだけど一応 + if (note.user != null) { + // たぶんnullになることは無いはずだけど一応 for (const antenna of myAntennas) { - if (await checkHitAntenna(antenna, note, note.user, undefined, Array.from(following))) { + if ( + await checkHitAntenna( + antenna, + note, + note.user, + undefined, + Array.from(following), + ) + ) { readAntennaNotes.push(note); } } } } - if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) { + if ( + readMentions.length > 0 || + readSpecifiedNotes.length > 0 || + readChannelNotes.length > 0 + ) { // Remove the record await NoteUnreads.delete({ userId: userId, - noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id), ...readChannelNotes.map(n => n.id)]), + noteId: In([ + ...readMentions.map((n) => n.id), + ...readSpecifiedNotes.map((n) => n.id), + ...readChannelNotes.map((n) => n.id), + ]), }); // TODO: ↓まとめてクエリしたい @@ -71,45 +106,51 @@ export default async function( NoteUnreads.countBy({ userId: userId, isMentioned: true, - }).then(mentionsCount => { + }).then((mentionsCount) => { if (mentionsCount === 0) { // 全て既読になったイベントを発行 - publishMainStream(userId, 'readAllUnreadMentions'); + publishMainStream(userId, "readAllUnreadMentions"); } }); NoteUnreads.countBy({ userId: userId, isSpecified: true, - }).then(specifiedCount => { + }).then((specifiedCount) => { if (specifiedCount === 0) { // 全て既読になったイベントを発行 - publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); + publishMainStream(userId, "readAllUnreadSpecifiedNotes"); } }); NoteUnreads.countBy({ userId: userId, noteChannelId: Not(IsNull()), - }).then(channelNoteCount => { + }).then((channelNoteCount) => { if (channelNoteCount === 0) { // 全て既読になったイベントを発行 - publishMainStream(userId, 'readAllChannels'); + publishMainStream(userId, "readAllChannels"); } }); readNotificationByQuery(userId, { - noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]), + noteId: In([ + ...readMentions.map((n) => n.id), + ...readSpecifiedNotes.map((n) => n.id), + ]), }); } if (readAntennaNotes.length > 0) { - await AntennaNotes.update({ - antennaId: In(myAntennas.map(a => a.id)), - noteId: In(readAntennaNotes.map(n => n.id)), - }, { - read: true, - }); + await AntennaNotes.update( + { + antennaId: In(myAntennas.map((a) => a.id)), + noteId: In(readAntennaNotes.map((n) => n.id)), + }, + { + read: true, + }, + ); // TODO: まとめてクエリしたい for (const antenna of myAntennas) { @@ -119,13 +160,13 @@ export default async function( }); if (count === 0) { - publishMainStream(userId, 'readAntenna', antenna); + publishMainStream(userId, "readAntenna", antenna); } } - Users.getHasUnreadAntenna(userId).then(unread => { + Users.getHasUnreadAntenna(userId).then((unread) => { if (!unread) { - publishMainStream(userId, 'readAllAntennas'); + publishMainStream(userId, "readAllAntennas"); } }); } diff --git a/packages/backend/src/services/note/unread.ts b/packages/backend/src/services/note/unread.ts index d9ed711e0..275b230d4 100644 --- a/packages/backend/src/services/note/unread.ts +++ b/packages/backend/src/services/note/unread.ts @@ -1,20 +1,24 @@ -import { Note } from '@/models/entities/note.js'; -import { publishMainStream } from '@/services/stream.js'; -import { User } from '@/models/entities/user.js'; -import { Mutings, NoteThreadMutings, NoteUnreads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; +import type { Note } from "@/models/entities/note.js"; +import { publishMainStream } from "@/services/stream.js"; +import type { User } from "@/models/entities/user.js"; +import { Mutings, NoteThreadMutings, NoteUnreads } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; -export async function insertNoteUnread(userId: User['id'], note: Note, params: { - // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse - isSpecified: boolean; - isMentioned: boolean; -}) { +export async function insertNoteUnread( + userId: User["id"], + note: Note, + params: { + // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse + isSpecified: boolean; + isMentioned: boolean; + }, +) { //#region ミュートしているなら無視 // TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする const mute = await Mutings.findBy({ muterId: userId, }); - if (mute.map(m => m.muteeId).includes(note.userId)) return; + if (mute.map((m) => m.muteeId).includes(note.userId)) return; //#endregion // スレッドミュート @@ -43,13 +47,13 @@ export async function insertNoteUnread(userId: User['id'], note: Note, params: { if (exist == null) return; if (params.isMentioned) { - publishMainStream(userId, 'unreadMention', note.id); + publishMainStream(userId, "unreadMention", note.id); } if (params.isSpecified) { - publishMainStream(userId, 'unreadSpecifiedNote', note.id); + publishMainStream(userId, "unreadSpecifiedNote", note.id); } if (note.channelId) { - publishMainStream(userId, 'unreadChannel', note.id); + publishMainStream(userId, "unreadChannel", note.id); } }, 2000); } diff --git a/packages/backend/src/services/note/unwatch.ts b/packages/backend/src/services/note/unwatch.ts index 3964b2ba5..b4da5e86d 100644 --- a/packages/backend/src/services/note/unwatch.ts +++ b/packages/backend/src/services/note/unwatch.ts @@ -1,8 +1,8 @@ -import { User } from '@/models/entities/user.js'; -import { NoteWatchings } from '@/models/index.js'; -import { Note } from '@/models/entities/note.js'; +import type { User } from "@/models/entities/user.js"; +import { NoteWatchings } from "@/models/index.js"; +import type { Note } from "@/models/entities/note.js"; -export default async (me: User['id'], note: Note) => { +export default async (me: User["id"], note: Note) => { await NoteWatchings.delete({ noteId: note.id, userId: me, diff --git a/packages/backend/src/services/note/watch.ts b/packages/backend/src/services/note/watch.ts index 2210c44a7..2a99dd694 100644 --- a/packages/backend/src/services/note/watch.ts +++ b/packages/backend/src/services/note/watch.ts @@ -1,10 +1,10 @@ -import { User } from '@/models/entities/user.js'; -import { Note } from '@/models/entities/note.js'; -import { NoteWatchings } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { NoteWatching } from '@/models/entities/note-watching.js'; +import type { User } from "@/models/entities/user.js"; +import type { Note } from "@/models/entities/note.js"; +import { NoteWatchings } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import type { NoteWatching } from "@/models/entities/note-watching.js"; -export default async (me: User['id'], note: Note) => { +export default async (me: User["id"], note: Note) => { // 自分の投稿はwatchできない if (me === note.userId) { return; diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 393a23d05..0e51ad967 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -1,50 +1,61 @@ -import push from 'web-push'; -import config from '@/config/index.js'; -import { SwSubscriptions } from '@/models/index.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Packed } from '@/misc/schema.js'; -import { getNoteSummary } from '@/misc/get-note-summary.js'; +import push from "web-push"; +import config from "@/config/index.js"; +import { SwSubscriptions } from "@/models/index.js"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import type { Packed } from "@/misc/schema.js"; +import { getNoteSummary } from "@/misc/get-note-summary.js"; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { - 'notification': Packed<'Notification'>; - 'unreadMessagingMessage': Packed<'MessagingMessage'>; - 'readNotifications': { notificationIds: string[] }; - 'readAllNotifications': undefined; - 'readAllMessagingMessages': undefined; - 'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string }; + notification: Packed<"Notification">; + unreadMessagingMessage: Packed<"MessagingMessage">; + readNotifications: { notificationIds: string[] }; + readAllNotifications: undefined; + readAllMessagingMessages: undefined; + readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string }; }; // プッシュメッセージサーバーには文字数制限があるため、内容を削減します -function truncateNotification(notification: Packed<'Notification'>): any { +function truncateNotification(notification: Packed<"Notification">): any { if (notification.note) { return { ...notification, note: { ...notification.note, // textをgetNoteSummaryしたものに置き換える - text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note), + text: getNoteSummary( + notification.type === "renote" + ? (notification.note.renote as Packed<"Note">) + : notification.note, + ), cw: undefined, reply: undefined, renote: undefined, user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる - } + }, }; } return notification; } -export async function pushNotification(userId: string, type: T, body: pushNotificationsTypes[T]) { +export async function pushNotification( + userId: string, + type: T, + body: pushNotificationsTypes[T], +) { const meta = await fetchMeta(); - if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; + if ( + !meta.enableServiceWorker || + meta.swPublicKey == null || + meta.swPrivateKey == null + ) + return; // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 - push.setVapidDetails(config.url, - meta.swPublicKey, - meta.swPrivateKey); + push.setVapidDetails(config.url, meta.swPublicKey, meta.swPrivateKey); // Fetch const subscriptions = await SwSubscriptions.findBy({ @@ -60,26 +71,35 @@ export async function pushNotification(u }, }; - push.sendNotification(pushSubscription, JSON.stringify({ - type, - body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body, - userId, - dateTime: (new Date()).getTime(), - }), { - proxy: config.proxy, - }).catch((err: any) => { - //swLogger.info(err.statusCode); - //swLogger.info(err.headers); - //swLogger.info(err.body); + push + .sendNotification( + pushSubscription, + JSON.stringify({ + type, + body: + type === "notification" + ? truncateNotification(body as Packed<"Notification">) + : body, + userId, + dateTime: new Date().getTime(), + }), + { + proxy: config.proxy, + }, + ) + .catch((err: any) => { + //swLogger.info(err.statusCode); + //swLogger.info(err.headers); + //swLogger.info(err.body); - if (err.statusCode === 410) { - SwSubscriptions.delete({ - userId: userId, - endpoint: subscription.endpoint, - auth: subscription.auth, - publickey: subscription.publickey, - }); - } - }); + if (err.statusCode === 410) { + SwSubscriptions.delete({ + userId: userId, + endpoint: subscription.endpoint, + auth: subscription.auth, + publickey: subscription.publickey, + }); + } + }); } } diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index df7d125d0..4c3570e90 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -1,12 +1,14 @@ -import { Instance } from '@/models/entities/instance.js'; -import { Instances } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { toPuny } from '@/misc/convert-host.js'; -import { Cache } from '@/misc/cache.js'; +import type { Instance } from "@/models/entities/instance.js"; +import { Instances } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { toPuny } from "@/misc/convert-host.js"; +import { Cache } from "@/misc/cache.js"; const cache = new Cache(1000 * 60 * 60); -export async function registerOrFetchInstanceDoc(host: string): Promise { +export async function registerOrFetchInstanceDoc( + host: string, +): Promise { host = toPuny(host); const cached = cache.get(host); @@ -20,7 +22,7 @@ export async function registerOrFetchInstanceDoc(host: string): Promise Instances.findOneByOrFail(x.identifiers[0])); + }).then((x) => Instances.findOneByOrFail(x.identifiers[0])); cache.set(host, i); return i; diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index a05645f09..244e05c03 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -1,16 +1,19 @@ -import { IsNull } from 'typeorm'; -import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js'; -import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js'; -import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import { deliver } from '@/queue/index.js'; -import { ILocalUser, User } from '@/models/entities/user.js'; -import { Users, Relays } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; -import { Cache } from '@/misc/cache.js'; -import { Relay } from '@/models/entities/relay.js'; -import { createSystemUser } from './create-system-user.js'; +import { IsNull } from "typeorm"; +import { renderFollowRelay } from "@/remote/activitypub/renderer/follow-relay.js"; +import { + renderActivity, + attachLdSignature, +} from "@/remote/activitypub/renderer/index.js"; +import renderUndo from "@/remote/activitypub/renderer/undo.js"; +import { deliver } from "@/queue/index.js"; +import type { ILocalUser, User } from "@/models/entities/user.js"; +import { Users, Relays } from "@/models/index.js"; +import { genId } from "@/misc/gen-id.js"; +import { Cache } from "@/misc/cache.js"; +import type { Relay } from "@/models/entities/relay.js"; +import { createSystemUser } from "./create-system-user.js"; -const ACTOR_USERNAME = 'relay.actor' as const; +const ACTOR_USERNAME = "relay.actor" as const; const relaysCache = new Cache(1000 * 60 * 10); @@ -30,8 +33,8 @@ export async function addRelay(inbox: string) { const relay = await Relays.insert({ id: genId(), inbox, - status: 'requesting', - }).then(x => Relays.findOneByOrFail(x.identifiers[0])); + status: "requesting", + }).then((x) => Relays.findOneByOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); const follow = await renderFollowRelay(relay, relayActor); @@ -47,7 +50,7 @@ export async function removeRelay(inbox: string) { }); if (relay == null) { - throw new Error('relay not found'); + throw new Error("relay not found"); } const relayActor = await getRelayActor(); @@ -66,7 +69,7 @@ export async function listRelay() { export async function relayAccepted(id: string) { const result = await Relays.update(id, { - status: 'accepted', + status: "accepted", }); return JSON.stringify(result); @@ -74,24 +77,29 @@ export async function relayAccepted(id: string) { export async function relayRejected(id: string) { const result = await Relays.update(id, { - status: 'rejected', + status: "rejected", }); return JSON.stringify(result); } -export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) { +export async function deliverToRelays( + user: { id: User["id"]; host: null }, + activity: any, +) { if (activity == null) return; - const relays = await relaysCache.fetch(null, () => Relays.findBy({ - status: 'accepted', - })); + const relays = await relaysCache.fetch(null, () => + Relays.findBy({ + status: "accepted", + }), + ); if (relays.length === 0) return; // TODO //const copy = structuredClone(activity); const copy = JSON.parse(JSON.stringify(activity)); - if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; + if (!copy.to) copy.to = ["https://www.w3.org/ns/activitystreams#Public"]; const signed = await attachLdSignature(copy, user); diff --git a/packages/backend/src/services/send-email-notification.ts b/packages/backend/src/services/send-email-notification.ts index 4a2f94b42..14a9754fe 100644 --- a/packages/backend/src/services/send-email-notification.ts +++ b/packages/backend/src/services/send-email-notification.ts @@ -1,14 +1,14 @@ -import { UserProfiles } from '@/models/index.js'; -import { User } from '@/models/entities/user.js'; -import { sendEmail } from './send-email.js'; -import { I18n } from '@/misc/i18n.js'; -import * as Acct from '@/misc/acct.js'; +import { UserProfiles } from "@/models/index.js"; +import type { User } from "@/models/entities/user.js"; +import { sendEmail } from "./send-email.js"; +import { I18n } from "@/misc/i18n.js"; +import * as Acct from "@/misc/acct.js"; // TODO //const locales = await import('../../../../locales/index.js'); // TODO: locale ファイルをクライアント用とサーバー用で分けたい -async function follow(userId: User['id'], follower: User) { +async function follow(userId: User["id"], follower: User) { /* const userProfile = await UserProfiles.findOneByOrFail({ userId: userId }); if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return; @@ -19,7 +19,7 @@ async function follow(userId: User['id'], follower: User) { */ } -async function receiveFollowRequest(userId: User['id'], follower: User) { +async function receiveFollowRequest(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/services/send-email.ts b/packages/backend/src/services/send-email.ts index 7487cb659..87a0d5e33 100644 --- a/packages/backend/src/services/send-email.ts +++ b/packages/backend/src/services/send-email.ts @@ -1,17 +1,22 @@ -import * as nodemailer from 'nodemailer'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import Logger from './logger.js'; -import config from '@/config/index.js'; +import * as nodemailer from "nodemailer"; +import { fetchMeta } from "@/misc/fetch-meta.js"; +import Logger from "./logger.js"; +import config from "@/config/index.js"; -export const logger = new Logger('email'); +export const logger = new Logger("email"); -export async function sendEmail(to: string, subject: string, html: string, text: string) { +export async function sendEmail( + to: string, + subject: string, + html: string, + text: string, +) { const meta = await fetchMeta(true); const iconUrl = `${config.url}/static-assets/mi-white.png`; const emailSettingUrl = `${config.url}/settings/email`; - const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; + const enableAuth = meta.smtpUser != null && meta.smtpUser !== ""; const transporter = nodemailer.createTransport({ host: meta.smtpHost, @@ -19,10 +24,12 @@ export async function sendEmail(to: string, subject: string, html: string, text: secure: meta.smtpSecure, ignoreTLS: !enableAuth, proxy: config.proxySmtp, - auth: enableAuth ? { - user: meta.smtpUser, - pass: meta.smtpPass, - } : undefined, + auth: enableAuth + ? { + user: meta.smtpUser, + pass: meta.smtpPass, + } + : undefined, } as any); try { @@ -36,7 +43,7 @@ export async function sendEmail(to: string, subject: string, html: string, text: - ${ subject } + ${subject}