From e00fdc2d5986b3f12b93e410ae7e191da8fe5315 Mon Sep 17 00:00:00 2001 From: YAVIIGI <118232419+YAVIIGI@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:27:51 +0900 Subject: [PATCH 01/57] =?UTF-8?q?fix(frontend):=20use-tooltip=20=E3=81=AE?= =?UTF-8?q?=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97=E5=85=83=E3=81=AE=20UI=20?= =?UTF-8?q?=E3=81=8C=E7=84=A1=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=9F=E3=82=89?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E7=9A=84=E3=81=AB=E5=89=8A=E9=99=A4=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=20(#11949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update use-tooltip.ts * Update CHANGELOG.md --- CHANGELOG.md | 1 + packages/frontend/src/scripts/use-tooltip.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2f11b156..c6cadf54b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Enhance: AiScriptでホストのアドレスを参照する定数`SERVER_URL`を追加 - Enhance: モデレーションログ機能の強化 - Enhance: ローカリゼーションの更新 +- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正 ### Server - Fix: Redisに古いバージョンのキャッシュが残っている場合、キャッシュが消えるまでの間通知が届かなくなる問題を修正 diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/scripts/use-tooltip.ts index 0a82997728..17ea380db0 100644 --- a/packages/frontend/src/scripts/use-tooltip.ts +++ b/packages/frontend/src/scripts/use-tooltip.ts @@ -21,6 +21,8 @@ export function useTooltip( let changeShowingState: (() => void) | null; + let autoHidingTimer; + const open = () => { close(); if (!isHovering) return; @@ -33,6 +35,16 @@ export function useTooltip( changeShowingState = () => { showing.value = false; }; + + autoHidingTimer = window.setInterval(() => { + if (!document.body.contains(elRef.value)) { + if (!isHovering) return; + isHovering = false; + window.clearTimeout(timeoutId); + close(); + window.clearInterval(autoHidingTimer); + } + }, 1000); }; const close = () => { @@ -53,6 +65,7 @@ export function useTooltip( if (!isHovering) return; isHovering = false; window.clearTimeout(timeoutId); + window.clearInterval(autoHidingTimer); close(); }; @@ -67,6 +80,7 @@ export function useTooltip( if (!isHovering) return; isHovering = false; window.clearTimeout(timeoutId); + window.clearInterval(autoHidingTimer); close(); }; From 000abcd2f0745989b0709f6b853fc6393c75d72f Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 3 Oct 2023 11:28:26 +0900 Subject: [PATCH 02/57] Update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6cadf54b6..2196015349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ --> +## 2023.10.0 + +### Client +- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正 + ## 2023.9.3 ### General - Enhance: ノートの翻訳機能の利用可否をロールで設定可能に @@ -20,7 +25,6 @@ - Enhance: AiScriptでホストのアドレスを参照する定数`SERVER_URL`を追加 - Enhance: モデレーションログ機能の強化 - Enhance: ローカリゼーションの更新 -- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正 ### Server - Fix: Redisに古いバージョンのキャッシュが残っている場合、キャッシュが消えるまでの間通知が届かなくなる問題を修正 From 10ae0b329add2930ad12d1aca36c3a357606f531 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 3 Oct 2023 18:33:22 +0900 Subject: [PATCH 03/57] enhance(frontend): tweak ui --- .../src/pages/settings/mute-block.vue | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 37d3d1773f..12a5ffec43 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -5,13 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only From 17b83ff4c13e873b63262c349ea9c7bade0d656a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 08:46:27 +0900 Subject: [PATCH 11/57] =?UTF-8?q?enhance:=20TL=E3=82=AD=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E5=AE=B9=E9=87=8F=E3=82=92=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../1696373953614-meta-cache-settings.js | 22 ++++ .../backend/src/core/NoteCreateService.ts | 24 ++-- packages/backend/src/models/Meta.ts | 20 ++++ .../src/server/api/endpoints/admin/meta.ts | 103 ++++++++++-------- .../server/api/endpoints/admin/update-meta.ts | 20 ++++ .../backend/src/server/api/endpoints/meta.ts | 4 +- .../src/pages/admin/external-services.vue | 81 ++++++++++++++ packages/frontend/src/pages/admin/index.vue | 5 + .../frontend/src/pages/admin/settings.vue | 40 ++++--- packages/frontend/src/router.ts | 4 + 12 files changed, 251 insertions(+), 74 deletions(-) create mode 100644 packages/backend/migration/1696373953614-meta-cache-settings.js create mode 100644 packages/frontend/src/pages/admin/external-services.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index 418e1c67ff..172cdcb754 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1131,6 +1131,7 @@ export interface Locale { "fileAttachedOnly": string; "showRepliesToOthersInTimeline": string; "hideRepliesToOthersInTimeline": string; + "externalServices": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 80e4466a74..1136f67baf 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1128,6 +1128,7 @@ mutualFollow: "相互フォロー" fileAttachedOnly: "ファイル付きのみ" showRepliesToOthersInTimeline: "TLに他の人への返信を含める" hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない" +externalServices: "外部サービス" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/backend/migration/1696373953614-meta-cache-settings.js b/packages/backend/migration/1696373953614-meta-cache-settings.js new file mode 100644 index 0000000000..f994b76ef2 --- /dev/null +++ b/packages/backend/migration/1696373953614-meta-cache-settings.js @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MetaCacheSettings1696373953614 { + name = 'MetaCacheSettings1696373953614' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "perLocalUserUserTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perRemoteUserUserTimelineCacheMax" integer NOT NULL DEFAULT '100'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perUserHomeTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perUserListTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserListTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserHomeTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perRemoteUserUserTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perLocalUserUserTimelineCacheMax"`); + } +} diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 8fb34fd637..7e1c0b5c22 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -803,6 +803,8 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { + const meta = await this.metaService.fetch(); + const redisPipeline = this.redisForTimelines.pipeline(); if (note.channelId) { @@ -816,14 +818,14 @@ export class NoteCreateService implements OnApplicationShutdown { for (const channelFollowing of channelFollowings) { redisPipeline.xadd( `homeTimeline:${channelFollowing.followerId}`, - 'MAXLEN', '~', '200', + 'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(), '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( `homeTimelineWithFiles:${channelFollowing.followerId}`, - 'MAXLEN', '~', '100', + 'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(), '*', 'note', note.id); } @@ -855,14 +857,14 @@ export class NoteCreateService implements OnApplicationShutdown { redisPipeline.xadd( `homeTimeline:${following.followerId}`, - 'MAXLEN', '~', '200', + 'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(), '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( `homeTimelineWithFiles:${following.followerId}`, - 'MAXLEN', '~', '100', + 'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(), '*', 'note', note.id); } @@ -882,14 +884,14 @@ export class NoteCreateService implements OnApplicationShutdown { redisPipeline.xadd( `userListTimeline:${userListMembership.userListId}`, - 'MAXLEN', '~', '200', + 'MAXLEN', '~', meta.perUserListTimelineCacheMax.toString(), '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( `userListTimelineWithFiles:${userListMembership.userListId}`, - 'MAXLEN', '~', '100', + 'MAXLEN', '~', (meta.perUserListTimelineCacheMax / 2).toString(), '*', 'note', note.id); } @@ -898,14 +900,14 @@ export class NoteCreateService implements OnApplicationShutdown { { // 自分自身のHTL redisPipeline.xadd( `homeTimeline:${user.id}`, - 'MAXLEN', '~', '200', + 'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(), '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( `homeTimelineWithFiles:${user.id}`, - 'MAXLEN', '~', '100', + 'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(), '*', 'note', note.id); } @@ -916,20 +918,20 @@ export class NoteCreateService implements OnApplicationShutdown { if (note.replyId && note.replyUserId !== note.userId) { redisPipeline.xadd( `userTimelineWithReplies:${user.id}`, - 'MAXLEN', '~', '1000', + 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), '*', 'note', note.id); } else { redisPipeline.xadd( `userTimeline:${user.id}`, - 'MAXLEN', '~', '1000', + 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( `userTimelineWithFiles:${user.id}`, - 'MAXLEN', '~', '500', + 'MAXLEN', '~', note.userHost == null ? (meta.perLocalUserUserTimelineCacheMax / 2).toString() : (meta.perRemoteUserUserTimelineCacheMax / 2).toString(), '*', 'note', note.id); } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index e69bef8e98..491d446723 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -471,4 +471,24 @@ export class MiMeta { length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', }) public preservedUsernames: string[]; + + @Column('integer', { + default: 300, + }) + public perLocalUserUserTimelineCacheMax: number; + + @Column('integer', { + default: 100, + }) + public perRemoteUserUserTimelineCacheMax: number; + + @Column('integer', { + default: 300, + }) + public perUserHomeTimelineCacheMax: number; + + @Column('integer', { + default: 300, + }) + public perUserListTimelineCacheMax: number; } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c3ba07cdd0..53e3672784 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -105,40 +105,32 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - userStarForReactionFallback: { - type: 'boolean', - optional: true, nullable: false, - }, pinnedUsers: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, hiddenTags: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, blockedHosts: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, sensitiveWords: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, preservedUsernames: { @@ -146,129 +138,124 @@ export const meta = { optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, hcaptchaSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, recaptchaSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, turnstileSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, sensitiveMediaDetection: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, sensitiveMediaDetectionSensitivity: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, setSensitiveFlagAutomatically: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableSensitiveMediaDetectionForVideos: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, proxyAccountId: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, format: 'id', }, - summaryProxy: { - type: 'string', - optional: true, nullable: true, - }, email: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpSecure: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, smtpHost: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpPort: { type: 'number', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpUser: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpPass: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, swPrivateKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, useObjectStorage: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageBaseUrl: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageBucket: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStoragePrefix: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageEndpoint: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageRegion: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStoragePort: { type: 'number', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageAccessKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageUseSSL: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageUseProxy: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageSetPublicRead: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableIpLogging: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableActiveEmailValidation: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableChartsForRemoteUser: { type: 'boolean', @@ -288,12 +275,28 @@ export const meta = { }, manifestJsonOverride: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, policies: { type: 'object', optional: false, nullable: false, }, + perLocalUserUserTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perRemoteUserUserTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perUserHomeTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perUserListTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, }, }, } as const; @@ -313,7 +316,7 @@ export default class extends Endpoint { // eslint- private metaService: MetaService, ) { - super(meta, paramDef, async (ps, me) => { + super(meta, paramDef, async () => { const instance = await this.metaService.fetch(true); return { @@ -399,6 +402,10 @@ export default class extends Endpoint { // eslint- enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, manifestJsonOverride: instance.manifestJsonOverride, + perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, }; }); } 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 ea6ebdd1fe..247d3ba4e0 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -108,6 +108,10 @@ export const paramDef = { serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, manifestJsonOverride: { type: 'string' }, + perLocalUserUserTimelineCacheMax: { type: 'integer' }, + perRemoteUserUserTimelineCacheMax: { type: 'integer' }, + perUserHomeTimelineCacheMax: { type: 'integer' }, + perUserListTimelineCacheMax: { type: 'integer' }, }, required: [], } as const; @@ -441,6 +445,22 @@ export default class extends Endpoint { // eslint- set.manifestJsonOverride = ps.manifestJsonOverride; } + if (ps.perLocalUserUserTimelineCacheMax !== undefined) { + set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax; + } + + if (ps.perRemoteUserUserTimelineCacheMax !== undefined) { + set.perRemoteUserUserTimelineCacheMax = ps.perRemoteUserUserTimelineCacheMax; + } + + if (ps.perUserHomeTimelineCacheMax !== undefined) { + set.perUserHomeTimelineCacheMax = ps.perUserHomeTimelineCacheMax; + } + + if (ps.perUserListTimelineCacheMax !== undefined) { + set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index fa6486ed18..271b3f6fb2 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -214,11 +214,11 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - localTimeLine: { + localTimeline: { type: 'boolean', optional: false, nullable: false, }, - globalTimeLine: { + globalTimeline: { type: 'boolean', optional: false, nullable: false, }, diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue new file mode 100644 index 0000000000..5944bf500a --- /dev/null +++ b/packages/frontend/src/pages/admin/external-services.vue @@ -0,0 +1,81 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 944ba7b950..a508c20cf3 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -198,6 +198,11 @@ const menuDef = $computed(() => [{ text: i18n.ts.proxyAccount, to: '/admin/proxy-account', active: currentPage?.route.name === 'proxy-account', + }, { + icon: 'ti ti-link', + text: i18n.ts.externalServices, + to: '/admin/external-services', + active: currentPage?.route.name === 'external-services', }, { icon: 'ti ti-adjustments', text: i18n.ts.other, diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index f93678d728..09a6cc7e2c 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -81,16 +81,24 @@ SPDX-License-Identifier: AGPL-3.0-only - +
- - - + + + + + + + + + + + + + + - - -
@@ -133,8 +141,10 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); -let deeplAuthKey: string = $ref(''); -let deeplIsPro: boolean = $ref(false); +let perLocalUserUserTimelineCacheMax: number = $ref(0); +let perRemoteUserUserTimelineCacheMax: number = $ref(0); +let perUserHomeTimelineCacheMax: number = $ref(0); +let perUserListTimelineCacheMax: number = $ref(0); async function init(): Promise { const meta = await os.api('admin/meta'); @@ -149,8 +159,10 @@ async function init(): Promise { enableServiceWorker = meta.enableServiceWorker; swPublicKey = meta.swPublickey; swPrivateKey = meta.swPrivateKey; - deeplAuthKey = meta.deeplAuthKey; - deeplIsPro = meta.deeplIsPro; + perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax; + perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax; + perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax; + perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax; } function save(): void { @@ -166,8 +178,10 @@ function save(): void { enableServiceWorker, swPublicKey, swPrivateKey, - deeplAuthKey, - deeplIsPro, + perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax, }).then(() => { fetchInstance(); }); diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 415d2f1974..20314711a4 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -435,6 +435,10 @@ export const routes = [{ path: '/proxy-account', name: 'proxy-account', component: page(() => import('./pages/admin/proxy-account.vue')), + }, { + path: '/external-services', + name: 'external-services', + component: page(() => import('./pages/admin/external-services.vue')), }, { path: '/other-settings', name: 'other-settings', From be81c1a6d68be363df426f2b0026559c054c3982 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 11:15:37 +0900 Subject: [PATCH 12/57] refactor --- .../api/endpoints/notes/hybrid-timeline.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) 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 d6ed3db6e3..cae749bb88 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -96,16 +96,18 @@ export default class extends Endpoint { // eslint- let ltlNoteIdsRes: [string, string[]][] = []; if (!ps.sinceId && !ps.sinceDate) { - htlNoteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - '-', - 'COUNT', limit); - ltlNoteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - '-', - 'COUNT', limit); + [htlNoteIdsRes, ltlNoteIdsRes] = await Promise.all([ + this.redisForTimelines.xrevrange( + ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + '-', + 'COUNT', limit), + this.redisForTimelines.xrevrange( + ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + '-', + 'COUNT', limit), + ]); } const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId); From a40734d417f589ad1e25f58c00cdc0616f962e8e Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 11:24:46 +0900 Subject: [PATCH 13/57] =?UTF-8?q?fix(backend):=20[2023.10.1.beta-1]=20[?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88]=E3=82=BF=E3=83=96=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E8=A6=8B=E3=81=88=E3=82=8B=E6=8A=95=E7=A8=BF=E3=81=8C?= =?UTF-8?q?[=E5=85=A8=E3=81=A6]=E3=82=BF=E3=83=96=E3=81=AB=E5=87=BA?= =?UTF-8?q?=E3=81=A6=E3=81=93=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #11960 --- .../src/server/api/endpoints/users/notes.ts | 27 +++++++-- packages/backend/test/e2e/timelines.ts | 58 +++++++++++++++++++ 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index abcc02eace..4613bfcf2a 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -76,16 +76,31 @@ export default class extends Endpoint { // eslint- const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1 let noteIdsRes: [string, string[]][] = []; + let repliesNoteIdsRes: [string, string[]][] = []; if (!ps.sinceId && !ps.sinceDate) { - noteIdsRes = await this.redisForTimelines.xrevrange( - ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : ps.withReplies ? `userTimelineWithReplies:${ps.userId}` : `userTimeline:${ps.userId}`, - ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', - '-', - 'COUNT', limit); + [noteIdsRes, repliesNoteIdsRes] = await Promise.all([ + this.redisForTimelines.xrevrange( + ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + '-', + 'COUNT', limit), + ps.withReplies + ? this.redisForTimelines.xrevrange( + `userTimelineWithReplies:${ps.userId}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', + '-', + 'COUNT', limit) + : Promise.resolve([]), + ]); } - const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId); + let noteIds = Array.from(new Set([ + ...noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId), + ...repliesNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId), + ])); + noteIds.sort((a, b) => a > b ? -1 : 1); + noteIds = noteIds.slice(0, ps.limit); if (noteIds.length === 0) { return []; diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index f4c7ffc82d..03311ac833 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -696,6 +696,64 @@ describe('Timelines', () => { }, 1000 * 10); }); + describe('User TL', () => { + test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', {}, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + }); + + test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + await api('/following/create', { userId: bob.id }, alice); + const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', {}, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); + }); + + test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', {}, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); + }); + + test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => { + const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); + + const carolNote = await post(carol, { text: 'hi' }); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', { withReplies: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); + }); + }); + // TODO: リノートミュート済みユーザーのテスト // TODO: ページネーションのテスト }); From 610b68c8ffa554aa7f18acf94e9f9e9bc4c8941c Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 11:32:33 +0900 Subject: [PATCH 14/57] tweak --- .../src/server/api/endpoints/users/notes.ts | 2 -- packages/backend/test/e2e/timelines.ts | 26 +++++++++++-------- packages/backend/test/utils.ts | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 4613bfcf2a..6be391be4e 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -18,8 +18,6 @@ import { ApiError } from '../../error.js'; export const meta = { tags: ['users', 'notes'], - description: 'Show all notes that this user created.', - res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 03311ac833..b1e84a821c 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -7,10 +7,14 @@ process.env.NODE_ENV = 'test'; process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true'; import * as assert from 'assert'; -import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl } from '../utils.js'; +import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl, randomString } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; import type * as misskey from 'misskey-js'; +function genHost() { + return randomString() + '.example.com'; +} + let app: INestApplicationContext; beforeAll(async () => { @@ -290,7 +294,7 @@ describe('Timelines', () => { }); test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await api('/following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); @@ -303,7 +307,7 @@ describe('Timelines', () => { }); test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await api('/following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); @@ -355,7 +359,7 @@ describe('Timelines', () => { }); test.concurrent('リモートユーザーのノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); const bobNote = await post(bob, { text: 'hi' }); @@ -487,7 +491,7 @@ describe('Timelines', () => { }); test.concurrent('リモートユーザーのノートが含まれない', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); const bobNote = await post(bob, { text: 'hi' }); @@ -499,7 +503,7 @@ describe('Timelines', () => { }); test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await api('/following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); @@ -512,7 +516,7 @@ describe('Timelines', () => { }); test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => { - const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]); + const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await api('/following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); @@ -704,7 +708,7 @@ describe('Timelines', () => { await sleep(100); // redisに追加されるのを待つ - const res = await api('/users/notes', {}, alice); + const res = await api('/users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -717,7 +721,7 @@ describe('Timelines', () => { await sleep(100); // redisに追加されるのを待つ - const res = await api('/users/notes', {}, alice); + const res = await api('/users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -732,7 +736,7 @@ describe('Timelines', () => { await sleep(100); // redisに追加されるのを待つ - const res = await api('/users/notes', {}, alice); + const res = await api('/users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); @@ -747,7 +751,7 @@ describe('Timelines', () => { await sleep(100); // redisに追加されるのを待つ - const res = await api('/users/notes', { withReplies: true }, alice); + const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index fae9018422..121c4711d3 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -99,7 +99,7 @@ export const relativeFetch = async (path: string, init?: RequestInit | undefined return await fetch(new URL(path, `http://127.0.0.1:${port}/`).toString(), init); }; -function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) { +export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) { let randomString = ''; for (let i = 0; i < length; i++) { randomString += chars[Math.floor(Math.random() * chars.length)]; From 3dd84f78240fc7fbeca9272f3941d90986f46c0a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 11:48:51 +0900 Subject: [PATCH 15/57] tweak --- .../backend/src/core/NoteCreateService.ts | 58 +++++++++---------- packages/backend/test/e2e/timelines.ts | 27 +++++++++ 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 7e1c0b5c22..3ea8af5cda 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -481,12 +481,10 @@ export class NoteCreateService implements OnApplicationShutdown { // Increment notes count (user) this.incNotesCountOfUser(user); - if (data.visibility === 'public' || data.visibility === 'home') { + if (data.visibility === 'specified') { + // TODO? + } else { this.pushToTl(note, user); - } else if (data.visibility === 'followers') { - this.pushToTl(note, user); - } else if (data.visibility === 'specified') { - // TODO } this.antennaService.addNoteToAntennas(note, user); @@ -913,44 +911,42 @@ export class NoteCreateService implements OnApplicationShutdown { } } - if (note.visibility === 'public' || note.visibility === 'home') { - // 自分自身以外への返信 - if (note.replyId && note.replyUserId !== note.userId) { + // 自分自身以外への返信 + if (note.replyId && note.replyUserId !== note.userId) { + redisPipeline.xadd( + `userTimelineWithReplies:${user.id}`, + 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), + '*', + 'note', note.id); + } else { + redisPipeline.xadd( + `userTimeline:${user.id}`, + 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), + '*', + 'note', note.id); + + if (note.fileIds.length > 0) { redisPipeline.xadd( - `userTimelineWithReplies:${user.id}`, - 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), + `userTimelineWithFiles:${user.id}`, + 'MAXLEN', '~', note.userHost == null ? (meta.perLocalUserUserTimelineCacheMax / 2).toString() : (meta.perRemoteUserUserTimelineCacheMax / 2).toString(), '*', 'note', note.id); - } else { + } + + if (note.visibility === 'public' && note.userHost == null) { redisPipeline.xadd( - `userTimeline:${user.id}`, - 'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(), + 'localTimeline', + 'MAXLEN', '~', '1000', '*', 'note', note.id); if (note.fileIds.length > 0) { redisPipeline.xadd( - `userTimelineWithFiles:${user.id}`, - 'MAXLEN', '~', note.userHost == null ? (meta.perLocalUserUserTimelineCacheMax / 2).toString() : (meta.perRemoteUserUserTimelineCacheMax / 2).toString(), + 'localTimelineWithFiles', + 'MAXLEN', '~', '500', '*', 'note', note.id); } - - if (note.visibility === 'public' && note.userHost == null) { - redisPipeline.xadd( - 'localTimeline', - 'MAXLEN', '~', '1000', - '*', - 'note', note.id); - - if (note.fileIds.length > 0) { - redisPipeline.xadd( - 'localTimelineWithFiles', - 'MAXLEN', '~', '500', - '*', - 'note', note.id); - } - } } } diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index b1e84a821c..10840b1336 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -701,6 +701,18 @@ describe('Timelines', () => { }); describe('User TL', () => { + test.concurrent('ノートが含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const bobNote = await post(bob, { text: 'hi' }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', { userId: bob.id }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + }); + test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); @@ -756,6 +768,21 @@ describe('Timelines', () => { assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); }); + + test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { + const [alice, bob] = await Promise.all([signup(), signup()]); + + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'); + const bobNote1 = await post(bob, { text: 'hi' }); + const bobNote2 = await post(bob, { fileIds: [file.id] }); + + await sleep(100); // redisに追加されるのを待つ + + const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice); + + assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); + }, 1000 * 10); }); // TODO: リノートミュート済みユーザーのテスト From cc4fd6b5c5ecff929930f82fed01ce5f756b2651 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 11:56:47 +0900 Subject: [PATCH 16/57] fix flaky test --- packages/backend/test/e2e/timelines.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 10840b1336..edb0b684ba 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -729,6 +729,7 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup()]); await api('/following/create', { userId: bob.id }, alice); + await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await sleep(100); // redisに追加されるのを待つ From b40329887f6f383a7236ac022b8dfde9217cdbae Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 12:05:01 +0900 Subject: [PATCH 17/57] revert: note editing --- CHANGELOG.md | 1 + locales/index.d.ts | 1 - locales/ja-JP.yml | 1 - .../1696388600237-revert-note-edit.js | 16 ++++ packages/backend/src/core/RoleService.ts | 3 - .../src/core/entities/NoteEntityService.ts | 1 - packages/backend/src/models/Note.ts | 5 -- .../backend/src/models/json-schema/note.ts | 5 -- .../backend/src/server/api/EndpointsModule.ts | 4 - packages/backend/src/server/api/endpoints.ts | 2 - .../src/server/api/endpoints/notes/update.ts | 89 ------------------- .../src/components/MkNoteDetailed.vue | 3 - .../frontend/src/components/MkNoteHeader.vue | 1 - .../frontend/src/components/MkPostForm.vue | 4 +- .../src/components/MkPostFormDialog.vue | 1 - packages/frontend/src/const.ts | 1 - .../frontend/src/pages/admin/roles.editor.vue | 20 ----- packages/frontend/src/pages/admin/roles.vue | 8 -- .../frontend/src/scripts/get-note-menu.ts | 9 -- .../frontend/src/scripts/use-note-capture.ts | 7 -- packages/misskey-js/etc/misskey-js.api.md | 3 +- packages/misskey-js/src/entities.ts | 1 - packages/misskey-js/src/streaming.types.ts | 7 -- 23 files changed, 19 insertions(+), 174 deletions(-) create mode 100644 packages/backend/migration/1696388600237-revert-note-edit.js delete mode 100644 packages/backend/src/server/api/endpoints/notes/update.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a761b79b82..5e525ace6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ## 2023.10.0 ### NOTE - muted_noteテーブルは使われなくなったため手動で削除を行ってください。 +- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました ### Changes - API: users/notes, notes/local-timeline で fileType 指定はできなくなりました diff --git a/locales/index.d.ts b/locales/index.d.ts index 172cdcb754..51d23436c0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1545,7 +1545,6 @@ export interface Locale { "gtlAvailable": string; "ltlAvailable": string; "canPublicNote": string; - "canEditNote": string; "canInvite": string; "inviteLimit": string; "inviteLimitCycle": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1136f67baf..2c586c7532 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1466,7 +1466,6 @@ _role: gtlAvailable: "グローバルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧" canPublicNote: "パブリック投稿の許可" - canEditNote: "ノートの編集" canInvite: "サーバー招待コードの発行" inviteLimit: "招待コードの作成可能数" inviteLimitCycle: "招待コードの発行間隔" diff --git a/packages/backend/migration/1696388600237-revert-note-edit.js b/packages/backend/migration/1696388600237-revert-note-edit.js new file mode 100644 index 0000000000..83bc552c35 --- /dev/null +++ b/packages/backend/migration/1696388600237-revert-note-edit.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RevertNoteEdit1696388600237 { + name = 'RevertNoteEdit1696388600237' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`); + } +} diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index ad40fbaecd..f2bd9de5ee 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -26,7 +26,6 @@ export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; canPublicNote: boolean; - canEditNote: boolean; canInvite: boolean; inviteLimit: number; inviteLimitCycle: number; @@ -52,7 +51,6 @@ export const DEFAULT_POLICIES: RolePolicies = { gtlAvailable: true, ltlAvailable: true, canPublicNote: true, - canEditNote: true, canInvite: false, inviteLimit: 0, inviteLimitCycle: 60 * 24 * 7, @@ -298,7 +296,6 @@ export class RoleService implements OnApplicationShutdown { gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)), ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)), canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)), - canEditNote: calc('canEditNote', vs => vs.some(v => v === true)), canInvite: calc('canInvite', vs => vs.some(v => v === true)), inviteLimit: calc('inviteLimit', vs => Math.max(...vs)), inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)), diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index cf0abd9174..824f8fa71d 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -308,7 +308,6 @@ export class NoteEntityService implements OnModuleInit { const packed: Packed<'Note'> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), - updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined, userId: note.userId, user: this.userEntityService.pack(note.user ?? note.userId, me, { detail: false, diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index f396a0cd7a..ed86d4549e 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -24,11 +24,6 @@ export class MiNote { }) public createdAt: Date; - @Column('timestamp with time zone', { - default: null, - }) - public updatedAt: Date | null; - @Index() @Column({ ...id(), diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index ad0cb3c45d..2caf0d0c3d 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -17,11 +17,6 @@ export const packedNoteSchema = { optional: false, nullable: false, format: 'date-time', }, - updatedAt: { - type: 'string', - optional: true, nullable: true, - format: 'date-time', - }, deletedAt: { type: 'string', optional: true, nullable: true, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 4b549c3439..3c4adafdbd 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -257,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_update from './endpoints/notes/update.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_featured from './endpoints/notes/featured.js'; @@ -607,7 +606,6 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; -const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default }; const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default }; const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default }; const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default }; @@ -961,7 +959,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_conversation, $notes_create, $notes_delete, - $notes_update, $notes_favorites_create, $notes_favorites_delete, $notes_featured, @@ -1309,7 +1306,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $notes_conversation, $notes_create, $notes_delete, - $notes_update, $notes_favorites_create, $notes_favorites_delete, $notes_featured, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index d33cd6d887..199d910fc4 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -257,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js'; import * as ep___notes_conversation from './endpoints/notes/conversation.js'; import * as ep___notes_create from './endpoints/notes/create.js'; import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_update from './endpoints/notes/update.js'; import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; import * as ep___notes_featured from './endpoints/notes/featured.js'; @@ -605,7 +604,6 @@ const eps = [ ['notes/conversation', ep___notes_conversation], ['notes/create', ep___notes_create], ['notes/delete', ep___notes_delete], - ['notes/update', ep___notes_update], ['notes/favorites/create', ep___notes_favorites_create], ['notes/favorites/delete', ep___notes_favorites_delete], ['notes/featured', ep___notes_featured], diff --git a/packages/backend/src/server/api/endpoints/notes/update.ts b/packages/backend/src/server/api/endpoints/notes/update.ts deleted file mode 100644 index cdf7f085e0..0000000000 --- a/packages/backend/src/server/api/endpoints/notes/update.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import ms from 'ms'; -import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository, NotesRepository } from '@/models/_.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { NoteDeleteService } from '@/core/NoteDeleteService.js'; -import { DI } from '@/di-symbols.js'; -import { GetterService } from '@/server/api/GetterService.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { ApiError } from '../../error.js'; - -export const meta = { - tags: ['notes'], - - requireCredential: true, - requireRolePolicy: 'canEditNote', - - kind: 'write:notes', - - limit: { - duration: ms('1hour'), - max: 10, - minInterval: ms('1sec'), - }, - - errors: { - noSuchNote: { - message: 'No such note.', - code: 'NO_SUCH_NOTE', - id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - noteId: { type: 'string', format: 'misskey:id' }, - text: { - type: 'string', - minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, - nullable: false, - }, - cw: { type: 'string', nullable: true, maxLength: 100 }, - }, - required: ['noteId', 'text', 'cw'], -} as const; - -@Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - private getterService: GetterService, - private globalEventService: GlobalEventService, - ) { - super(meta, paramDef, async (ps, me) => { - const note = await this.getterService.getNote(ps.noteId).catch(err => { - if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw err; - }); - - if (note.userId !== me.id) { - throw new ApiError(meta.errors.noSuchNote); - } - - await this.notesRepository.update({ id: note.id }, { - updatedAt: new Date(), - cw: ps.cw, - text: ps.text, - }); - - this.globalEventService.publishNoteStream(note.id, 'updated', { - cw: ps.cw, - text: ps.text, - }); - }); - } -} diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a9da5a3a62..a1360aba9d 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -93,9 +93,6 @@ SPDX-License-Identifier: AGPL-3.0-only