diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a78d91900b..0583a66960 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": { - "version": "8.8.0" + "version": "8.9.2" }, "ghcr.io/devcontainers/features/node:1": { "version": "20.5.1" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4222966397..a3bd7feb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,25 @@ --> +## 2023.x.x (unreleased) + +### General +- Feat: アンテナでローカルの投稿のみ収集できるようになりました +- Feat: サーバーサイレンス機能が追加されました +- Enhance: 依存関係の更新 +- Enhance: 新規にフォローした人のをデフォルトでTLに追加できるように +- Enhance: HTLとLTLを2023.10.0アップデート以前まで遡れるように +- Enhance: フォロー/フォロー解除したときに過去分のHTLにも含まれる投稿が反映されるように +- Change: nyaizeはAPIレスポンス時ではなく投稿時に一度だけ非可逆的に行われるようになりました + +### Client +- Enhance: TLの返信表示オプションを記憶するように + +### Server +- Enhance: ストリーミングAPIのパフォーマンスを向上 +- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正 +- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正 + ## 2023.10.1 ### General - Enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に @@ -27,6 +46,7 @@ ### NOTE - 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました - アップデートを行うと、タイムラインが一時的にリセットされます + - アンテナ内のノートも含む - ソフトミュート設定はクライアントではなくサーバー側に保存されるようになったため、アップデートを行うとソフトミュートの設定がリセットされます ### Changes diff --git a/locales/index.d.ts b/locales/index.d.ts index 2494c1709b..363032eaa2 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -198,6 +198,7 @@ export interface Locale { "perDay": string; "stopActivityDelivery": string; "blockThisInstance": string; + "silenceThisInstance": string; "operations": string; "software": string; "version": string; @@ -217,6 +218,8 @@ export interface Locale { "clearCachedFilesConfirm": string; "blockedInstances": string; "blockedInstancesDescription": string; + "silencedInstances": string; + "silencedInstancesDescription": string; "muteAndBlock": string; "mutedUsers": string; "blockedUsers": string; @@ -534,6 +537,7 @@ export interface Locale { "deleteAll": string; "showFixedPostForm": string; "showFixedPostFormInChannel": string; + "withRepliesByDefaultForNewlyFollowed": string; "newNoteRecived": string; "sounds": string; "sound": string; @@ -2051,6 +2055,7 @@ export interface Locale { "userLists": string; "excludeMutingUsers": string; "excludeInactiveUsers": string; + "withReplies": string; }; "_charts": { "federation": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9adc4381a7..f1b57f8bde 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -195,6 +195,7 @@ perHour: "1時間ごと" perDay: "1日ごと" stopActivityDelivery: "アクティビティの配送を停止" blockThisInstance: "このサーバーをブロック" +silenceThisInstance: "サーバーをサイレンス" operations: "操作" software: "ソフトウェア" version: "バージョン" @@ -213,7 +214,9 @@ clearQueueConfirmText: "未配達の投稿は配送されなくなります。 clearCachedFiles: "キャッシュをクリア" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" blockedInstances: "ブロックしたサーバー" -blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このサーバーとやり取りできなくなります。サブドメインもブロックされます。" +blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。" +silencedInstances: "サイレンスしたサーバー" +silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。" muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" @@ -531,6 +534,7 @@ serverLogs: "サーバーログ" deleteAll: "全て削除" showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)" +withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする" newNoteRecived: "新しいノートがあります" sounds: "サウンド" sound: "サウンド" @@ -589,7 +593,7 @@ poll: "アンケート" useCw: "内容を隠す" enablePlayer: "プレイヤーを開く" disablePlayer: "プレイヤーを閉じる" -expandTweet: "ツイートを展開する" +expandTweet: "ポストを展開する" themeEditor: "テーマエディター" description: "説明" describeFile: "キャプションを付ける" @@ -794,7 +798,7 @@ active: "アクティブ" offline: "オフライン" notRecommended: "非推奨" botProtection: "Botプロテクション" -instanceBlocking: "サーバーブロック" +instanceBlocking: "サーバーブロック・サイレンス" selectAccount: "アカウントを選択" switchAccount: "アカウントを切り替え" enabled: "有効" @@ -1966,6 +1970,7 @@ _exportOrImport: userLists: "リスト" excludeMutingUsers: "ミュートしているユーザーを除外" excludeInactiveUsers: "使われていないアカウントを除外" + withReplies: "インポートした人による返信をTLに含むようにする" _charts: federation: "連合" diff --git a/package.json b/package.json index a9b2b13532..8808b71d72 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2023.10.1", + "version": "2023.10.2-beta.1", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.8.0", + "packageManager": "pnpm@8.9.2", "workspaces": [ "packages/frontend", "packages/backend", @@ -51,10 +51,10 @@ "typescript": "5.2.2" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "cross-env": "7.0.3", - "cypress": "13.3.0", + "cypress": "13.3.1", "eslint": "8.51.0", "start-server-and-test": "2.0.1" }, diff --git a/packages/backend/migration/1697247230117-InstanceSilence.js b/packages/backend/migration/1697247230117-InstanceSilence.js new file mode 100644 index 0000000000..5fdbca3b27 --- /dev/null +++ b/packages/backend/migration/1697247230117-InstanceSilence.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class InstanceSilence1697247230117 { + name = 'InstanceSilence1697247230117' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "silencedHosts" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`); + } +} diff --git a/packages/backend/migration/1697420555911-deleteCreatedAt.js b/packages/backend/migration/1697420555911-deleteCreatedAt.js new file mode 100644 index 0000000000..958d61a348 --- /dev/null +++ b/packages/backend/migration/1697420555911-deleteCreatedAt.js @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class DeleteCreatedAt1697420555911 { + name = 'DeleteCreatedAt1697420555911' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_02878d441ceae15ce060b73daf"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c8dfad3b72196dd1d6b5db168a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e11e649824a45d8ed01d597fd9"`); + await queryRunner.query(`DROP INDEX "public"."IDX_db2098070b2b5a523c58181f74"`); + await queryRunner.query(`DROP INDEX "public"."IDX_048a757923ed8b157e9895da53"`); + await queryRunner.query(`DROP INDEX "public"."IDX_1129c2ef687fc272df040bafaa"`); + await queryRunner.query(`DROP INDEX "public"."IDX_118ec703e596086fc4515acb39"`); + await queryRunner.query(`DROP INDEX "public"."IDX_b9a354f7941c1e779f3b33aea6"`); + await queryRunner.query(`DROP INDEX "public"."IDX_71cb7b435b7c0d4843317e7e16"`); + await queryRunner.query(`DROP INDEX "public"."IDX_11e71f2511589dcc8a4d3214f9"`); + await queryRunner.query(`DROP INDEX "public"."IDX_735a5544f9249d412255f47f95"`); + await queryRunner.query(`DROP INDEX "public"."IDX_582f8fab771a9040a12961f3e7"`); + await queryRunner.query(`DROP INDEX "public"."IDX_8f1a239bd077c8864a20c62c2c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f86d57fbca33c7a4e6897490cc"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`); + await queryRunner.query(`DROP INDEX "public"."IDX_fbb4297c927a9b85e9cefa2eb1"`); + await queryRunner.query(`DROP INDEX "public"."IDX_0fb627e1c2f753262a74f0562d"`); + await queryRunner.query(`DROP INDEX "public"."IDX_149d2e44785707548c82999b01"`); + await queryRunner.query(`ALTER TABLE "drive_folder" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "app" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "access_token" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "announcement_read" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "auth_session" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "blocking" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel_following" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "channel_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "clip" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "clip_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "gallery_post" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "gallery_like" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "moderation_log" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "renote_muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_reaction" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "page_like" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "password_reset_request" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "poll_vote" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "promo_read" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "registration_ticket" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "signin" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "sw_subscription" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_note_pining" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "user_pending" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "role_assignment" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "flash" DROP COLUMN "createdAt"`); + await queryRunner.query(`ALTER TABLE "flash_like" DROP COLUMN "createdAt"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "flash_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "flash" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "role_assignment" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "role" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "webhook" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_pending" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_note_pining" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "sw_subscription" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "signin" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "registry_item" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "registration_ticket" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "promo_read" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "poll_vote" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "password_reset_request" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "page_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "page" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_reaction" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "renote_muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "muting" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "moderation_log" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "gallery_like" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "gallery_post" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "follow_request" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "following" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "clip_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "note" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "clip" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel_favorite" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel_following" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "channel" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "blocking" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "auth_session" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "antenna" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_list" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "announcement_read" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "announcement" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "ad" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "access_token" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "app" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "user" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "drive_file" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`ALTER TABLE "drive_folder" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`); + await queryRunner.query(`CREATE INDEX "IDX_149d2e44785707548c82999b01" ON "flash" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_0fb627e1c2f753262a74f0562d" ON "poll_vote" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_fbb4297c927a9b85e9cefa2eb1" ON "page" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_f86d57fbca33c7a4e6897490cc" ON "muting" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_8f1a239bd077c8864a20c62c2c" ON "gallery_post" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_582f8fab771a9040a12961f3e7" ON "following" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_735a5544f9249d412255f47f95" ON "channel_favorite" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_11e71f2511589dcc8a4d3214f9" ON "channel_following" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_71cb7b435b7c0d4843317e7e16" ON "channel" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_b9a354f7941c1e779f3b33aea6" ON "blocking" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_118ec703e596086fc4515acb39" ON "announcement" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_1129c2ef687fc272df040bafaa" ON "ad" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_048a757923ed8b157e9895da53" ON "app" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_db2098070b2b5a523c58181f74" ON "abuse_user_report" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_e11e649824a45d8ed01d597fd9" ON "user" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_c8dfad3b72196dd1d6b5db168a" ON "drive_file" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_02878d441ceae15ce060b73daf" ON "drive_folder" ("createdAt") `); + } +} diff --git a/packages/backend/migration/1697436246389-antenna-localOnly.js b/packages/backend/migration/1697436246389-antenna-localOnly.js new file mode 100644 index 0000000000..0228673291 --- /dev/null +++ b/packages/backend/migration/1697436246389-antenna-localOnly.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AntennaLocalOnly1697436246389 { + name = 'AntennaLocalOnly1697436246389' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" ADD "localOnly" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "localOnly"`); + } +} diff --git a/packages/backend/migration/1697441463087-FollowRequestWithReplies.js b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js new file mode 100644 index 0000000000..214c6f6680 --- /dev/null +++ b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + + +export class FollowRequestWithReplies1697441463087 { + name = 'FollowRequestWithReplies1697441463087' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "follow_request" ADD "withReplies" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "withReplies"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index bc24b4938c..30ca2e1102 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -59,9 +59,9 @@ "@aws-sdk/client-s3": "3.412.0", "@aws-sdk/lib-storage": "3.412.0", "@smithy/node-http-handler": "2.1.5", - "@bull-board/api": "5.8.4", - "@bull-board/fastify": "5.8.4", - "@bull-board/ui": "5.8.4", + "@bull-board/api": "5.9.1", + "@bull-board/fastify": "5.9.1", + "@bull-board/ui": "5.9.1", "@discordapp/twemoji": "14.1.2", "@fastify/accepts": "4.2.0", "@fastify/cookie": "9.1.0", @@ -75,10 +75,10 @@ "@nestjs/core": "10.2.7", "@nestjs/testing": "10.2.7", "@peertube/http-signature": "1.7.0", - "@simplewebauthn/server": "8.2.0", + "@simplewebauthn/server": "8.3.2", "@sinonjs/fake-timers": "11.1.0", "@swc/cli": "0.1.62", - "@swc/core": "1.3.92", + "@swc/core": "1.3.93", "accepts": "1.3.8", "ajv": "8.12.0", "archiver": "6.0.1", @@ -86,7 +86,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "4.12.3", + "bullmq": "4.12.4", "cacheable-lookup": "7.0.0", "cbor": "9.0.1", "chalk": "5.3.0", @@ -97,7 +97,7 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "4.23.2", + "fastify": "4.24.2", "feed": "4.2.2", "file-type": "18.5.0", "fluent-ffmpeg": "2.1.2", @@ -121,13 +121,13 @@ "mime-types": "2.1.35", "misskey-js": "workspace:*", "ms": "3.0.0-canary.1", - "nanoid": "5.0.1", + "nanoid": "5.0.2", "nested-property": "4.0.0", "node-fetch": "3.3.2", "nodemailer": "6.9.6", "nsfwjs": "2.4.2", "oauth": "0.10.0", - "oauth2orize": "1.11.1", + "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", "otpauth": "9.1.5", @@ -155,7 +155,7 @@ "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", - "systeminformation": "5.21.11", + "systeminformation": "5.21.12", "tinycolor2": "1.6.0", "tmp": "0.2.1", "tsc-alias": "1.8.8", @@ -173,36 +173,36 @@ "@jest/globals": "29.7.0", "@simplewebauthn/typescript-types": "8.0.0", "@swc/jest": "0.2.29", - "@types/accepts": "1.3.5", - "@types/archiver": "5.3.3", - "@types/bcryptjs": "2.4.4", - "@types/body-parser": "1.19.3", + "@types/accepts": "1.3.6", + "@types/archiver": "5.3.4", + "@types/bcryptjs": "2.4.5", + "@types/body-parser": "1.19.4", "@types/cbor": "6.0.0", - "@types/color-convert": "2.0.1", - "@types/content-disposition": "0.5.6", + "@types/color-convert": "2.0.2", + "@types/content-disposition": "0.5.7", "@types/fluent-ffmpeg": "2.1.22", "@types/http-link-header": "1.0.3", "@types/jest": "29.5.5", - "@types/js-yaml": "4.0.6", + "@types/js-yaml": "4.0.7", "@types/jsdom": "21.1.3", "@types/jsonld": "1.5.10", "@types/jsrsasign": "10.5.9", "@types/mime-types": "2.1.2", "@types/ms": "0.7.32", - "@types/node": "20.8.4", + "@types/node": "20.8.6", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.11", "@types/oauth": "0.9.2", "@types/oauth2orize": "1.11.1", "@types/oauth2orize-pkce": "0.1.0", - "@types/pg": "8.10.4", + "@types/pg": "8.10.5", "@types/pug": "2.0.7", "@types/punycode": "2.1.0", "@types/qrcode": "1.5.2", "@types/random-seed": "0.3.3", "@types/ratelimiter": "3.4.4", "@types/rename": "1.0.5", - "@types/sanitize-html": "2.9.1", + "@types/sanitize-html": "2.9.2", "@types/semver": "7.5.3", "@types/sharp": "0.32.0", "@types/simple-oauth2": "5.0.5", @@ -211,9 +211,9 @@ "@types/tmp": "0.2.4", "@types/vary": "1.1.1", "@types/web-push": "3.6.1", - "@types/ws": "8.5.6", - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@types/ws": "8.5.7", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "aws-sdk-client-mock": "3.0.0", "cross-env": "7.0.3", "eslint": "8.51.0", diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index db64f42754..350aa6ba24 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -180,13 +180,13 @@ export class AccountMoveService { { muteeId: dst.id, expiresAt: IsNull() }, ).then(mutings => mutings.map(muting => muting.muterId)); - const newMutings: Map = new Map(); + const newMutings: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { - id = this.idService.genId(); + id = this.idService.gen(); } while (newMutings.has(id)); return id; }; @@ -194,7 +194,6 @@ export class AccountMoveService { if (existingMutingsMuterUserIds.includes(muting.muterId)) continue; // skip if already muted indefinitely newMutings.set(genId(), { ...muting, - createdAt: new Date(), muteeId: dst.id, }); } @@ -228,20 +227,19 @@ export class AccountMoveService { }, }).then(memberships => memberships.map(membership => membership.userListId)); - const newMemberships: Map = new Map(); + const newMemberships: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { - id = this.idService.genId(); + id = this.idService.gen(); } while (newMemberships.has(id)); return id; }; for (const membership of oldMemberships) { if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list newMemberships.set(genId(), { - createdAt: new Date(), userId: dst.id, userListId: membership.userListId, userListUserId: membership.userListUserId, diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index a5330db53f..ec1a082d78 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -53,7 +53,7 @@ export class AnnouncementService { })) .andWhere(new Brackets(qb => { qb.orWhere('announcement.forExistingUsers = false'); - qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt }); + qb.orWhere('announcement.id > :userId', { userId: user.id }); })) .andWhere(`announcement.id NOT IN (${ readsQuery.getQuery() })`); @@ -65,8 +65,7 @@ export class AnnouncementService { @bindThis public async create(values: Partial, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> { const announcement = await this.announcementsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: null, title: values.title, text: values.text, @@ -179,8 +178,7 @@ export class AnnouncementService { public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise { try { await this.announcementReadsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), announcementId: announcementId, userId: user.id, }); @@ -204,7 +202,7 @@ export class AnnouncementService { const reads = me ? (options?.reads ?? await this.getReads(me.id)) : []; return announcements.map(announcement => ({ id: announcement.id, - createdAt: announcement.createdAt.toISOString(), + createdAt: this.idService.parse(announcement.id).date.toISOString(), updatedAt: announcement.updatedAt?.toISOString() ?? null, text: announcement.text, title: announcement.title, diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index ca7624b1d4..65be275548 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -39,7 +39,7 @@ export class AntennaService implements OnApplicationShutdown { private utilityService: UtilityService, private globalEventService: GlobalEventService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { this.antennasFetched = false; this.antennas = []; @@ -57,14 +57,12 @@ export class AntennaService implements OnApplicationShutdown { case 'antennaCreated': this.antennas.push({ ...body, - createdAt: new Date(body.createdAt), lastUsedAt: new Date(body.lastUsedAt), }); break; case 'antennaUpdated': this.antennas[this.antennas.findIndex(a => a.id === body.id)] = { ...body, - createdAt: new Date(body.createdAt), lastUsedAt: new Date(body.lastUsedAt), }; break; @@ -86,7 +84,7 @@ export class AntennaService implements OnApplicationShutdown { const redisPipeline = this.redisForTimelines.pipeline(); for (const antenna of matchedAntennas) { - this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); + this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } @@ -100,6 +98,8 @@ export class AntennaService implements OnApplicationShutdown { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; + if (antenna.localOnly && noteUser.host != null) return false; + if (!antenna.withReplies && note.replyId != null) return false; if (antenna.src === 'home') { diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index 3d9982e80f..e94f1eb531 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -46,8 +46,7 @@ export class ClipService { } const clip = await this.clipsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: name, isPublic: isPublic, @@ -109,7 +108,7 @@ export class ClipService { try { await this.clipNotesRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), noteId: noteId, clipId: clip.id, }); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 0dc025d998..e7e66646fc 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -61,7 +61,7 @@ import { FileInfoService } from './FileInfoService.js'; import { SearchService } from './SearchService.js'; import { ClipService } from './ClipService.js'; import { FeaturedService } from './FeaturedService.js'; -import { RedisTimelineService } from './RedisTimelineService.js'; +import { FunoutTimelineService } from './FunoutTimelineService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -190,7 +190,7 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService }; -const $RedisTimelineService: Provider = { provide: 'RedisTimelineService', useExisting: RedisTimelineService }; +const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -323,7 +323,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SearchService, ClipService, FeaturedService, - RedisTimelineService, + FunoutTimelineService, ChartLoggerService, FederationChart, NotesChart, @@ -449,7 +449,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SearchService, $ClipService, $FeaturedService, - $RedisTimelineService, + $FunoutTimelineService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -576,7 +576,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SearchService, ClipService, FeaturedService, - RedisTimelineService, + FunoutTimelineService, FederationChart, NotesChart, UsersChart, @@ -701,7 +701,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SearchService, $ClipService, $FeaturedService, - $RedisTimelineService, + $FunoutTimelineService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 3419d0b497..6b475316f0 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -52,8 +52,7 @@ export class CreateSystemUserService { if (exist) throw new Error('the user is already exists'); account = await transactionalEntityManager.insert(MiUser, { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), username: username, usernameLower: username.toLowerCase(), host: null, diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 145c224f67..9a8267b466 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -69,7 +69,7 @@ export class CustomEmojiService implements OnApplicationShutdown { roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; }, moderator?: MiUser): Promise { const emoji = await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), updatedAt: new Date(), name: data.name, category: data.category, @@ -331,7 +331,7 @@ export class CustomEmojiService implements OnApplicationShutdown { const queryOrNull = async () => (await this.emojisRepository.findOneBy({ name, - host: host ?? IsNull(), + host, })) ?? null; const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index cecbec9638..484f4fc52e 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -564,8 +564,7 @@ export class DriveService { const folder = await fetchFolder(); let file = new MiDriveFile(); - file.id = this.idService.genId(); - file.createdAt = new Date(); + file.id = this.idService.gen(); file.userId = user ? user.id : null; file.userHost = user ? user.host : null; file.folderId = folder !== null ? folder.id : null; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 61806583c6..e41f010e48 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -56,7 +56,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { if (index == null) { const i = await this.instancesRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), host, firstRetrievedAt: new Date(), }).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/FunoutTimelineService.ts similarity index 95% rename from packages/backend/src/core/RedisTimelineService.ts rename to packages/backend/src/core/FunoutTimelineService.ts index 94541759cc..c633c329e5 100644 --- a/packages/backend/src/core/RedisTimelineService.ts +++ b/packages/backend/src/core/FunoutTimelineService.ts @@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; @Injectable() -export class RedisTimelineService { +export class FunoutTimelineService { constructor( @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, @@ -77,4 +77,9 @@ export class RedisTimelineService { ); }); } + + @bindThis + public purge(name: string) { + return this.redisForTimelines.del('list:' + name); + } } diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index ddff28359a..d378999907 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -45,7 +45,7 @@ export class HashtagService { await this.updateHashtag(user, tag, true, true); } - for (const tag of (user.tags ?? []).filter(x => !tags.includes(x))) { + for (const tag of user.tags.filter(x => !tags.includes(x))) { await this.updateHashtag(user, tag, true, false); } } @@ -120,7 +120,7 @@ export class HashtagService { } else { if (isUserAttached) { this.hashtagsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), name: tag, mentionedUserIds: [], mentionedUsersCount: 0, @@ -137,7 +137,7 @@ export class HashtagService { } as MiHashtag); } else { this.hashtagsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), name: tag, mentionedUserIds: [user.id], mentionedUsersCount: 1, diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 06c58ad8a1..c98b8ea6fc 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -26,17 +26,21 @@ export class IdService { this.method = config.id.toLowerCase(); } + /** + * 時間を元にIDを生成します(省略時は現在日時) + * @param time 日時 + */ @bindThis - public genId(date?: Date): string { - if (!date || (date > new Date())) date = new Date(); + public gen(time?: number): string { + const t = (!time || (time > Date.now())) ? Date.now() : time; switch (this.method) { - case 'aid': return genAid(date); - case 'aidx': return genAidx(date); - case 'meid': return genMeid(date); - case 'meidg': return genMeidg(date); - case 'ulid': return ulid(date.getTime()); - case 'objectid': return genObjectId(date); + case 'aid': return genAid(t); + case 'aidx': return genAidx(t); + case 'meid': return genMeid(t); + case 'meidg': return genMeidg(t); + case 'ulid': return ulid(t); + case 'objectid': return genObjectId(t); default: throw new Error('unrecognized id generation method'); } } diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts index f7f9063d92..8b78d02047 100644 --- a/packages/backend/src/core/ModerationLogService.ts +++ b/packages/backend/src/core/ModerationLogService.ts @@ -24,8 +24,7 @@ export class ModerationLogService { @bindThis public async log(moderator: { id: MiUser['id'] }, type: T, info?: ModerationLogPayloads[T]) { await this.moderationLogsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: moderator.id, type: type, info: (info as any) ?? {}, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 64d2880ba1..30d7f3e76a 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -54,7 +54,9 @@ import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { nyaize } from '@/misc/nyaize.js'; +import { UtilityService } from '@/core/UtilityService.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -195,7 +197,7 @@ export class NoteCreateService implements OnApplicationShutdown { private idService: IdService, private globalEventService: GlobalEventService, private queueService: QueueService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, private noteReadService: NoteReadService, private notificationService: NotificationService, private relayService: RelayService, @@ -214,6 +216,7 @@ export class NoteCreateService implements OnApplicationShutdown { private perUserNotesChart: PerUserNotesChart, private activeUsersChart: ActiveUsersChart, private instanceChart: InstanceChart, + private utilityService: UtilityService, ) { } @bindThis @@ -221,9 +224,11 @@ export class NoteCreateService implements OnApplicationShutdown { id: MiUser['id']; username: MiUser['username']; host: MiUser['host']; - createdAt: MiUser['createdAt']; isBot: MiUser['isBot']; + isCat: MiUser['isCat']; }, data: Option, silent = false): Promise { + let patsedText: mfm.MfmNode[] | null = null; + // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -247,8 +252,10 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.localOnly = true; + const meta = await this.metaService.fetch(); + if (data.visibility === 'public' && data.channel == null) { - const sensitiveWords = (await this.metaService.fetch()).sensitiveWords; + const sensitiveWords = meta.sensitiveWords; if (this.isSensitive(data, sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { @@ -256,6 +263,12 @@ export class NoteCreateService implements OnApplicationShutdown { } } + const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); + + if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { + data.visibility = 'home'; + } + if (data.renote) { switch (data.renote.visibility) { case 'public': @@ -302,6 +315,25 @@ export class NoteCreateService implements OnApplicationShutdown { data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH); } data.text = data.text.trim(); + + if (user.isCat) { + patsedText = mfm.parse(data.text); + function nyaizeNode(node: mfm.MfmNode) { + if (node.type === 'quote') return; + if (node.type === 'text') { + node.props.text = nyaize(node.props.text); + } + if (node.children) { + for (const child of node.children) { + nyaizeNode(child); + } + } + } + for (const node of patsedText) { + nyaizeNode(node); + } + data.text = mfm.toString(patsedText); + } } else { data.text = null; } @@ -312,7 +344,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Parse MFM if needed if (!tags || !emojis || !mentionedUsers) { - const tokens = data.text ? mfm.parse(data.text)! : []; + const tokens = patsedText ?? (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)!)) @@ -327,7 +359,7 @@ export class NoteCreateService implements OnApplicationShutdown { mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens); } - tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); + 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 this.usersRepository.findOneByOrFail({ id: data.reply!.userId })); @@ -360,8 +392,7 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async insertNote(user: { id: MiUser['id']; host: MiUser['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) { const insert = new MiNote({ - id: this.idService.genId(data.createdAt!), - createdAt: data.createdAt!, + id: this.idService.gen(data.createdAt?.getTime()), fileIds: data.files ? data.files.map(file => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, @@ -460,7 +491,6 @@ export class NoteCreateService implements OnApplicationShutdown { id: MiUser['id']; username: MiUser['username']; host: MiUser['host']; - createdAt: MiUser['createdAt']; isBot: MiUser['isBot']; }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { const meta = await this.metaService.fetch(); @@ -554,7 +584,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // Pack the note - const noteObj = await this.noteEntityService.pack(note); + const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true }); this.globalEventService.publishNotesStream(noteObj); @@ -821,9 +851,9 @@ export class NoteCreateService implements OnApplicationShutdown { const r = this.redisForTimelines.pipeline(); if (note.channelId) { - this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); + this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); - this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); const channelFollowings = await this.channelFollowingsRepository.find({ where: { @@ -833,9 +863,9 @@ export class NoteCreateService implements OnApplicationShutdown { }); for (const channelFollowing of channelFollowings) { - this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } } else { @@ -873,9 +903,9 @@ export class NoteCreateService implements OnApplicationShutdown { if (!following.withReplies) continue; } - this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } @@ -891,36 +921,36 @@ export class NoteCreateService implements OnApplicationShutdown { if (!userListMembership.withReplies) continue; } - this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); } } if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL - this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); } } // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { - this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); if (note.visibility === 'public' && note.userHost == null) { - this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r); + this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); } } else { - this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.funoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); + this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); } if (note.visibility === 'public' && note.userHost == null) { - this.redisTimelineService.push('localTimeline', note.id, 1000, r); + this.funoutTimelineService.push('localTimeline', note.id, 1000, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push('localTimelineWithFiles', note.id, 500, r); + this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r); } } } diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts index 147554ee9a..52abb4c2a1 100644 --- a/packages/backend/src/core/NotePiningService.ts +++ b/packages/backend/src/core/NotePiningService.ts @@ -71,8 +71,7 @@ export class NotePiningService { } await this.userNotePiningsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, noteId: note.id, } as MiUserNotePining); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 422e0192cf..03c1735e04 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -57,7 +57,7 @@ export class NoteReadService implements OnApplicationShutdown { if (isThreadMuted) return; const unread = { - id: this.idService.genId(), + id: this.idService.gen(), noteId: note.id, userId: userId, isSpecified: params.isSpecified, diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 32d54d2576..c6d5023e65 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -125,7 +125,7 @@ export class NotificationService implements OnApplicationShutdown { } const notification = { - id: this.idService.genId(), + id: this.idService.gen(), createdAt: new Date(), type: type, notifierId: notifierId, diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 570f2350f1..9e1b5ca78a 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -72,10 +72,8 @@ export class PollService { throw new Error('already voted'); } - // Create vote await this.pollVotesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: user.id, choice: choice, diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 50d1d2e65b..ae8f8a3f19 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -52,14 +52,14 @@ export class QueryService { q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); q.orderBy(`${q.alias}.id`, 'DESC'); } else if (sinceDate && untilDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) }); + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); q.orderBy(`${q.alias}.id`, 'DESC'); } else if (sinceDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) }); + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); q.orderBy(`${q.alias}.id`, 'ASC'); } else if (untilDate) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); q.orderBy(`${q.alias}.id`, 'DESC'); } else { q.orderBy(`${q.alias}.id`, 'DESC'); diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d8c7250034..be378a899b 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -237,10 +237,11 @@ export class QueueService { } @bindThis - public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) { + public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id'], withReplies?: boolean) { return this.dbQueue.add('importFollowing', { user: { id: user.id }, fileId: fileId, + withReplies, }, { removeOnComplete: true, removeOnFail: true, @@ -248,8 +249,8 @@ export class QueueService { } @bindThis - public createImportFollowingToDbJob(user: ThinUser, targets: string[]) { - const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel })); + public createImportFollowingToDbJob(user: ThinUser, targets: string[], withReplies?: boolean) { + const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel, withReplies })); return this.dbQueue.addBulk(jobs); } @@ -342,7 +343,7 @@ export class QueueService { } @bindThis - public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) { + public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean, withReplies?: boolean }[]) { const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel)); return this.relationshipQueue.addBulk(jobs); } @@ -384,6 +385,7 @@ export class QueueService { to: { id: data.to.id }, silent: data.silent, requestId: data.requestId, + withReplies: data.withReplies, }, opts: { removeOnComplete: true, diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index e7bbd44926..1458e2b173 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -148,13 +148,12 @@ export class ReactionService { reaction = FALLBACK; } } else { - reaction = this.normalize(reaction ?? null); + reaction = this.normalize(reaction); } } const record: MiNoteReaction = { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: user.id, reaction, diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts index 7171bf84c5..d40cd080c7 100644 --- a/packages/backend/src/core/RelayService.ts +++ b/packages/backend/src/core/RelayService.ts @@ -54,7 +54,7 @@ export class RelayService { @bindThis public async addRelay(inbox: string): Promise { const relay = await this.relaysRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), inbox, status: 'requesting', }).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 2c3547e4ac..2c2ff7af1d 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import type { Packed } from '@/misc/json-schema.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type RolePolicies = { @@ -103,7 +103,7 @@ export class RoleService implements OnApplicationShutdown { private globalEventService: GlobalEventService, private idService: IdService, private moderationLogService: ModerationLogService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { //this.onMessage = this.onMessage.bind(this); @@ -125,7 +125,6 @@ export class RoleService implements OnApplicationShutdown { if (cached) { cached.push({ ...body, - createdAt: new Date(body.createdAt), updatedAt: new Date(body.updatedAt), lastUsedAt: new Date(body.lastUsedAt), }); @@ -139,7 +138,6 @@ export class RoleService implements OnApplicationShutdown { if (i > -1) { cached[i] = { ...body, - createdAt: new Date(body.createdAt), updatedAt: new Date(body.updatedAt), lastUsedAt: new Date(body.lastUsedAt), }; @@ -159,7 +157,6 @@ export class RoleService implements OnApplicationShutdown { if (cached) { cached.push({ ...body, - createdAt: new Date(body.createdAt), expiresAt: body.expiresAt ? new Date(body.expiresAt) : null, }); } @@ -198,10 +195,10 @@ export class RoleService implements OnApplicationShutdown { return this.userEntityService.isRemoteUser(user); } case 'createdLessThan': { - return user.createdAt.getTime() > (Date.now() - (value.sec * 1000)); + return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000)); } case 'createdMoreThan': { - return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); + return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000)); } case 'followersLessThanOrEq': { return user.followersCount <= value.value; @@ -382,7 +379,7 @@ export class RoleService implements OnApplicationShutdown { @bindThis public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise { - const now = new Date(); + const now = Date.now(); const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); @@ -392,7 +389,7 @@ export class RoleService implements OnApplicationShutdown { }); if (existing) { - if (existing.expiresAt && (existing.expiresAt.getTime() < now.getTime())) { + if (existing.expiresAt && (existing.expiresAt.getTime() < now)) { await this.roleAssignmentsRepository.delete({ roleId: roleId, userId: userId, @@ -403,8 +400,7 @@ export class RoleService implements OnApplicationShutdown { } const created = await this.roleAssignmentsRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now), expiresAt: expiresAt, roleId: roleId, userId: userId, @@ -474,7 +470,7 @@ export class RoleService implements OnApplicationShutdown { const redisPipeline = this.redisClient.pipeline(); for (const role of roles) { - this.redisTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); + this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); this.globalEventService.publishRoleTimelineStream(role.id, 'note', note); } @@ -485,8 +481,7 @@ export class RoleService implements OnApplicationShutdown { public async create(values: Partial, moderator?: MiUser): Promise { const date = new Date(); const created = await this.rolesRepository.insert({ - id: this.idService.genId(), - createdAt: date, + id: this.idService.gen(date.getTime()), updatedAt: date, lastUsedAt: date, name: values.name, diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 3ef321dd32..b6d2bcabc8 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -131,7 +131,7 @@ export class SearchService { await this.meilisearchNoteIndex?.addDocuments([{ id: note.id, - createdAt: note.createdAt.getTime(), + createdAt: this.idService.parse(note.id).date.getTime(), userId: note.userId, userHost: note.userHost, channelId: note.channelId, diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index dfec0cfcfe..b9e3ded46f 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -120,8 +120,7 @@ export class SignupService { if (exist) throw new Error(' the username is already used'); account = await transactionalEntityManager.save(new MiUser({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), username: username, usernameLower: username.toLowerCase(), host: this.utilityService.toPunyNullable(host), diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 087dfd9214..39b19325c3 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -68,8 +68,7 @@ export class UserBlockingService implements OnModuleInit { ]); const blocking = { - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), blocker, blockerId: blocker.id, blockee, diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index beffcc2e9c..4d7e14f683 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { IsNull } from 'typeorm'; import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; @@ -28,6 +28,8 @@ import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import Logger from '../logger.js'; const logger = new Logger('following/create'); @@ -71,6 +73,7 @@ export class UserFollowingService implements OnModuleInit { private instancesRepository: InstancesRepository, private cacheService: CacheService, + private utilityService: UtilityService, private userEntityService: UserEntityService, private idService: IdService, private queueService: QueueService, @@ -81,6 +84,7 @@ export class UserFollowingService implements OnModuleInit { private webhookService: WebhookService, private apRendererService: ApRendererService, private accountMoveService: AccountMoveService, + private funoutTimelineService: FunoutTimelineService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, ) { @@ -91,7 +95,15 @@ export class UserFollowingService implements OnModuleInit { } @bindThis - public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise { + public async follow( + _follower: { id: MiUser['id'] }, + _followee: { id: MiUser['id'] }, + { requestId, silent = false, withReplies }: { + requestId?: string, + silent?: boolean, + withReplies?: boolean, + } = {}, + ): Promise { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }), @@ -118,15 +130,16 @@ export class UserFollowingService implements OnModuleInit { } const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id }); - // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or - // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである + // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or + // フォロワーがローカルユーザーであり、フォロー対象がサイレンスされているサーバーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく if ( followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || - (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') + (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') || + (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host)) ) { let autoAccept = false; @@ -168,12 +181,12 @@ export class UserFollowingService implements OnModuleInit { } if (!autoAccept) { - await this.createFollowRequest(follower, followee, requestId); + await this.createFollowRequest(follower, followee, requestId, withReplies); return; } } - await this.insertFollowingDoc(followee, follower, silent); + await this.insertFollowingDoc(followee, follower, silent, withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); @@ -190,16 +203,17 @@ export class UserFollowingService implements OnModuleInit { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'] }, silent = false, + withReplies?: boolean, ): Promise { if (follower.id === followee.id) return; let alreadyFollowed = false as boolean; await this.followingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: follower.id, followeeId: followee.id, + withReplies: withReplies, // 非正規化 followerHost: follower.host, @@ -276,8 +290,8 @@ export class UserFollowingService implements OnModuleInit { this.perUserFollowingChart.update(follower, followee, true); } - // Publish follow event if (this.userEntityService.isLocalUser(follower) && !silent) { + // Publish follow event this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { @@ -290,6 +304,8 @@ export class UserFollowingService implements OnModuleInit { }); } }); + + this.funoutTimelineService.purge(`homeTimeline:${follower.id}`); } // Publish followed event @@ -343,8 +359,8 @@ export class UserFollowingService implements OnModuleInit { this.decrementFollowing(following.follower, following.followee); - // Publish unfollow event if (!silent && this.userEntityService.isLocalUser(follower)) { + // Publish unfollow event this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { @@ -357,6 +373,8 @@ export class UserFollowingService implements OnModuleInit { }); } }); + + this.funoutTimelineService.purge(`homeTimeline:${follower.id}`); } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { @@ -452,6 +470,7 @@ export class UserFollowingService implements OnModuleInit { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, requestId?: string, + withReplies?: boolean, ): Promise { if (follower.id === followee.id) return; @@ -465,11 +484,11 @@ export class UserFollowingService implements OnModuleInit { if (blocked) throw new Error('blocked'); const followRequest = await this.followRequestsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: follower.id, followeeId: followee.id, requestId, + withReplies, // 非正規化 followerHost: follower.host, @@ -554,7 +573,7 @@ export class UserFollowingService implements OnModuleInit { throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.'); } - await this.insertFollowingDoc(followee, follower); + await this.insertFollowingDoc(followee, follower, false, request.withReplies); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee)); @@ -694,4 +713,12 @@ export class UserFollowingService implements OnModuleInit { }); } } + + @bindThis + public getFollowees(userId: MiUser['id']) { + return this.followingsRepository.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: userId }) + .getMany(); + } } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 5b4e7a711e..702c731fc3 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -93,8 +93,7 @@ export class UserListService implements OnApplicationShutdown { } await this.userListMembershipsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: target.id, userListId: list.id, userListUserId: list.userId, diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 2387c9d648..397e6bdd5d 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -26,8 +26,7 @@ export class UserMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { await this.mutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: expiresAt ?? null, muterId: user.id, muteeId: target.id, diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index d2d2776bd2..b95e41167b 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -35,6 +35,12 @@ export class UtilityService { return blockedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } + @bindThis + public isSilencedHost(silencedHosts: string[] | undefined, host: string | null): boolean { + if (!silencedHosts || host == null) return false; + return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); + } + @bindThis public extractDbHost(uri: string): string { const url = new URL(uri); diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index ff70f7bc0c..930e6ef64a 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -51,7 +51,6 @@ export class WebhookService implements OnApplicationShutdown { if (body.active) { this.webhooks.push({ ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }); } @@ -62,13 +61,11 @@ export class WebhookService implements OnApplicationShutdown { if (i > -1) { this.webhooks[i] = { ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }; } else { this.webhooks.push({ ...body, - createdAt: new Date(body.createdAt), latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null, }); } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index b921ee7454..7aba140689 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -164,7 +164,7 @@ export class ApInboxService { } // don't queue because the sender may attempt again when timeout - await this.userFollowingService.follow(actor, followee, activity.id); + await this.userFollowingService.follow(actor, followee, { requestId: activity.id }); return 'ok'; } @@ -514,8 +514,7 @@ export class ApInboxService { if (users.length < 1) return 'skip'; await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), targetUserId: users[0].id, targetUserHost: users[0].host, reporterId: actor.id, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 7a9d2e21d8..e29bc1d096 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -27,6 +27,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil import { bindThis } from '@/decorators.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { IdService } from '@/core/IdService.js'; import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; @@ -59,6 +60,7 @@ export class ApRendererService { private userKeypairService: UserKeypairService, private apMfmService: ApMfmService, private mfmService: MfmService, + private idService: IdService, ) { } @@ -105,7 +107,7 @@ export class ApRendererService { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Announce', - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), to, cc, object, @@ -137,7 +139,7 @@ export class ApRendererService { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Create', - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), object, }; @@ -437,7 +439,7 @@ export class ApRendererService { }, _misskey_quote: quote, quoteUrl: quote, - published: note.createdAt.toISOString(), + published: this.idService.parse(note.id).date.toISOString(), to, cc, inReplyTo, diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 573dff5b91..1979cdda9c 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -386,7 +386,7 @@ export class ApNoteService { this.logger.info(`register emoji host=${host}, name=${name}`); return await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), host, name, uri: tag.id, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index ea64883395..47f8d7313e 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -295,10 +295,9 @@ export class ApPersonService implements OnModuleInit { // Start transaction await this.db.transaction(async transactionalEntityManager => { user = await transactionalEntityManager.save(new MiUser({ - id: this.idService.genId(), + id: this.idService.gen(), avatarId: null, bannerId: null, - createdAt: new Date(), lastFetchedAt: new Date(), name: truncate(person.name, nameLength), isLocked: person.manuallyApprovesFollowers, @@ -607,8 +606,7 @@ export class ApPersonService implements OnModuleInit { for (const note of featuredNotes.filter((note): note is MiNote => note != null)) { td -= 1000; transactionalEntityManager.insert(MiUserNotePining, { - id: this.idService.genId(new Date(Date.now() + td)), - createdAt: new Date(), + id: this.idService.gen(Date.now() + td), userId: user.id, noteId: note.id, }); diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 55da1469e5..f0918e059c 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -9,6 +9,7 @@ import { AppLockService } from '@/core/AppLockService.js'; import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; import { name, schema } from './entities/active-users.js'; @@ -29,6 +30,7 @@ export default class ActiveUsersChart extends Chart { // eslint-d private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, + private idService: IdService, ) { super(db, (k) => appLockService.getChartInsertLock(k), chartLoggerService.logger, name, schema); } @@ -42,20 +44,21 @@ export default class ActiveUsersChart extends Chart { // eslint-d } @bindThis - public async read(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { + public async read(user: { id: MiUser['id'], host: null }): Promise { + const createdAt = this.idService.parse(user.id).date; 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] : [], + 'registeredWithinWeek': (Date.now() - createdAt.getTime() < week) ? [user.id] : [], + 'registeredWithinMonth': (Date.now() - createdAt.getTime() < month) ? [user.id] : [], + 'registeredWithinYear': (Date.now() - createdAt.getTime() < year) ? [user.id] : [], + 'registeredOutsideWeek': (Date.now() - createdAt.getTime() > week) ? [user.id] : [], + 'registeredOutsideMonth': (Date.now() - createdAt.getTime() > month) ? [user.id] : [], + 'registeredOutsideYear': (Date.now() - createdAt.getTime() > year) ? [user.id] : [], }); } @bindThis - public async write(user: { id: MiUser['id'], host: null, createdAt: MiUser['createdAt'] }): Promise { + public async write(user: { id: MiUser['id'], host: null }): Promise { await this.commit({ 'write': [user.id], }); diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 0e65a10d26..97de891ece 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -9,6 +9,7 @@ import type { AbuseUserReportsRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -18,6 +19,7 @@ export class AbuseUserReportEntityService { private abuseUserReportsRepository: AbuseUserReportsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -29,7 +31,7 @@ export class AbuseUserReportEntityService { return await awaitAll({ id: report.id, - createdAt: report.createdAt.toISOString(), + createdAt: this.idService.parse(report.id).date.toISOString(), comment: report.comment, resolved: report.resolved, reporterId: report.reporterId, diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index ed108f2ce5..265a61e8ad 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -9,12 +9,15 @@ import type { AntennasRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import type { MiAntenna } from '@/models/Antenna.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class AntennaEntityService { constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, + + private idService: IdService, ) { } @@ -26,7 +29,7 @@ export class AntennaEntityService { return { id: antenna.id, - createdAt: antenna.createdAt.toISOString(), + createdAt: this.idService.parse(antenna.id).date.toISOString(), name: antenna.name, keywords: antenna.keywords, excludeKeywords: antenna.excludeKeywords, @@ -34,6 +37,7 @@ export class AntennaEntityService { userListId: antenna.userListId, users: antenna.users, caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, notify: antenna.notify, withReplies: antenna.withReplies, withFile: antenna.withFile, diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts index 44466e24e8..b4760346b7 100644 --- a/packages/backend/src/core/entities/BlockingEntityService.ts +++ b/packages/backend/src/core/entities/BlockingEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiBlocking } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -20,6 +21,7 @@ export class BlockingEntityService { private blockingsRepository: BlockingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -32,7 +34,7 @@ export class BlockingEntityService { return await awaitAll({ id: blocking.id, - createdAt: blocking.createdAt.toISOString(), + createdAt: this.idService.parse(blocking.id).date.toISOString(), blockeeId: blocking.blockeeId, blockee: this.userEntityService.pack(blocking.blockeeId, me, { detail: true, diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 094de4d2d5..dd72953c7d 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -11,6 +11,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiChannel } from '@/models/Channel.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; import { NoteEntityService } from './NoteEntityService.js'; import { In } from 'typeorm'; @@ -38,6 +39,7 @@ export class ChannelEntityService { private noteEntityService: NoteEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -81,7 +83,7 @@ export class ChannelEntityService { return { id: channel.id, - createdAt: channel.createdAt.toISOString(), + createdAt: this.idService.parse(channel.id).date.toISOString(), lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null, name: channel.name, description: channel.description, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index e141db03f1..96422894fd 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiClip } from '@/models/Clip.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -23,6 +24,7 @@ export class ClipEntityService { private clipFavoritesRepository: ClipFavoritesRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -36,7 +38,7 @@ export class ClipEntityService { return await awaitAll({ id: clip.id, - createdAt: clip.createdAt.toISOString(), + createdAt: this.idService.parse(clip.id).date.toISOString(), lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null, userId: clip.userId, user: this.userEntityService.pack(clip.user ?? clip.userId), diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 23273b0413..14be000367 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -17,6 +17,7 @@ import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; import { isMimeImage } from '@/misc/is-mime-image.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { IdService } from '@/core/IdService.js'; import { UtilityService } from '../UtilityService.js'; import { VideoProcessingService } from '../VideoProcessingService.js'; import { UserEntityService } from './UserEntityService.js'; @@ -44,6 +45,7 @@ export class DriveFileEntityService { private utilityService: UtilityService, private driveFolderEntityService: DriveFolderEntityService, private videoProcessingService: VideoProcessingService, + private idService: IdService, ) { } @@ -88,7 +90,7 @@ export class DriveFileEntityService { if (file.type.startsWith('video')) { if (file.thumbnailUrl) return file.thumbnailUrl; - return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url ?? file.uri); + return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url); } else if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { // 動画ではなくリモートかつメディアプロキシ return this.getProxiedUrl(file.uri, 'static'); @@ -143,7 +145,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -155,7 +157,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -167,7 +169,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -179,7 +181,7 @@ export class DriveFileEntityService { .select('SUM(file.size)', 'sum') .getRawOne(); - return parseInt(sum, 10) ?? 0; + return parseInt(sum, 10) || 0; } @bindThis @@ -196,7 +198,7 @@ export class DriveFileEntityService { return await awaitAll>({ id: file.id, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), name: file.name, type: file.type, md5: file.md5, @@ -231,7 +233,7 @@ export class DriveFileEntityService { return await awaitAll>({ id: file.id, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), name: file.name, type: file.type, md5: file.md5, diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts index 55014284bd..8fa78154b9 100644 --- a/packages/backend/src/core/entities/DriveFolderEntityService.ts +++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiDriveFolder } from '@/models/DriveFolder.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class DriveFolderEntityService { @@ -20,6 +21,8 @@ export class DriveFolderEntityService { @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private idService: IdService, ) { } @@ -38,7 +41,7 @@ export class DriveFolderEntityService { return await awaitAll({ id: folder.id, - createdAt: folder.createdAt.toISOString(), + createdAt: this.idService.parse(folder.id).date.toISOString(), name: folder.name, parentId: folder.parentId, diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 4701cddcba..dc335d9975 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiFlash } from '@/models/Flash.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -24,6 +25,7 @@ export class FlashEntityService { private flashLikesRepository: FlashLikesRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -37,7 +39,7 @@ export class FlashEntityService { return await awaitAll({ id: flash.id, - createdAt: flash.createdAt.toISOString(), + createdAt: this.idService.parse(flash.id).date.toISOString(), updatedAt: flash.updatedAt.toISOString(), userId: flash.userId, user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意 diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts index 9f6eb51e8c..52aa979677 100644 --- a/packages/backend/src/core/entities/FollowingEntityService.ts +++ b/packages/backend/src/core/entities/FollowingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiFollowing } from '@/models/Following.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; type LocalFollowerFollowing = MiFollowing & { @@ -45,6 +46,7 @@ export class FollowingEntityService { private followingsRepository: FollowingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -83,7 +85,7 @@ export class FollowingEntityService { return await awaitAll({ id: following.id, - createdAt: following.createdAt.toISOString(), + createdAt: this.idService.parse(following.id).date.toISOString(), followeeId: following.followeeId, followerId: following.followerId, followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, { diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index bbaf70f0fd..d7b960e0d9 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiGalleryPost } from '@/models/GalleryPost.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -26,6 +27,7 @@ export class GalleryPostEntityService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -39,7 +41,7 @@ export class GalleryPostEntityService { return await awaitAll({ id: post.id, - createdAt: post.createdAt.toISOString(), + createdAt: this.idService.parse(post.id).date.toISOString(), updatedAt: post.updatedAt.toISOString(), userId: post.userId, user: this.userEntityService.pack(post.user ?? post.userId, me), diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 0e27e9df7f..9afe87eab7 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -3,9 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/Blocking.js'; import type { MiInstance } from '@/models/Instance.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; @@ -43,6 +42,7 @@ export class InstanceEntityService { description: instance.description, maintainerName: instance.maintainerName, maintainerEmail: instance.maintainerEmail, + isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host), iconUrl: instance.iconUrl, faviconUrl: instance.faviconUrl, themeColor: instance.themeColor, diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts index 914eaafe68..0f15fb5ab2 100644 --- a/packages/backend/src/core/entities/InviteCodeEntityService.ts +++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts @@ -11,6 +11,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiUser } from '@/models/User.js'; import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -20,6 +21,7 @@ export class InviteCodeEntityService { private registrationTicketsRepository: RegistrationTicketsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -39,7 +41,7 @@ export class InviteCodeEntityService { id: target.id, code: target.code, expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null, - createdAt: target.createdAt.toISOString(), + createdAt: this.idService.parse(target.id).date.toISOString(), createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null, usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null, usedAt: target.usedAt ? target.usedAt.toISOString() : null, diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts index 83b024d83b..6729ca2671 100644 --- a/packages/backend/src/core/entities/ModerationLogEntityService.ts +++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts @@ -10,6 +10,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js'; import type { } from '@/models/Blocking.js'; import type { MiModerationLog } from '@/models/ModerationLog.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -19,6 +20,7 @@ export class ModerationLogEntityService { private moderationLogsRepository: ModerationLogsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -30,7 +32,7 @@ export class ModerationLogEntityService { return await awaitAll({ id: log.id, - createdAt: log.createdAt.toISOString(), + createdAt: this.idService.parse(log.id).date.toISOString(), type: log.type, info: log.info, userId: log.userId, diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts index e3d5d2e211..9d672169ba 100644 --- a/packages/backend/src/core/entities/MutingEntityService.ts +++ b/packages/backend/src/core/entities/MutingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiMuting } from '@/models/Muting.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -21,6 +22,7 @@ export class MutingEntityService { private mutingsRepository: MutingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -33,7 +35,7 @@ export class MutingEntityService { return await awaitAll({ id: muting.id, - createdAt: muting.createdAt.toISOString(), + createdAt: this.idService.parse(muting.id).date.toISOString(), expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null, muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index abe4aafd6e..7cb7e4b913 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -5,11 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; -import * as mfm from 'mfm-js'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; import type { Packed } from '@/misc/json-schema.js'; -import { nyaize } from '@/misc/nyaize.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; @@ -18,6 +16,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; import { DebounceLoader } from '@/misc/loader.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -30,6 +29,7 @@ export class NoteEntityService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; + private idService: IdService; private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( @@ -68,6 +68,7 @@ export class NoteEntityService implements OnModuleInit { this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.reactionService = this.moduleRef.get('ReactionService'); + this.idService = this.moduleRef.get('IdService'); } @bindThis @@ -169,11 +170,11 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: { + public async populateMyReaction(noteId: MiNote['id'], meId: MiUser['id'], _hint_?: { myReactions: Map; }) { if (_hint_?.myReactions) { - const reaction = _hint_.myReactions.get(note.id); + const reaction = _hint_.myReactions.get(noteId); if (reaction) { return this.reactionService.convertLegacyReaction(reaction.reaction); } else if (reaction === null) { @@ -182,14 +183,14 @@ export class NoteEntityService implements OnModuleInit { // 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない } - // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない - if (note.createdAt.getTime() + 1000 > Date.now()) { + // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない + if (this.idService.parse(noteId).date.getTime() + 2000 > Date.now()) { return undefined; } const reaction = await this.noteReactionsRepository.findOneBy({ userId: meId, - noteId: note.id, + noteId: noteId, }); if (reaction) { @@ -309,7 +310,7 @@ export class NoteEntityService implements OnModuleInit { const packed: Packed<'Note'> = await awaitAll({ id: note.id, - createdAt: note.createdAt.toISOString(), + createdAt: this.idService.parse(note.id).date.toISOString(), userId: note.userId, user: this.userEntityService.pack(note.user ?? note.userId, me, { detail: false, @@ -317,7 +318,7 @@ export class NoteEntityService implements OnModuleInit { text: text, cw: note.cw, visibility: note.visibility, - localOnly: note.localOnly ?? undefined, + localOnly: note.localOnly, reactionAcceptance: note.reactionAcceptance, visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, renoteCount: note.renoteCount, @@ -357,30 +358,11 @@ export class NoteEntityService implements OnModuleInit { poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, ...(meId ? { - myReaction: this.populateMyReaction(note, meId, options?._hint_), + myReaction: this.populateMyReaction(note.id, meId, options?._hint_), } : {}), } : {}), }); - if (packed.user.isCat && packed.text) { - const tokens = packed.text ? mfm.parse(packed.text) : []; - function nyaizeNode(node: mfm.MfmNode) { - if (node.type === 'quote') return; - if (node.type === 'text') { - node.props.text = nyaize(node.props.text); - } - if (node.children) { - for (const child of node.children) { - nyaizeNode(child); - } - } - } - for (const node of tokens) { - nyaizeNode(node); - } - packed.text = mfm.toString(tokens); - } - if (!opts.skipHide) { await this.hideNote(packed, meId); } @@ -403,8 +385,9 @@ export class NoteEntityService implements OnModuleInit { const myReactionsMap = new Map(); if (meId) { const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!); - // パフォーマンスのためノートが作成されてから1秒以上経っていない場合はリアクションを取得しない - const targets = [...notes.filter(n => n.createdAt.getTime() + 1000 < Date.now()).map(n => n.id), ...renoteIds]; + // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない + const oldId = this.idService.gen(Date.now() - 2000); + const targets = [...notes.filter(n => n.id < oldId).map(n => n.id), ...renoteIds]; const myReactions = await this.noteReactionsRepository.findBy({ userId: meId, noteId: In(targets), diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts index 808c8c9f69..1c9aed413f 100644 --- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts +++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts @@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiNoteFavorite } from '@/models/NoteFavorite.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { NoteEntityService } from './NoteEntityService.js'; @Injectable() @@ -19,6 +20,7 @@ export class NoteFavoriteEntityService { private noteFavoritesRepository: NoteFavoritesRepository, private noteEntityService: NoteEntityService, + private idService: IdService, ) { } @@ -31,7 +33,7 @@ export class NoteFavoriteEntityService { return { id: favorite.id, - createdAt: favorite.createdAt.toISOString(), + createdAt: this.idService.parse(favorite.id).date.toISOString(), noteId: favorite.noteId, note: await this.noteEntityService.pack(favorite.note ?? favorite.noteId, me), }; diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 9701f37fdb..f4aba3e543 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -8,6 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { NoteReactionsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; @@ -22,6 +23,7 @@ export class NoteReactionEntityService implements OnModuleInit { private userEntityService: UserEntityService; private noteEntityService: NoteEntityService; private reactionService: ReactionService; + private idService: IdService; constructor( private moduleRef: ModuleRef, @@ -32,6 +34,7 @@ export class NoteReactionEntityService implements OnModuleInit { //private userEntityService: UserEntityService, //private noteEntityService: NoteEntityService, //private reactionService: ReactionService, + //private idService: IdService, ) { } @@ -39,6 +42,7 @@ export class NoteReactionEntityService implements OnModuleInit { this.userEntityService = this.moduleRef.get('UserEntityService'); this.noteEntityService = this.moduleRef.get('NoteEntityService'); this.reactionService = this.moduleRef.get('ReactionService'); + this.idService = this.moduleRef.get('IdService'); } @bindThis @@ -57,7 +61,7 @@ export class NoteReactionEntityService implements OnModuleInit { return { id: reaction.id, - createdAt: reaction.createdAt.toISOString(), + createdAt: this.idService.parse(reaction.id).date.toISOString(), user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), ...(opts.withNote ? { diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index e3a1e19ddd..f39ef949db 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -13,6 +13,7 @@ import type { MiUser } from '@/models/User.js'; import type { MiPage } from '@/models/Page.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; import { DriveFileEntityService } from './DriveFileEntityService.js'; @@ -30,6 +31,7 @@ export class PageEntityService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -85,7 +87,7 @@ export class PageEntityService { return await awaitAll({ id: page.id, - createdAt: page.createdAt.toISOString(), + createdAt: this.idService.parse(page.id).date.toISOString(), updatedAt: page.updatedAt.toISOString(), userId: page.userId, user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意 diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts index 7111fab08a..3f9dc9180a 100644 --- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts +++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts @@ -12,6 +12,7 @@ import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -21,6 +22,7 @@ export class RenoteMutingEntityService { private renoteMutingsRepository: RenoteMutingsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -33,7 +35,7 @@ export class RenoteMutingEntityService { return await awaitAll({ id: muting.id, - createdAt: muting.createdAt.toISOString(), + createdAt: this.idService.parse(muting.id).date.toISOString(), muteeId: muting.muteeId, mutee: this.userEntityService.pack(muting.muteeId, me, { detail: true, diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 79375a7008..5563f9a1ac 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js'; import type { MiRole } from '@/models/Role.js'; import { bindThis } from '@/decorators.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class RoleEntityService { @@ -21,6 +22,8 @@ export class RoleEntityService { @Inject(DI.roleAssignmentsRepository) private roleAssignmentsRepository: RoleAssignmentsRepository, + + private idService: IdService, ) { } @@ -51,7 +54,7 @@ export class RoleEntityService { return await awaitAll({ id: role.id, - createdAt: role.createdAt.toISOString(), + createdAt: this.idService.parse(role.id).date.toISOString(), updatedAt: role.updatedAt.toISOString(), name: role.name, description: role.description, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ee67634da5..4a3ca00849 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { AnnouncementService } from '../AnnouncementService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js'; @@ -60,6 +61,7 @@ export class UserEntityService implements OnModuleInit { private announcementService: AnnouncementService; private roleService: RoleService; private federatedInstanceService: FederatedInstanceService; + private idService: IdService; constructor( private moduleRef: ModuleRef, @@ -111,13 +113,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, - - //private noteEntityService: NoteEntityService, - //private driveFileEntityService: DriveFileEntityService, - //private pageEntityService: PageEntityService, - //private customEmojiService: CustomEmojiService, - //private antennaService: AntennaService, - //private roleService: RoleService, ) { } @@ -130,6 +125,7 @@ export class UserEntityService implements OnModuleInit { this.announcementService = this.moduleRef.get('AnnouncementService'); this.roleService = this.moduleRef.get('RoleService'); this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); + this.idService = this.moduleRef.get('IdService'); } //#region Validators @@ -337,8 +333,8 @@ export class UserEntityService implements OnModuleInit { host: user.host, avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user), avatarBlurhash: user.avatarBlurhash, - isBot: user.isBot ?? falsy, - isCat: user.isCat ?? falsy, + isBot: user.isBot, + isCat: user.isCat, instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, @@ -364,14 +360,14 @@ export class UserEntityService implements OnModuleInit { ? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null))) .then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[]) : null, - createdAt: user.createdAt.toISOString(), + createdAt: this.idService.parse(user.id).date.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, bannerUrl: user.bannerUrl, bannerBlurhash: user.bannerBlurhash, isLocked: user.isLocked, isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), - isSuspended: user.isSuspended ?? falsy, + isSuspended: user.isSuspended, description: profile!.description, location: profile!.location, birthday: profile!.birthday, diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 06b6e852b1..31ab7293da 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -10,6 +10,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiUserList } from '@/models/UserList.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { UserEntityService } from './UserEntityService.js'; @Injectable() @@ -22,6 +23,7 @@ export class UserListEntityService { private userListMembershipsRepository: UserListMembershipsRepository, private userEntityService: UserEntityService, + private idService: IdService, ) { } @@ -37,7 +39,7 @@ export class UserListEntityService { return { id: userList.id, - createdAt: userList.createdAt.toISOString(), + createdAt: this.idService.parse(userList.id).date.toISOString(), name: userList.name, userIds: users.map(x => x.userId), isPublic: userList.isPublic, @@ -50,7 +52,7 @@ export class UserListEntityService { ) { return Promise.all(memberships.map(async x => ({ id: x.id, - createdAt: x.createdAt.toISOString(), + createdAt: this.idService.parse(x.id).date.toISOString(), userId: x.userId, user: await this.userEntityService.pack(x.userId), withReplies: x.withReplies, diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index d294628740..c5ef9b2fa3 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -108,6 +108,5 @@ async function net() { // FS STAT async function fs() { - const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); - return data ?? { rIO_sec: 0, wIO_sec: 0 }; + return await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 })); } diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index ec8aa849c9..e7b59f262b 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -24,8 +24,7 @@ function getNoise(): string { return counter.toString(36).padStart(2, '0').slice(-2); } -export function genAid(date: Date): string { - const t = date.getTime(); +export function genAid(t: number): string { if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date'); counter++; return getTime(t) + getNoise(); diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts index 5b031ea4c0..bed223225a 100644 --- a/packages/backend/src/misc/id/aidx.ts +++ b/packages/backend/src/misc/id/aidx.ts @@ -31,8 +31,7 @@ function getNoise(): string { return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH); } -export function genAidx(date: Date): string { - const t = date.getTime(); +export function genAidx(t: number): string { if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date'); counter++; return getTime(t) + nodeId + getNoise(); diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts index 82cda37237..366738de05 100644 --- a/packages/backend/src/misc/id/meid.ts +++ b/packages/backend/src/misc/id/meid.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genMeid(date: Date): string { - return getTime(date.getTime()) + getRandom(); +export function genMeid(t: number): string { + return getTime(t) + getRandom(); } export function parseMeid(id: string): { date: Date; } { diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts index fba7156718..426a46970b 100644 --- a/packages/backend/src/misc/id/meidg.ts +++ b/packages/backend/src/misc/id/meidg.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genMeidg(date: Date): string { - return 'g' + getTime(date.getTime()) + getRandom(); +export function genMeidg(t: number): string { + return 'g' + getTime(t) + getRandom(); } export function parseMeidg(id: string): { date: Date; } { diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts index e3b6e8e433..49bd9591c0 100644 --- a/packages/backend/src/misc/id/object-id.ts +++ b/packages/backend/src/misc/id/object-id.ts @@ -29,8 +29,8 @@ function getRandom() { return str; } -export function genObjectId(date: Date): string { - return getTime(date.getTime()) + getRandom(); +export function genObjectId(t: number): string { + return getTime(t) + getRandom(); } export function parseObjectId(id: string): { date: Date; } { diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 2551af7cb6..593c44f66b 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -12,12 +12,6 @@ export class MiAbuseUserReport { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the AbuseUserReport.', - }) - public createdAt: Date; - @Index() @Column(id()) public targetUserId: MiUser['id']; diff --git a/packages/backend/src/models/AccessToken.ts b/packages/backend/src/models/AccessToken.ts index 5a6269a729..452711eb8c 100644 --- a/packages/backend/src/models/AccessToken.ts +++ b/packages/backend/src/models/AccessToken.ts @@ -13,11 +13,6 @@ export class MiAccessToken { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AccessToken.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { nullable: true, }) diff --git a/packages/backend/src/models/Ad.ts b/packages/backend/src/models/Ad.ts index 6dfc9cb30e..b1d7d7d79e 100644 --- a/packages/backend/src/models/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -11,12 +11,6 @@ export class MiAd { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Ad.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The expired date of the Ad.', diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index 34b092a8d4..05d5a086f1 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -12,12 +12,6 @@ export class MiAnnouncement { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Announcement.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the Announcement.', nullable: true, diff --git a/packages/backend/src/models/AnnouncementRead.ts b/packages/backend/src/models/AnnouncementRead.ts index 3d6ec5652c..db09e65f50 100644 --- a/packages/backend/src/models/AnnouncementRead.ts +++ b/packages/backend/src/models/AnnouncementRead.ts @@ -14,11 +14,6 @@ export class MiAnnouncementRead { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AnnouncementRead.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index dc398b6dd2..b74c61b728 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -13,11 +13,6 @@ export class MiAntenna { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Antenna.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone') public lastUsedAt: Date; @@ -98,4 +93,9 @@ export class MiAntenna { default: true, }) public isActive: boolean; + + @Column('boolean', { + default: false, + }) + public localOnly: boolean; } diff --git a/packages/backend/src/models/App.ts b/packages/backend/src/models/App.ts index c599ef8be0..5c56a224a2 100644 --- a/packages/backend/src/models/App.ts +++ b/packages/backend/src/models/App.ts @@ -12,12 +12,6 @@ export class MiApp { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the App.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/AuthSession.ts b/packages/backend/src/models/AuthSession.ts index d9de6b6979..81bed21211 100644 --- a/packages/backend/src/models/AuthSession.ts +++ b/packages/backend/src/models/AuthSession.ts @@ -13,11 +13,6 @@ export class MiAuthSession { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the AuthSession.', - }) - public createdAt: Date; - @Index() @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/Blocking.ts b/packages/backend/src/models/Blocking.ts index 1e3dd3a644..9bf7a63b6e 100644 --- a/packages/backend/src/models/Blocking.ts +++ b/packages/backend/src/models/Blocking.ts @@ -13,12 +13,6 @@ export class MiBlocking { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Blocking.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Channel.ts b/packages/backend/src/models/Channel.ts index ae3886a657..f90f8c03d8 100644 --- a/packages/backend/src/models/Channel.ts +++ b/packages/backend/src/models/Channel.ts @@ -13,12 +13,6 @@ export class MiChannel { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Channel.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/ChannelFavorite.ts b/packages/backend/src/models/ChannelFavorite.ts index ab74aa5530..fc25ffe260 100644 --- a/packages/backend/src/models/ChannelFavorite.ts +++ b/packages/backend/src/models/ChannelFavorite.ts @@ -14,12 +14,6 @@ export class MiChannelFavorite { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ChannelFavorite.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/ChannelFollowing.ts b/packages/backend/src/models/ChannelFollowing.ts index c62a95332a..4dd391a082 100644 --- a/packages/backend/src/models/ChannelFollowing.ts +++ b/packages/backend/src/models/ChannelFollowing.ts @@ -14,12 +14,6 @@ export class MiChannelFollowing { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ChannelFollowing.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Clip.ts b/packages/backend/src/models/Clip.ts index c60b2964e0..2483b0925a 100644 --- a/packages/backend/src/models/Clip.ts +++ b/packages/backend/src/models/Clip.ts @@ -12,11 +12,6 @@ export class MiClip { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Clip.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/ClipFavorite.ts b/packages/backend/src/models/ClipFavorite.ts index 054764389b..aa949b3ea8 100644 --- a/packages/backend/src/models/ClipFavorite.ts +++ b/packages/backend/src/models/ClipFavorite.ts @@ -14,9 +14,6 @@ export class MiClipFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts index c12f0e0f02..24e6be9c90 100644 --- a/packages/backend/src/models/DriveFile.ts +++ b/packages/backend/src/models/DriveFile.ts @@ -14,12 +14,6 @@ export class MiDriveFile { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the DriveFile.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/DriveFolder.ts b/packages/backend/src/models/DriveFolder.ts index 3e049136bd..18f6d17709 100644 --- a/packages/backend/src/models/DriveFolder.ts +++ b/packages/backend/src/models/DriveFolder.ts @@ -12,12 +12,6 @@ export class MiDriveFolder { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the DriveFolder.', - }) - public createdAt: Date; - @Column('varchar', { length: 128, comment: 'The name of the DriveFolder.', diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts index 185063029d..ac880843b0 100644 --- a/packages/backend/src/models/Flash.ts +++ b/packages/backend/src/models/Flash.ts @@ -12,12 +12,6 @@ export class MiFlash { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Flash.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the Flash.', diff --git a/packages/backend/src/models/FlashLike.ts b/packages/backend/src/models/FlashLike.ts index 7c66010ae6..ad7f4966b4 100644 --- a/packages/backend/src/models/FlashLike.ts +++ b/packages/backend/src/models/FlashLike.ts @@ -14,9 +14,6 @@ export class MiFlashLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/FollowRequest.ts b/packages/backend/src/models/FollowRequest.ts index 769b9a6cb5..9899694dd6 100644 --- a/packages/backend/src/models/FollowRequest.ts +++ b/packages/backend/src/models/FollowRequest.ts @@ -13,11 +13,6 @@ export class MiFollowRequest { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the FollowRequest.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), @@ -50,6 +45,11 @@ export class MiFollowRequest { }) public requestId: string | null; + @Column('boolean', { + default: false, + }) + public withReplies: boolean; + //#region Denormalized fields @Column('varchar', { length: 128, nullable: true, diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts index 607538b1e7..e320911a1d 100644 --- a/packages/backend/src/models/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -14,12 +14,6 @@ export class MiFollowing { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Following.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/GalleryLike.ts b/packages/backend/src/models/GalleryLike.ts index b5f71764aa..84d4ce9c3e 100644 --- a/packages/backend/src/models/GalleryLike.ts +++ b/packages/backend/src/models/GalleryLike.ts @@ -14,9 +14,6 @@ export class MiGalleryLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/GalleryPost.ts b/packages/backend/src/models/GalleryPost.ts index 4c6063f32b..b72220caf9 100644 --- a/packages/backend/src/models/GalleryPost.ts +++ b/packages/backend/src/models/GalleryPost.ts @@ -13,12 +13,6 @@ export class MiGalleryPost { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the GalleryPost.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the GalleryPost.', diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index d2bd0c26e9..23ae513ede 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -76,6 +76,11 @@ export class MiMeta { }) public sensitiveWords: string[]; + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public silencedHosts: string[]; + @Column('varchar', { length: 1024, nullable: true, diff --git a/packages/backend/src/models/ModerationLog.ts b/packages/backend/src/models/ModerationLog.ts index a12b6ab614..71b33c3e47 100644 --- a/packages/backend/src/models/ModerationLog.ts +++ b/packages/backend/src/models/ModerationLog.ts @@ -12,11 +12,6 @@ export class MiModerationLog { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the ModerationLog.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/Muting.ts b/packages/backend/src/models/Muting.ts index 2f06ca8e5e..a528e1e7d7 100644 --- a/packages/backend/src/models/Muting.ts +++ b/packages/backend/src/models/Muting.ts @@ -13,12 +13,6 @@ export class MiMuting { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Muting.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 3e2adf4d82..ac7f57d5d6 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -18,11 +18,6 @@ export class MiNote { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Note.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/NoteFavorite.ts b/packages/backend/src/models/NoteFavorite.ts index 1171684bcf..364eaabd98 100644 --- a/packages/backend/src/models/NoteFavorite.ts +++ b/packages/backend/src/models/NoteFavorite.ts @@ -14,11 +14,6 @@ export class MiNoteFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the NoteFavorite.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts index 43323f8a43..ee3a447464 100644 --- a/packages/backend/src/models/NoteReaction.ts +++ b/packages/backend/src/models/NoteReaction.ts @@ -14,11 +14,6 @@ export class MiNoteReaction { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the NoteReaction.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/NoteThreadMuting.ts b/packages/backend/src/models/NoteThreadMuting.ts index 2d120e4c25..00311aa570 100644 --- a/packages/backend/src/models/NoteThreadMuting.ts +++ b/packages/backend/src/models/NoteThreadMuting.ts @@ -13,10 +13,6 @@ export class MiNoteThreadMuting { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 3cb986f4ee..9cab875499 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -14,12 +14,6 @@ export class MiPage { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Page.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { comment: 'The updated date of the Page.', diff --git a/packages/backend/src/models/PageLike.ts b/packages/backend/src/models/PageLike.ts index 92adf9bcc2..b845f58b7d 100644 --- a/packages/backend/src/models/PageLike.ts +++ b/packages/backend/src/models/PageLike.ts @@ -14,9 +14,6 @@ export class MiPageLike { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/PasswordResetRequest.ts b/packages/backend/src/models/PasswordResetRequest.ts index 79f2e984b8..5be439511f 100644 --- a/packages/backend/src/models/PasswordResetRequest.ts +++ b/packages/backend/src/models/PasswordResetRequest.ts @@ -12,9 +12,6 @@ export class MiPasswordResetRequest { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index({ unique: true }) @Column('varchar', { length: 256, diff --git a/packages/backend/src/models/PollVote.ts b/packages/backend/src/models/PollVote.ts index 37cd55fc18..751be8a32b 100644 --- a/packages/backend/src/models/PollVote.ts +++ b/packages/backend/src/models/PollVote.ts @@ -14,12 +14,6 @@ export class MiPollVote { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the PollVote.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/PromoRead.ts b/packages/backend/src/models/PromoRead.ts index 09ebfc8346..d9f3075416 100644 --- a/packages/backend/src/models/PromoRead.ts +++ b/packages/backend/src/models/PromoRead.ts @@ -14,11 +14,6 @@ export class MiPromoRead { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the PromoRead.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/RegistrationTicket.ts b/packages/backend/src/models/RegistrationTicket.ts index d94f465916..730cedffba 100644 --- a/packages/backend/src/models/RegistrationTicket.ts +++ b/packages/backend/src/models/RegistrationTicket.ts @@ -23,9 +23,6 @@ export class MiRegistrationTicket { }) public expiresAt: Date | null; - @Column('timestamp with time zone') - public createdAt: Date; - @ManyToOne(type => MiUser, { onDelete: 'CASCADE', }) diff --git a/packages/backend/src/models/RegistryItem.ts b/packages/backend/src/models/RegistryItem.ts index fdce57c467..60bdced957 100644 --- a/packages/backend/src/models/RegistryItem.ts +++ b/packages/backend/src/models/RegistryItem.ts @@ -13,11 +13,6 @@ export class MiRegistryItem { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the RegistryItem.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the RegistryItem.', }) diff --git a/packages/backend/src/models/RenoteMuting.ts b/packages/backend/src/models/RenoteMuting.ts index d2a36249dc..17df43ea31 100644 --- a/packages/backend/src/models/RenoteMuting.ts +++ b/packages/backend/src/models/RenoteMuting.ts @@ -13,12 +13,6 @@ export class MiRenoteMuting { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the Muting.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts index df7541db3d..6976956e13 100644 --- a/packages/backend/src/models/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -89,11 +89,6 @@ export class MiRole { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Role.', - }) - public createdAt: Date; - @Column('timestamp with time zone', { comment: 'The updated date of the Role.', }) diff --git a/packages/backend/src/models/RoleAssignment.ts b/packages/backend/src/models/RoleAssignment.ts index 4e5322c60b..30c7e19f2a 100644 --- a/packages/backend/src/models/RoleAssignment.ts +++ b/packages/backend/src/models/RoleAssignment.ts @@ -14,11 +14,6 @@ export class MiRoleAssignment { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the RoleAssignment.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/Signin.ts b/packages/backend/src/models/Signin.ts index a8b1a45c53..656b44dfe0 100644 --- a/packages/backend/src/models/Signin.ts +++ b/packages/backend/src/models/Signin.ts @@ -12,11 +12,6 @@ export class MiSignin { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Signin.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/SwSubscription.ts b/packages/backend/src/models/SwSubscription.ts index be1e4e3687..f685a8ff3e 100644 --- a/packages/backend/src/models/SwSubscription.ts +++ b/packages/backend/src/models/SwSubscription.ts @@ -12,9 +12,6 @@ export class MiSwSubscription { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 4d961c4290..796d7c8356 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -13,12 +13,6 @@ export class MiUser { @PrimaryColumn(id()) public id: string; - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the User.', - }) - public createdAt: Date; - @Index() @Column('timestamp with time zone', { nullable: true, diff --git a/packages/backend/src/models/UserList.ts b/packages/backend/src/models/UserList.ts index 9af85af97e..7ad15419d7 100644 --- a/packages/backend/src/models/UserList.ts +++ b/packages/backend/src/models/UserList.ts @@ -12,11 +12,6 @@ export class MiUserList { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserList.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/UserListFavorite.ts b/packages/backend/src/models/UserListFavorite.ts index d0b054b932..a18ed9253a 100644 --- a/packages/backend/src/models/UserListFavorite.ts +++ b/packages/backend/src/models/UserListFavorite.ts @@ -14,9 +14,6 @@ export class MiUserListFavorite { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListMembership.ts index f57f9ac33d..fa8287f17a 100644 --- a/packages/backend/src/models/UserListMembership.ts +++ b/packages/backend/src/models/UserListMembership.ts @@ -14,11 +14,6 @@ export class MiUserListMembership { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserListMembership.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/UserNotePining.ts b/packages/backend/src/models/UserNotePining.ts index 1d50a5068e..ae5977aa56 100644 --- a/packages/backend/src/models/UserNotePining.ts +++ b/packages/backend/src/models/UserNotePining.ts @@ -14,11 +14,6 @@ export class MiUserNotePining { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the UserNotePinings.', - }) - public createdAt: Date; - @Index() @Column(id()) public userId: MiUser['id']; diff --git a/packages/backend/src/models/UserPending.ts b/packages/backend/src/models/UserPending.ts index b15ededa14..8b1f8f617f 100644 --- a/packages/backend/src/models/UserPending.ts +++ b/packages/backend/src/models/UserPending.ts @@ -11,9 +11,6 @@ export class MiUserPending { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone') - public createdAt: Date; - @Index({ unique: true }) @Column('varchar', { length: 128, diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts index 5b009c18a6..ec4e13cc76 100644 --- a/packages/backend/src/models/Webhook.ts +++ b/packages/backend/src/models/Webhook.ts @@ -14,11 +14,6 @@ export class MiWebhook { @PrimaryColumn(id()) public id: string; - @Column('timestamp with time zone', { - comment: 'The created date of the Antenna.', - }) - public createdAt: Date; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts index 7b6475919c..4a9f0ed355 100644 --- a/packages/backend/src/models/json-schema/antenna.ts +++ b/packages/backend/src/models/json-schema/antenna.ts @@ -67,6 +67,11 @@ export const packedAntennaSchema = { optional: false, nullable: false, default: false, }, + localOnly: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, notify: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index ac07519f16..4ad84d02ff 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -93,6 +93,11 @@ export const packedFederationInstanceSchema = { type: 'string', optional: false, nullable: true, }, + isSilenced: { + type: "boolean", + optional: false, + nullable: false, + }, infoUpdatedAt: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts index 5aac3f19e8..9f49d85c7f 100644 --- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -47,13 +47,13 @@ export class AggregateRetentionProcessorService { // 今日登録したユーザーを全て取得 const targetUsers = await this.usersRepository.findBy({ host: IsNull(), - createdAt: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24))), + id: MoreThan(this.idService.gen(Date.now() - (1000 * 60 * 60 * 24))), }); const targetUserIds = targetUsers.map(u => u.id); try { await this.retentionAggregationsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), createdAt: now, updatedAt: now, dateKey, diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index a0afbee3ba..d0968d2923 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -80,6 +80,7 @@ export class ExportAntennasProcessorService { return this.utilityService.getFullApAccount(u.username, u.host); // acct }) : null, caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index 7248c7a649..af2a3434a9 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -15,6 +15,7 @@ import { createTemp } from '@/misc/create-temp.js'; import type { MiPoll } from '@/models/Poll.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -35,6 +36,7 @@ export class ExportFavoritesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites'); } @@ -99,7 +101,7 @@ export class ExportFavoritesProcessorService { if (favorite.note.hasPoll) { poll = await this.pollsRepository.findOneByOrFail({ noteId: favorite.note.id }); } - const content = JSON.stringify(serialize(favorite, poll)); + const content = JSON.stringify(this.serialize(favorite, poll)); const isFirst = exportedFavoritesCount === 0; await write(isFirst ? content : ',\n' + content); exportedFavoritesCount++; @@ -125,34 +127,34 @@ export class ExportFavoritesProcessorService { cleanup(); } } -} -function serialize(favorite: MiNoteFavorite & { note: MiNote & { user: MiUser } }, poll: MiPoll | null = null): Record { - return { - id: favorite.id, - createdAt: favorite.createdAt, - note: { - id: favorite.note.id, - text: favorite.note.text, - createdAt: favorite.note.createdAt, - fileIds: favorite.note.fileIds, - replyId: favorite.note.replyId, - renoteId: favorite.note.renoteId, - poll: poll, - cw: favorite.note.cw, - visibility: favorite.note.visibility, - visibleUserIds: favorite.note.visibleUserIds, - localOnly: favorite.note.localOnly, - reactionAcceptance: favorite.note.reactionAcceptance, - uri: favorite.note.uri, - url: favorite.note.url, - user: { - id: favorite.note.user.id, - name: favorite.note.user.name, - username: favorite.note.user.username, - host: favorite.note.user.host, - uri: favorite.note.user.uri, + private serialize(favorite: MiNoteFavorite & { note: MiNote & { user: MiUser } }, poll: MiPoll | null = null): Record { + return { + id: favorite.id, + createdAt: this.idService.parse(favorite.id).date.toISOString(), + note: { + id: favorite.note.id, + text: favorite.note.text, + createdAt: this.idService.parse(favorite.note.id).date.toISOString(), + fileIds: favorite.note.fileIds, + replyId: favorite.note.replyId, + renoteId: favorite.note.renoteId, + poll: poll, + cw: favorite.note.cw, + visibility: favorite.note.visibility, + visibleUserIds: favorite.note.visibleUserIds, + localOnly: favorite.note.localOnly, + reactionAcceptance: favorite.note.reactionAcceptance, + uri: favorite.note.uri, + url: favorite.note.url, + user: { + id: favorite.note.user.id, + name: favorite.note.user.name, + username: favorite.note.user.username, + host: favorite.note.user.host, + uri: favorite.note.user.uri, + }, }, - }, - }; + }; + } } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index e0bc80e190..cd4ccb0b07 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -17,6 +17,7 @@ import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; +import { IdService } from '@/core/IdService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -37,8 +38,8 @@ export class ExportNotesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, - private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } @@ -103,7 +104,7 @@ export class ExportNotesProcessorService { poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id }); } const files = await this.driveFileEntityService.packManyByIds(note.fileIds); - const content = JSON.stringify(serialize(note, poll, files)); + const content = JSON.stringify(this.serialize(note, poll, files)); const isFirst = exportedNotesCount === 0; await write(isFirst ? content : ',\n' + content); exportedNotesCount++; @@ -129,22 +130,22 @@ export class ExportNotesProcessorService { cleanup(); } } -} -function serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { - return { - id: note.id, - text: note.text, - createdAt: note.createdAt, - fileIds: note.fileIds, - files: files, - replyId: note.replyId, - renoteId: note.renoteId, - poll: poll, - cw: note.cw, - visibility: note.visibility, - visibleUserIds: note.visibleUserIds, - localOnly: note.localOnly, - reactionAcceptance: note.reactionAcceptance, - }; + private serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record { + return { + id: note.id, + text: note.text, + createdAt: this.idService.parse(note.id).date.toISOString(), + fileIds: note.fileIds, + files: files, + replyId: note.replyId, + renoteId: note.renoteId, + poll: poll, + cw: note.cw, + visibility: note.visibility, + visibleUserIds: note.visibleUserIds, + localOnly: note.localOnly, + reactionAcceptance: note.reactionAcceptance, + }; + } } diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 7c95bccaff..291ea14b67 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -43,6 +43,7 @@ const validate = new Ajv().compile({ type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -76,8 +77,7 @@ export class ImportAntennasProcessorService { continue; } const result = await this.antennasRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, userId: job.data.user.id, name: antenna.name, @@ -87,6 +87,7 @@ export class ImportAntennasProcessorService { excludeKeywords: antenna.excludeKeywords, users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean), caseSensitive: antenna.caseSensitive, + localOnly: antenna.localOnly, withReplies: antenna.withReplies, withFile: antenna.withFile, notify: antenna.notify, diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts index 2b5e41a12d..e75499a56f 100644 --- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts @@ -56,7 +56,7 @@ export class ImportFollowingProcessorService { const csv = await this.downloadService.downloadTextFile(file.url); const targets = csv.trim().split('\n'); - this.queueService.createImportFollowingToDbJob({ id: user.id }, targets); + this.queueService.createImportFollowingToDbJob({ id: user.id }, targets, job.data.withReplies); this.logger.succ('Import jobs created'); } @@ -93,9 +93,9 @@ export class ImportFollowingProcessorService { // skip myself if (target.id === job.data.user.id) return; - this.logger.info(`Follow ${target.id} ...`); + this.logger.info(`Follow ${target.id} ${job.data.withReplies ? 'with replies' : 'without replies'} ...`); - this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true }]); + this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]); } catch (e) { this.logger.warn(`Error: ${e}`); } diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 9be36a9d0d..5dd3fbe887 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -80,8 +80,7 @@ export class ImportUserListsProcessorService { if (list == null) { list = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, name: listName, }).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 99e823f9fa..89d4ea503e 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -88,7 +88,7 @@ export class InboxProcessorService { if (err.isClientError) { throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`); } - throw new Error(`Error in actor ${activity.actor} - ${err.statusCode ?? err}`); + throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`); } } } diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 5b2d2ef313..b2d8e3631f 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -34,8 +34,12 @@ export class RelationshipProcessorService { @bindThis public async processFollow(job: Bull.Job): Promise { - this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id}`); - await this.userFollowingService.follow(job.data.from, job.data.to, job.data.requestId, job.data.silent); + this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`); + await this.userFollowingService.follow(job.data.from, job.data.to, { + requestId: job.data.requestId, + silent: job.data.silent, + withReplies: job.data.withReplies, + }); return 'ok'; } diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index c9122f5ca2..9330c01528 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -32,6 +32,7 @@ export type RelationshipJobData = { to: ThinUser; silent?: boolean; requestId?: string; + withReplies?: boolean; } export type DbJobData = DbJobMap[T]; @@ -79,6 +80,7 @@ export type DbUserDeleteJobData = { export type DbUserImportJobData = { user: ThinUser; fileId: MiDriveFile['id']; + withReplies?: boolean; }; export type DBAntennaImportJobData = { @@ -89,6 +91,7 @@ export type DBAntennaImportJobData = { export type DbUserImportToDbJobData = { user: ThinUser; target: string; + withReplies?: boolean; }; export type ObjectStorageJobData = ObjectStorageFileJobData | Record; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 150f3f24d4..f3115690fe 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -128,8 +128,7 @@ export class SigninApiService { const fail = async (status?: number, failure?: { id: string }) => { // Append signin history await this.signinsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, ip: request.ip, headers: request.headers as any, diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index cebba8c8ee..98e9027006 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -30,8 +30,7 @@ export class SigninService { setImmediate(async () => { // Append signin history const record = await this.signinsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: user.id, ip: request.ip, headers: request.headers as any, diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 431df581b5..d2c4440116 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -164,8 +164,7 @@ export class SignupApiService { const hash = await bcrypt.hash(password, salt); const pendingUser = await this.userPendingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), code, email: emailAddress!, username: username, 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 e48dffecf4..17f792639b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -44,8 +44,7 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const ad = await this.adsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), dayOfWeek: ps.dayOfWeek, 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 262b36b9a4..253a29cf5a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -71,7 +71,6 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const { raw, packed } = await this.announcementService.create({ - createdAt: new Date(), updatedAt: null, title: ps.title, text: ps.text, 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 c82e702eef..fefc379c00 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -9,6 +9,7 @@ import type { MiAnnouncement } from '@/models/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -81,6 +82,7 @@ export default class extends Endpoint { // eslint- private announcementReadsRepository: AnnouncementReadsRepository, private queryService: QueryService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); @@ -102,7 +104,7 @@ export default class extends Endpoint { // eslint- return announcements.map(announcement => ({ id: announcement.id, - createdAt: announcement.createdAt.toISOString(), + createdAt: this.idService.parse(announcement.id).date.toISOString(), updatedAt: announcement.updatedAt?.toISOString() ?? null, title: announcement.title, text: announcement.text, 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 7fb5342f8d..4e5320007e 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 @@ -8,6 +8,7 @@ import type { DriveFilesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -163,6 +164,7 @@ export default class extends Endpoint { // eslint- private usersRepository: UsersRepository, private roleService: RoleService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({ @@ -212,7 +214,7 @@ export default class extends Endpoint { // eslint- type: file.type, name: file.name, md5: file.md5, - createdAt: file.createdAt.toISOString(), + createdAt: this.idService.parse(file.id).date.toISOString(), requestIp: iAmModerator ? file.requestIp : null, requestHeaders: iAmModerator && !ownerIsModerator ? file.requestHeaders : null, }; 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 c5f986ff02..a65e4e7624 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- } const copied = await this.emojisRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), updatedAt: new Date(), name: emoji.name, host: null, 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 cf94c998fa..6afa824703 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 @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserIpsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -28,11 +29,13 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const ips = await this.userIpsRepository.find({ where: { userId: ps.userId }, - order: { createdAt: 'DESC' }, + order: { id: 'DESC' }, take: 30, }); diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 2cc5ab6e35..4a22fd4824 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0]))); diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts index a20a51121a..f25d3fcb33 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -56,8 +56,8 @@ export default class extends Endpoint { // eslint- } switch (ps.sort) { - case '+createdAt': query.orderBy('ticket.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('ticket.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('ticket.id', 'DESC'); break; + case '-createdAt': query.orderBy('ticket.id', 'ASC'); break; case '+usedAt': query.orderBy('ticket.usedAt', 'DESC', 'NULLS LAST'); break; case '-usedAt': query.orderBy('ticket.usedAt', 'ASC', 'NULLS FIRST'); break; default: query.orderBy('ticket.id', 'DESC'); break; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5a74456ab0..f294934344 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -105,6 +105,16 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + silencedHosts: { + type: "array", + optional: true, + nullable: false, + items: { + type: "string", + optional: false, + nullable: false, + }, + }, pinnedUsers: { type: 'array', optional: false, nullable: false, @@ -367,6 +377,7 @@ export default class extends Endpoint { // eslint- pinnedUsers: instance.pinnedUsers, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, + silencedHosts: instance.silencedHosts, sensitiveWords: instance.sensitiveWords, preservedUsernames: instance.preservedUsernames, hcaptchaSecretKey: instance.hcaptchaSecretKey, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index ef5627bc9a..b7f9aa0495 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -10,6 +10,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -49,6 +50,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private userEntityService: UserEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ @@ -74,7 +76,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(assigns.map(async assign => ({ id: assign.id, - createdAt: assign.createdAt, + createdAt: this.idService.parse(assign.id).date.toISOString(), user: await this.userEntityService.pack(assign.user!, me, { detail: true }), expiresAt: assign.expiresAt, }))); 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 0731413d05..f550c4fd28 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -44,6 +45,7 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, private roleEntityService: RoleEntityService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const [user, profile] = await Promise.all([ @@ -92,7 +94,7 @@ export default class extends Endpoint { // eslint- policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), roleAssigns: roleAssigns.map(a => ({ - createdAt: a.createdAt.toISOString(), + createdAt: this.idService.parse(a.id).date.toISOString(), expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null, roleId: a.roleId, })), 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 e89e1a1490..fc810987d2 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -99,8 +99,8 @@ export default class extends Endpoint { // eslint- 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 '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '+updatedAt': query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST'); break; case '-updatedAt': query.orderBy('user.updatedAt', 'ASC', 'NULLS FIRST'); break; case '+lastActiveDate': query.orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST'); break; 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 7db25e659f..f05819b186 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -20,18 +20,26 @@ export const paramDef = { type: 'object', properties: { disableRegistration: { type: 'boolean', nullable: true }, - pinnedUsers: { type: 'array', nullable: true, items: { - type: 'string', - } }, - hiddenTags: { type: 'array', nullable: true, items: { - type: 'string', - } }, - blockedHosts: { type: 'array', nullable: true, items: { - type: 'string', - } }, - sensitiveWords: { type: 'array', nullable: true, items: { - type: 'string', - } }, + pinnedUsers: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + hiddenTags: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + blockedHosts: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + sensitiveWords: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, @@ -67,9 +75,11 @@ export const paramDef = { proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true }, maintainerName: { type: 'string', nullable: true }, maintainerEmail: { type: 'string', nullable: true }, - langs: { type: 'array', items: { - type: 'string', - } }, + langs: { + type: 'array', items: { + type: 'string', + }, + }, summalyProxy: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true }, deeplIsPro: { type: 'boolean' }, @@ -86,8 +96,8 @@ export const paramDef = { tosUrl: { type: 'string', nullable: true }, repositoryUrl: { type: 'string' }, feedbackUrl: { type: 'string' }, - impressumUrl: { type: 'string' }, - privacyPolicyUrl: { type: 'string' }, + impressumUrl: { type: 'string', nullable: true }, + privacyPolicyUrl: { type: 'string', nullable: true }, useObjectStorage: { type: 'boolean' }, objectStorageBaseUrl: { type: 'string', nullable: true }, objectStorageBucket: { type: 'string', nullable: true }, @@ -115,6 +125,13 @@ export const paramDef = { perUserHomeTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' }, notesPerOneAd: { type: 'integer' }, + silencedHosts: { + type: 'array', + nullable: true, + items: { + type: 'string', + }, + }, }, required: [], } as const; @@ -147,7 +164,14 @@ export default class extends Endpoint { // eslint- if (Array.isArray(ps.sensitiveWords)) { set.sensitiveWords = ps.sensitiveWords.filter(Boolean); } - + if (Array.isArray(ps.silencedHosts)) { + let lastValue = ''; + set.silencedHosts = ps.silencedHosts.sort().filter((h) => { + const lv = lastValue; + lastValue = h; + return h !== '' && h !== lv && !set.blockedHosts?.includes(h); + }); + } if (ps.themeColor !== undefined) { set.themeColor = ps.themeColor; } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 15fca4904d..b029493d3a 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -63,6 +63,7 @@ export const paramDef = { type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -112,8 +113,7 @@ export default class extends Endpoint { // eslint- const now = new Date(); const antenna = await this.antennasRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, userId: me.id, name: ps.name, @@ -123,6 +123,7 @@ export default class extends Endpoint { // eslint- excludeKeywords: ps.excludeKeywords, users: ps.users, caseSensitive: ps.caseSensitive, + localOnly: ps.localOnly, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 6d69971e30..9b5911800c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -12,7 +12,7 @@ import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -70,11 +70,11 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private queryService: QueryService, private noteReadService: NoteReadService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const antenna = await this.antennasRepository.findOneBy({ id: ps.antennaId, @@ -90,7 +90,7 @@ export default class extends Endpoint { // eslint- lastUsedAt: new Date(), }); - let noteIds = await this.redisTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { return []; diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 0e98746881..3457bb6f66 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -62,6 +62,7 @@ export const paramDef = { type: 'string', } }, caseSensitive: { type: 'boolean' }, + localOnly: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, notify: { type: 'boolean' }, @@ -116,6 +117,7 @@ export default class extends Endpoint { // eslint- excludeKeywords: ps.excludeKeywords, users: ps.users, caseSensitive: ps.caseSensitive, + localOnly: ps.localOnly, withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index cb00221506..f89d9823ba 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -55,8 +55,7 @@ export default class extends Endpoint { // eslint- // Create account const app = await this.appsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me ? me.id : null, name: ps.name, description: ps.description, diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 1b1893fd94..e0baeb3565 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- const now = new Date(); await this.accessTokensRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, appId: session.appId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 8b6a2c213d..6e474c59e0 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -79,8 +79,7 @@ export default class extends Endpoint { // eslint- // Create session token document const doc = await this.authSessionsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), appId: app.id, token: token, }).then(x => this.authSessionsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index e72120e156..3ba411d28c 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- } const channel = await this.channelsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, description: ps.description ?? null, diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts index 1f78a86dd4..c175718919 100644 --- a/packages/backend/src/server/api/endpoints/channels/favorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts @@ -57,8 +57,7 @@ export default class extends Endpoint { // eslint- } await this.channelFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, channelId: channel.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 5a43e8be1b..76ec6be805 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -57,8 +57,7 @@ export default class extends Endpoint { // eslint- } await this.channelFollowingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), followerId: me.id, followeeId: channel.id, }); diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 2dfcf659d7..fae4249c8a 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -12,7 +12,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { CacheService } from '@/core/CacheService.js'; import { ApiError } from '../../error.js'; @@ -69,13 +69,13 @@ export default class extends Endpoint { // eslint- private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, private cacheService: CacheService, private activeUsersChart: ActiveUsersChart, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const isRangeSpecified = untilId != null && sinceId != null; const channel = await this.channelsRepository.findOneBy({ @@ -95,7 +95,7 @@ export default class extends Endpoint { // eslint- this.cacheService.userMutingsCache.fetch(me.id), ]) : [new Set()]; - let noteIds = await this.redisTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length > 0) { diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index 6cd34f0a54..015b2cfa85 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -74,8 +74,7 @@ export default class extends Endpoint { // eslint- } await this.clipFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), clipId: clip.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 6f3a62977f..b7e9d12e94 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -69,8 +69,8 @@ export default class extends Endpoint { // eslint- } switch (ps.sort) { - case '+createdAt': query.orderBy('file.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('file.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('file.id', 'DESC'); break; + case '-createdAt': query.orderBy('file.id', 'ASC'); break; case '+name': query.orderBy('file.name', 'DESC'); break; case '-name': query.orderBy('file.name', 'ASC'); break; case '+size': query.orderBy('file.size', 'DESC'); break; 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 bc3a9bbe21..d18199f19b 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -76,8 +76,7 @@ export default class extends Endpoint { // eslint- // Create folder const folder = await this.driveFoldersRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), name: ps.name, parentId: parent !== null ? parent.id : null, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index be73e5dbb8..c8beefa9c7 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -36,6 +36,7 @@ export const paramDef = { blocked: { type: 'boolean', nullable: true }, notResponding: { type: 'boolean', nullable: true }, suspended: { type: 'boolean', nullable: true }, + silenced: { type: "boolean", nullable: true }, federating: { type: 'boolean', nullable: true }, subscribing: { type: 'boolean', nullable: true }, publishing: { type: 'boolean', nullable: true }, @@ -102,6 +103,23 @@ export default class extends Endpoint { // eslint- } } + if (typeof ps.silenced === "boolean") { + const meta = await this.metaService.fetch(true); + + if (ps.silenced) { + if (meta.silencedHosts.length === 0) { + return []; + } + query.andWhere("instance.host IN (:...silences)", { + silences: meta.silencedHosts, + }); + } else if (meta.silencedHosts.length > 0) { + query.andWhere("instance.host NOT IN (:...silences)", { + silences: meta.silencedHosts, + }); + } + } + if (typeof ps.federating === 'boolean') { if (ps.federating) { query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))'); diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index b46660d218..4fa65ac9aa 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -53,9 +53,8 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const flash = await this.flashsRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), userId: me.id, - createdAt: new Date(), updatedAt: new Date(), title: ps.title, summary: ps.summary, diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index a90e5f653a..1003249c0c 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.flashLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), flashId: flash.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index e0e7fed87a..9037944ef9 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -71,6 +71,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, + withReplies: { type: 'boolean' } }, required: ['userId'], } as const; @@ -112,7 +113,7 @@ export default class extends Endpoint { // eslint- } try { - await this.userFollowingService.follow(follower, followee); + await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies }); } catch (e) { if (e instanceof IdentifiableError) { if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); 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 94701712de..71e0ad4141 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -76,8 +76,7 @@ export default class extends Endpoint { // eslint- } const post = await this.galleryPostsRepository.insert(new MiGalleryPost({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), title: ps.title, description: ps.description, 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 c557054066..561b2492ab 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.galleryLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), postId: post.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 1cef76d3d2..50aea79943 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -66,8 +66,8 @@ export default class extends Endpoint { // eslint- 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 '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; } diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index daa3e536a4..09f6540a77 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { requireCredential: true, @@ -27,6 +28,8 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const query = this.accessTokensRepository.createQueryBuilder('token') @@ -34,8 +37,8 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('token.app', 'app'); switch (ps.sort) { - case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; - case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; + case '+createdAt': query.orderBy('token.id', 'DESC'); break; + case '-createdAt': query.orderBy('token.id', '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; @@ -46,7 +49,7 @@ export default class extends Endpoint { // eslint- return await Promise.all(tokens.map(token => ({ id: token.id, name: token.name ?? token.app?.name, - createdAt: token.createdAt, + createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt, permission: token.permission, }))); 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 38c9283043..e5fa2ac96a 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -52,6 +52,7 @@ export const paramDef = { type: 'object', properties: { fileId: { type: 'string', format: 'misskey:id' }, + withReplies: { type: 'boolean' }, }, required: ['fileId'], } as const; @@ -79,7 +80,7 @@ export default class extends Endpoint { // eslint- ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); - this.queueService.createImportFollowingJob(me, file.id); + this.queueService.createImportFollowingJob(me, file.id, ps.withReplies); }); } } 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 c074b152df..6203e7aa8b 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -53,8 +53,7 @@ export default class extends Endpoint { // eslint- }); } else { await this.registryItemsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), userId: me.id, domain: null, 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 48eaeff406..f00dba4a85 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -63,8 +63,7 @@ export default class extends Endpoint { // eslint- } const webhook = await this.webhooksRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, url: ps.url, diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts index 7361ab616c..94836283fa 100644 --- a/packages/backend/src/server/api/endpoints/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/invite/create.ts @@ -62,7 +62,7 @@ export default class extends Endpoint { // eslint- if (policies.inviteLimit) { const count = await this.registrationTicketsRepository.countBy({ - createdAt: MoreThan(new Date(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), + id: MoreThan(this.idService.gen(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), createdById: me.id, }); @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- } const ticket = await this.registrationTicketsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), createdBy: me, createdById: me.id, expiresAt: policies.inviteExpirationTime ? new Date(Date.now() + (policies.inviteExpirationTime * 1000 * 60)) : null, diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts index 43b94e4f06..1f4190c948 100644 --- a/packages/backend/src/server/api/endpoints/invite/limit.ts +++ b/packages/backend/src/server/api/endpoints/invite/limit.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/_.js'; import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['meta'], @@ -41,12 +42,13 @@ export default class extends Endpoint { // eslint- private registrationTicketsRepository: RegistrationTicketsRepository, private roleService: RoleService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me.id); const count = policies.inviteLimit ? await this.registrationTicketsRepository.countBy({ - createdAt: MoreThan(new Date(Date.now() - (policies.inviteExpirationTime * 60 * 1000))), + id: MoreThan(this.idService.gen(Date.now() - (policies.inviteExpirationTime * 60 * 1000))), createdById: me.id, }) : 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 e40656cb6d..cac8f41f8e 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -59,8 +59,7 @@ export default class extends Endpoint { // eslint- // Insert access token doc await this.accessTokensRepository.insert({ - id: this.idService.genId(), - createdAt: now, + id: this.idService.gen(now.getTime()), lastUsedAt: now, session: ps.session, userId: me.id, 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 cc648e22a8..ed3dce7f35 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -80,8 +80,7 @@ export default class extends Endpoint { // eslint- // Create favorite await this.noteFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: me.id, }); 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 8ac5f1b038..9986c6e436 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -5,7 +5,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -15,7 +14,9 @@ import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { CacheService } from '@/core/CacheService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { QueryService } from '@/core/QueryService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -63,9 +64,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -74,11 +72,13 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private queryService: QueryService, + private userFollowingService: UserFollowingService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const policies = await this.roleService.getUserPolicies(me.id); if (!policies.ltlAvailable) { @@ -96,71 +96,148 @@ export default class extends Endpoint { // eslint- ]); let noteIds: string[]; + let shouldFallbackToDb = false; if (ps.withFiles) { - const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimelineWithFiles:${me.id}`, 'localTimelineWithFiles', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); } else if (ps.withReplies) { - const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimeline:${me.id}`, 'localTimeline', 'localTimelineWithReplies', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds])); } else { - const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([ + const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([ `homeTimeline:${me.id}`, 'localTimeline', ], untilId, sinceId); noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds])); + shouldFallbackToDb = htlNoteIds.length === 0; } noteIds.sort((a, b) => a > b ? -1 : 1); noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (!shouldFallbackToDb) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (note.userId === me.id) { - return true; - } - if (isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (note.userId === me.id) { + return true; } + if (isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const followees = await this.userFollowingService.getFollowees(me.id); + + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { + if (followees.length > 0) { + const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; + qb.where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); + qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + } else { + qb.where('note.userId = :meId', { meId: me.id }); + qb.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + } + })) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.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'); + })); + })); + + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); } - return true; - }); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - // TODO: フィルタした結果件数が足りなかった場合の対応 + 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)'); + })); + } - timeline.sort((a, b) => a.id > b.id ? -1 : 1); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion - process.nextTick(() => { - this.activeUsersChart.read(me); - }); + const timeline = await query.limit(ps.limit).getMany(); - return await this.noteEntityService.packMany(timeline, me); + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index d10c3bedbf..9d5688f96f 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -5,7 +5,6 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import type { MiNote, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -15,7 +14,8 @@ import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { QueryService } from '@/core/QueryService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -59,9 +59,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -70,11 +67,12 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const policies = await this.roleService.getUserPolicies(me ? me.id : null); if (!policies.ltlAvailable) { @@ -94,9 +92,9 @@ export default class extends Endpoint { // eslint- let noteIds: string[]; if (ps.withFiles) { - noteIds = await this.redisTimelineService.get('localTimelineWithFiles', untilId, sinceId); + noteIds = await this.funoutTimelineService.get('localTimelineWithFiles', untilId, sinceId); } else { - const [nonReplyNoteIds, replyNoteIds] = await this.redisTimelineService.getMulti([ + const [nonReplyNoteIds, replyNoteIds] = await this.funoutTimelineService.getMulti([ 'localTimeline', 'localTimelineWithReplies', ], untilId, sinceId); @@ -106,49 +104,75 @@ export default class extends Endpoint { // eslint- noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (noteIds.length > 0) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (me && (note.userId === me.id)) { - return true; - } - if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false; - if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (me && (note.userId === me.id)) { + return true; } + if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false; + if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (me && isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me); + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } - return true; - }); + const timeline = await query.limit(ps.limit).getMany(); - // TODO: フィルタした結果件数が足りなかった場合の対応 + process.nextTick(() => { + if (me) { + this.activeUsersChart.read(me); + } + }); - timeline.sort((a, b) => a.id > b.id ? -1 : 1); - - process.nextTick(() => { - if (me) { - this.activeUsersChart.read(me); - } - }); - - return await this.noteEntityService.packMany(timeline, me); + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 986201e950..af7ff8bdcd 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -98,7 +98,7 @@ export default class extends Endpoint { // eslint- id: In(polls.map(poll => poll.noteId)), }, order: { - createdAt: 'DESC', + id: '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 a58bf09b85..734c3f0e63 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -145,8 +145,7 @@ export default class extends Endpoint { // eslint- // Create vote const vote = await this.pollVotesRepository.insert({ - id: this.idService.genId(), - createdAt, + id: this.idService.gen(createdAt.getTime()), noteId: note.id, userId: me.id, choice: ps.choice, 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 449a838604..b2cdaa00ac 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 @@ -72,8 +72,7 @@ export default class extends Endpoint { // eslint- await this.noteReadService.read(me.id, mutedNotes); await this.noteThreadMutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), threadId: note.threadId ?? note.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 760d52c9db..3b597107ae 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -5,8 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; -import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js'; +import type { NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -15,7 +14,8 @@ import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; export const meta = { tags: ['notes'], @@ -53,9 +53,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -63,11 +60,13 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private idService: IdService, private cacheService: CacheService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, + private userFollowingService: UserFollowingService, + private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const [ followings, @@ -81,52 +80,127 @@ export default class extends Endpoint { // eslint- this.cacheService.userBlockedCache.fetch(me.id), ]); - let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); - if (noteIds.length === 0) { - return []; - } + if (noteIds.length > 0) { + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel'); - const query = this.notesRepository.createQueryBuilder('note') - .where('note.id IN (:...noteIds)', { noteIds: noteIds }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('note.channel', 'channel'); + let timeline = await query.getMany(); - let timeline = await query.getMany(); - - timeline = timeline.filter(note => { - if (note.userId === me.id) { - return true; - } - if (isUserRelated(note, userIdsWhoBlockingMe)) return false; - if (isUserRelated(note, userIdsWhoMeMuting)) return false; - if (note.renoteId) { - if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { - if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; - if (ps.withRenotes === false) return false; + timeline = timeline.filter(note => { + if (note.userId === me.id) { + return true; } - } - if (note.reply && note.reply.visibility === 'followers') { - if (!Object.hasOwn(followings, note.reply.userId)) return false; + if (isUserRelated(note, userIdsWhoBlockingMe)) return false; + if (isUserRelated(note, userIdsWhoMeMuting)) return false; + if (note.renoteId) { + if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) { + if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false; + if (ps.withRenotes === false) return false; + } + } + if (note.reply && note.reply.visibility === 'followers') { + if (!Object.hasOwn(followings, note.reply.userId)) return false; + } + + return true; + }); + + // TODO: フィルタした結果件数が足りなかった場合の対応 + + timeline.sort((a, b) => a.id > b.id ? -1 : 1); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } else { // fallback to db + const followees = await this.userFollowingService.getFollowees(me.id); + + //#region Construct query + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.channelId IS NULL') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + if (followees.length > 0) { + const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)]; + + query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }); + } else { + query.andWhere('note.userId = :meId', { meId: me.id }); } - return true; - }); + query.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'); + })); + })); - // TODO: フィルタした結果件数が足りなかった場合の対応 + this.queryService.generateVisibilityQuery(query, me); + this.queryService.generateMutedUserQuery(query, me); + this.queryService.generateBlockedUserQuery(query, me); + this.queryService.generateMutedUserRenotesQueryForNotes(query, me); - timeline.sort((a, b) => a.id > b.id ? -1 : 1); + if (ps.includeMyRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.userId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - process.nextTick(() => { - this.activeUsersChart.read(me); - }); + if (ps.includeRenotedMyNotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserId != :meId', { meId: me.id }); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } - return await this.noteEntityService.packMany(timeline, me); + if (ps.includeLocalRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteUserHost IS NOT NULL'); + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } + //#endregion + + const timeline = await query.limit(ps.limit).getMany(); + + process.nextTick(() => { + this.activeUsersChart.read(me); + }); + + return await this.noteEntityService.packMany(timeline, me); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index f7ee58264e..2b31e6169c 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 @@ -15,7 +15,7 @@ import { DI } from '@/di-symbols.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -80,11 +80,11 @@ export default class extends Endpoint { // eslint- private activeUsersChart: ActiveUsersChart, private cacheService: CacheService, private idService: IdService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const list = await this.userListsRepository.findOneBy({ id: ps.listId, @@ -105,7 +105,7 @@ export default class extends Endpoint { // eslint- this.cacheService.userBlockedCache.fetch(me.id), ]); - let noteIds = await this.redisTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index c0e8fab16c..4c2ef516e5 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -103,8 +103,7 @@ export default class extends Endpoint { // eslint- }); const page = await this.pagesRepository.insert(new MiPage({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), updatedAt: new Date(), title: ps.title, name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 6c69cad9d5..8c18982b50 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -83,8 +83,7 @@ export default class extends Endpoint { // eslint- // Create like await this.pageLikesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), pageId: page.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index b197756acc..7d07c92178 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -60,8 +60,7 @@ export default class extends Endpoint { // eslint- } await this.promoReadsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), noteId: note.id, userId: me.id, }); diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 3c9d266e21..7ff7b5de3a 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -90,8 +90,7 @@ export default class extends Endpoint { // eslint- // Create mute await this.renoteMutingsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), muterId: muter.id, muteeId: mutee.id, } as MiRenoteMuting); 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 adb160c58b..f13710e1dd 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -84,8 +84,7 @@ export default class extends Endpoint { // eslint- const token = secureRndstr(64, { chars: L_CHARS }); await this.passwordResetRequestsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: profile.userId, token, }); diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 1858c922a0..3a6dc14bcd 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['reset password'], @@ -38,6 +39,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { const req = await this.passwordResetRequestsRepository.findOneByOrFail({ @@ -45,7 +48,7 @@ export default class extends Endpoint { // eslint- }); // 発行してから30分以上経過していたら無効 - if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { + if (Date.now() - this.idService.parse(req.id).date.getTime() > 1000 * 60 * 30) { throw new Error(); // TODO } diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 0db51abc55..daa9affc20 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -11,7 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -66,11 +66,11 @@ export default class extends Endpoint { // eslint- private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const role = await this.rolesRepository.findOneBy({ id: ps.roleId, @@ -84,7 +84,7 @@ export default class extends Endpoint { // eslint- return []; } - let noteIds = await this.redisTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId); + let noteIds = await this.funoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId); noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 5cfbeab73f..9ab062326d 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -88,8 +88,7 @@ export default class extends Endpoint { // eslint- } await this.swSubscriptionsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, endpoint: ps.endpoint, auth: ps.auth, diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 21c585f1ad..8dc5841314 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -74,8 +74,8 @@ export default class extends Endpoint { // eslint- 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 '+createdAt': query.orderBy('user.id', 'DESC'); break; + case '-createdAt': query.orderBy('user.id', '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; diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index f2f6c4303a..4eb37c3e43 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -104,8 +104,7 @@ export default class extends Endpoint { // eslint- } const userList = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); 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 60b2b3f17e..e86e4c0ded 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -66,8 +66,7 @@ export default class extends Endpoint { // eslint- } const userList = await this.userListsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index 1707afee60..2ecf0a1256 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -69,8 +69,7 @@ export default class extends Endpoint { } await this.userListFavoritesRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), userId: me.id, userListId: ps.listId, }); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index df0951ce74..343d320f6e 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -14,7 +14,7 @@ import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { QueryService } from '@/core/QueryService.js'; -import { RedisTimelineService } from '@/core/RedisTimelineService.js'; +import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -71,11 +71,11 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private cacheService: CacheService, private idService: IdService, - private redisTimelineService: RedisTimelineService, + private funoutTimelineService: FunoutTimelineService, ) { super(meta, paramDef, async (ps, me) => { - const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null); - const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null); + const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); + const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const isRangeSpecified = untilId != null && sinceId != null; const isSelf = me && (me.id === ps.userId); @@ -87,9 +87,9 @@ export default class extends Endpoint { // eslint- ]) : [new Set()]; const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([ - this.redisTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId), - ps.withReplies ? this.redisTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), - ps.withChannelNotes ? this.redisTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), + this.funoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId), + ps.withReplies ? this.funoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), + ps.withChannelNotes ? this.funoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]), ]); let noteIds = Array.from(new Set([ @@ -151,7 +151,10 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.withChannelNotes) { - if (!isSelf) query.andWhere('channel.isSensitive = false'); + if (!isSelf) query.andWhere(new Brackets(qb => { + qb.orWhere('note.channelId IS NULL'); + qb.orWhere('channel.isSensitive = false'); + })); } else { query.andWhere('note.channelId IS NULL'); } 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 50aa6fa09e..3bcf44cc42 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -82,8 +82,7 @@ export default class extends Endpoint { // eslint- } const report = await this.abuseUserReportsRepository.insert({ - id: this.idService.genId(), - createdAt: new Date(), + id: this.idService.gen(), targetUserId: user.id, targetUserHost: user.host, reporterId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts index 194d488052..b3f67815ef 100644 --- a/packages/backend/src/server/api/endpoints/users/update-memo.ts +++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts @@ -72,7 +72,7 @@ export default class extends Endpoint { // eslint- if (!previousMemo) { await this.userMemosRepository.insert({ - id: this.idService.genId(), + id: this.idService.gen(), userId: me.id, targetUserId: target.id, memo: ps.memo, diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index a01714e76d..e4c34e00ce 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -38,19 +38,6 @@ class ChannelChannel extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する @@ -58,6 +45,11 @@ class ChannelChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); 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 03f2dff62b..c499d1787e 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -52,19 +52,6 @@ class GlobalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && !this.following[note.userId]?.withReplies) { const reply = note.reply; @@ -84,6 +71,11 @@ class GlobalTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 3945b1a1eb..2cfe9572d3 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -43,13 +43,6 @@ class HashtagChannel extends Channel { const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); if (!matched) return; - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する @@ -57,6 +50,11 @@ class HashtagChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); 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 24be590504..3ccf4af66d 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -49,29 +49,12 @@ class HomeTimelineChannel extends Channel { } // 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 (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) return; } // 関係ない返信は除外 @@ -90,6 +73,11 @@ class HomeTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); 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 adedca5152..760fab60a4 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -62,31 +62,14 @@ class HybridTimelineChannel extends Channel { (note.channelId != null && this.followingChannels.has(note.channelId)) )) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) 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.following[note.userId]?.withReplies && !this.withReplies) { @@ -104,6 +87,11 @@ class HybridTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); 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 69aa366f00..a211041134 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -54,19 +54,6 @@ class LocalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null && !this.followingChannels.has(note.channelId)) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) { const reply = note.reply; @@ -83,6 +70,11 @@ class LocalTimelineChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + this.connection.cacheNote(note); this.send('note', note); 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 240822d9ab..b73cedaa8b 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -82,27 +82,10 @@ class UserListChannel extends Channel { if (!Object.hasOwn(this.membershipsMap, note.userId)) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await this.noteEntityService.pack(note.id, this.user, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await this.noteEntityService.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await this.noteEntityService.pack(note.renoteId, this.user, { - detail: true, - }); - } + if (note.visibility === 'followers') { + if (!Object.hasOwn(this.following, note.userId)) return; + } else if (note.visibility === 'specified') { + if (!note.visibleUserIds!.includes(this.user!.id)) return; } // 関係ない返信は除外 @@ -119,6 +102,13 @@ class UserListChannel extends Channel { if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.user && note.renoteId && !note.text) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id); + note.renote!.myReaction = myRenoteReaction; + } + + this.connection.cacheNote(note); + this.send('note', note); } diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index c3a78561c2..4fa7b800e8 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -325,8 +325,7 @@ export class OAuth2ProviderService { // NOTE: we don't have a setup for automatic token expiration await accessTokensRepository.insert({ - id: idService.genId(), - createdAt: now, + id: idService.gen(now.getTime()), lastUsedAt: now, userId: granted.userId, token: accessToken, diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 78551e800b..3ba26ad34a 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -13,6 +13,7 @@ import type { MiUser } from '@/models/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; @Injectable() export class FeedService { @@ -31,6 +32,7 @@ export class FeedService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, + private idService: IdService, ) { } @@ -49,14 +51,14 @@ export class FeedService { renoteId: IsNull(), visibility: In(['public', 'home']), }, - order: { createdAt: -1 }, + order: { id: -1 }, take: 20, }); const feed = new Feed({ id: author.link, title: `${author.name} (@${user.username}@${this.config.host})`, - updated: notes[0].createdAt, + updated: this.idService.parse(notes[0].id).date, generator: 'Misskey', description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, @@ -78,10 +80,10 @@ export class FeedService { feed.addItem({ title: `New note by ${author.name}`, link: `${this.config.url}/notes/${note.id}`, - date: note.createdAt, + date: this.idService.parse(note.id).date, description: note.cw ?? undefined, content: note.text ?? undefined, - image: file ? this.driveFileEntityService.getPublicUrl(file) ?? undefined : undefined, + image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined, }); } diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 7e2beab1ab..c0317f1435 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -174,6 +174,7 @@ describe('アンテナ', () => { users: [''], withFile: false, withReplies: false, + localOnly: false, } as Antenna; assert.deepStrictEqual(response, expected); }); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index 3f158f9f13..b009ef124a 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -188,6 +188,7 @@ describe('Account Move', () => { excludeKeywords: [], users: [], caseSensitive: false, + localOnly: false, withReplies: false, withFile: false, notify: false, @@ -431,6 +432,7 @@ describe('Account Move', () => { excludeKeywords: [], users: [eve.id], caseSensitive: false, + localOnly: false, withReplies: false, withFile: false, notify: false, diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 77de144882..5a83bbb7da 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -18,7 +18,6 @@ describe('Streaming', () => { const follow = async (follower: any, followee: any) => { await Followings.save({ id: 'a', - createdAt: new Date(), followerId: follower.id, followeeId: followee.id, followerHost: follower.host, diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index f753b54c6d..28f07bf3f7 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -727,7 +727,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { }, alice); + const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index 8f61d91ba9..f2aa5d35e4 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -35,8 +35,7 @@ describe('AnnouncementService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), username: un, usernameLower: un, ...data, @@ -44,10 +43,9 @@ describe('AnnouncementService', () => { .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); } - function createAnnouncement(data: Partial = {}) { + function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(data.createdAt ?? new Date()), updatedAt: null, title: 'Title', text: 'Text', diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index c6a14702ae..f644312bc9 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -37,8 +37,7 @@ describe('RoleService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), username: un, usernameLower: un, ...data, @@ -48,8 +47,7 @@ describe('RoleService', () => { function createRole(data: Partial = {}) { return rolesRepository.insert({ - id: genAidx(new Date()), - createdAt: new Date(), + id: genAidx(Date.now()), updatedAt: new Date(), lastUsedAt: new Date(), description: '', @@ -198,10 +196,10 @@ describe('RoleService', () => { test('conditional role', async () => { const user1 = await createUser({ - createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)), + id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), }); const user2 = await createUser({ - createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)), + id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), followersCount: 10, }); await createRole({ diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 2e9454927c..c418e8413d 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -23,6 +23,7 @@ import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; import { MetaService } from '@/core/MetaService.js'; import type { MiRemoteUser } from '@/models/User.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { MockResolver } from '../misc/mock-resolver.js'; const host = 'https://host1.test'; @@ -200,7 +201,7 @@ describe('ActivityPub', () => { describe('Renderer', () => { test('Render an announce with visibility: followers', () => { rendererService.renderAnnounce(null, { - createdAt: new Date(0), + id: genAidx(Date.now()), visibility: 'followers', } as MiNote); }); diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts index 57b4ea9947..59783a9fa1 100644 --- a/packages/backend/test/unit/misc/id.ts +++ b/packages/backend/test/unit/misc/id.ts @@ -14,44 +14,44 @@ import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js'; describe('misc:id', () => { test('aid', () => { - const date = new Date(); + const date = Date.now(); const gotAid = genAid(date); expect(gotAid).toMatch(aidRegExp); - expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); + expect(parseAid(gotAid).date.getTime()).toBe(date); }); test('aidx', () => { - const date = new Date(); + const date = Date.now(); const gotAidx = genAidx(date); expect(gotAidx).toMatch(aidxRegExp); - expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime()); + expect(parseAidx(gotAidx).date.getTime()).toBe(date); }); test('meid', () => { - const date = new Date(); + const date = Date.now(); const gotMeid = genMeid(date); expect(gotMeid).toMatch(meidRegExp); - expect(parseMeid(gotMeid).date.getTime()).toBe(date.getTime()); + expect(parseMeid(gotMeid).date.getTime()).toBe(date); }); test('meidg', () => { - const date = new Date(); + const date = Date.now(); const gotMeidg = genMeidg(date); expect(gotMeidg).toMatch(meidgRegExp); - expect(parseMeidg(gotMeidg).date.getTime()).toBe(date.getTime()); + expect(parseMeidg(gotMeidg).date.getTime()).toBe(date); }); test('objectid', () => { - const date = new Date(); + const date = Date.now(); const gotObjectId = genObjectId(date); expect(gotObjectId).toMatch(objectIdRegExp); - expect(Math.floor(parseObjectId(gotObjectId).date.getTime() / 1000)).toBe(Math.floor(date.getTime() / 1000)); + expect(Math.floor(parseObjectId(gotObjectId).date.getTime() / 1000)).toBe(Math.floor(date / 1000)); }); test('ulid', () => { - const date = new Date(); - const gotUlid = ulid(date.getTime()); + const date = Date.now(); + const gotUlid = ulid(date); expect(gotUlid).toMatch(ulidRegExp); - expect(parseUlid(gotUlid).date.getTime()).toBe(date.getTime()); + expect(parseUlid(gotUlid).date.getTime()).toBe(date); }); }); diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8b7604f311..feadbbb041 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,7 @@ "@github/webauthn-json": "2.1.1", "@rollup/plugin-alias": "5.0.1", "@rollup/plugin-json": "6.0.1", - "@rollup/plugin-replace": "5.0.3", + "@rollup/plugin-replace": "5.0.4", "@rollup/pluginutils": "5.0.5", "@syuilo/aiscript": "0.16.0", "@tabler/icons-webfont": "2.37.0", @@ -29,7 +29,7 @@ "@vue/compiler-sfc": "3.3.4", "astring": "1.8.6", "autosize": "6.0.1", - "broadcast-channel": "5.3.0", + "broadcast-channel": "5.4.0", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "buraha": "0.0.1", "canvas-confetti": "1.6.1", @@ -38,7 +38,7 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "7.2.3", + "chromatic": "7.4.0", "compare-versions": "6.1.0", "cropperjs": "2.0.0-beta.4", "date-fns": "2.30.0", @@ -57,9 +57,9 @@ "prismjs": "1.29.0", "punycode": "2.3.0", "querystring": "0.2.1", - "rollup": "4.0.2", + "rollup": "4.1.4", "sanitize-html": "2.11.0", - "sass": "1.69.1", + "sass": "1.69.3", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.157.0", @@ -78,44 +78,44 @@ "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-actions": "7.4.6", - "@storybook/addon-essentials": "7.4.6", - "@storybook/addon-interactions": "7.4.6", - "@storybook/addon-links": "7.4.6", - "@storybook/addon-storysource": "7.4.6", - "@storybook/addons": "7.4.6", - "@storybook/blocks": "7.4.6", - "@storybook/core-events": "7.4.6", + "@storybook/addon-actions": "7.5.0", + "@storybook/addon-essentials": "7.5.0", + "@storybook/addon-interactions": "7.5.0", + "@storybook/addon-links": "7.5.0", + "@storybook/addon-storysource": "7.5.0", + "@storybook/addons": "7.5.0", + "@storybook/blocks": "7.5.0", + "@storybook/core-events": "7.5.0", "@storybook/jest": "0.2.3", - "@storybook/manager-api": "7.4.6", - "@storybook/preview-api": "7.4.6", - "@storybook/react": "7.4.6", - "@storybook/react-vite": "7.4.6", + "@storybook/manager-api": "7.5.0", + "@storybook/preview-api": "7.5.0", + "@storybook/react": "7.5.0", + "@storybook/react-vite": "7.5.0", "@storybook/testing-library": "0.2.2", - "@storybook/theming": "7.4.6", - "@storybook/types": "7.4.6", - "@storybook/vue3": "7.4.6", - "@storybook/vue3-vite": "7.4.6", + "@storybook/theming": "7.5.0", + "@storybook/types": "7.5.0", + "@storybook/vue3": "7.5.0", + "@storybook/vue3-vite": "7.5.0", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", "@types/estree": "1.0.2", "@types/matter-js": "0.19.1", "@types/micromatch": "4.0.3", - "@types/node": "20.8.4", + "@types/node": "20.8.6", "@types/punycode": "2.1.0", - "@types/sanitize-html": "2.9.1", + "@types/sanitize-html": "2.9.2", "@types/throttle-debounce": "5.0.0", "@types/tinycolor2": "1.4.4", "@types/uuid": "9.0.5", "@types/websocket": "1.0.7", - "@types/ws": "8.5.6", - "@typescript-eslint/eslint-plugin": "6.7.5", - "@typescript-eslint/parser": "6.7.5", + "@types/ws": "8.5.7", + "@typescript-eslint/eslint-plugin": "6.8.0", + "@typescript-eslint/parser": "6.8.0", "@vitest/coverage-v8": "0.34.6", "@vue/runtime-core": "3.3.4", "acorn": "8.10.0", "cross-env": "7.0.3", - "cypress": "13.3.0", + "cypress": "13.3.1", "eslint": "8.51.0", "eslint-plugin-import": "2.28.1", "eslint-plugin-vue": "9.17.0", @@ -123,19 +123,19 @@ "happy-dom": "10.0.3", "micromatch": "4.0.5", "msw": "1.3.2", - "msw-storybook-addon": "1.8.0", + "msw-storybook-addon": "1.9.0", "nodemon": "3.0.1", "prettier": "3.0.3", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.1", - "storybook": "7.4.6", + "storybook": "7.5.0", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", "vue-eslint-parser": "9.3.2", - "vue-tsc": "1.8.18" + "vue-tsc": "1.8.19" } } diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 15043fcd0b..b8de71e3b7 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -42,6 +42,7 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { $i } from '@/account.js'; +import { defaultStore } from "@/store.js"; const props = withDefaults(defineProps<{ user: Misskey.entities.UserDetailed, @@ -52,6 +53,10 @@ const props = withDefaults(defineProps<{ large: false, }); +const emit = defineEmits<{ + (_: 'update:user', value: Misskey.entities.UserDetailed): void +}>(); + let isFollowing = $ref(props.user.isFollowing); let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); let wait = $ref(false); @@ -95,6 +100,11 @@ async function onClick() { } else { await os.api('following/create', { userId: props.user.id, + withReplies: defaultStore.state.defaultWithReplies, + }); + emit('update:user', { + ...props.user, + withReplies: defaultStore.state.defaultWithReplies }); hasPendingFollowRequestFromYou = true; diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue index de726e3aa4..e384b7a0bc 100644 --- a/packages/frontend/src/components/MkInstanceCardMini.vue +++ b/packages/frontend/src/components/MkInstanceCardMini.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only -->