From f7c6932a83f80b6c43bcb79dcd11e8d490ce242a Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Sep 2023 10:55:26 +0900
Subject: [PATCH] =?UTF-8?q?enhance:=20=E5=90=84=E3=83=8E=E3=83=BC=E3=83=88?=
 =?UTF-8?q?=E3=81=8C=E8=A2=AB=E3=82=AF=E3=83=AA=E3=83=83=E3=83=97=E6=95=B0?=
 =?UTF-8?q?=E3=82=92=E4=BF=9D=E6=8C=81=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?=
 =?UTF-8?q?=E3=81=AB=E3=81=97=E3=80=81=E7=84=A1=E6=84=8F=E5=91=B3=E3=81=AB?=
 =?UTF-8?q?notes/clips=E3=82=92=E5=8F=A9=E3=81=8B=E3=81=AA=E3=81=84?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../1694850832075-server-icons-and-manifest.js   |  5 +++++
 .../migration/1694915420864-clipped-count.js     | 16 ++++++++++++++++
 .../src/core/entities/NoteEntityService.ts       |  2 ++
 packages/backend/src/models/entities/Note.ts     |  5 +++++
 .../src/server/api/endpoints/clips/add-note.ts   |  9 +++++++--
 .../server/api/endpoints/clips/remove-note.ts    |  5 +++++
 packages/frontend/src/pages/note.vue             | 11 ++++++-----
 packages/misskey-js/etc/misskey-js.api.md        |  1 +
 packages/misskey-js/src/entities.ts              |  1 +
 9 files changed, 48 insertions(+), 7 deletions(-)
 create mode 100644 packages/backend/migration/1694915420864-clipped-count.js

diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
index cbbb07d393..1bd8979d9b 100644
--- a/packages/backend/migration/1694850832075-server-icons-and-manifest.js
+++ b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
@@ -1,3 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
 export class ServerIconsAndManifest1694850832075 {
     name = 'ServerIconsAndManifest1694850832075'
 
diff --git a/packages/backend/migration/1694915420864-clipped-count.js b/packages/backend/migration/1694915420864-clipped-count.js
new file mode 100644
index 0000000000..1ad8e04ce0
--- /dev/null
+++ b/packages/backend/migration/1694915420864-clipped-count.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ClippedCount1694915420864 {
+    name = 'ClippedCount1694915420864'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" ADD "clippedCount" smallint NOT NULL DEFAULT '0'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "clippedCount"`);
+    }
+}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index add50457c2..242ef07e7c 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -340,6 +340,8 @@ export class NoteEntityService implements OnModuleInit {
 			url: note.url ?? undefined,
 
 			...(opts.detail ? {
+				clippedCount: note.clippedCount,
+
 				reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
 					detail: false,
 					_hint_: options?._hint_,
diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts
index 42343f015b..effc1509e5 100644
--- a/packages/backend/src/models/entities/Note.ts
+++ b/packages/backend/src/models/entities/Note.ts
@@ -107,6 +107,11 @@ export class MiNote {
 	})
 	public repliesCount: number;
 
+	@Column('smallint', {
+		default: 0,
+	})
+	public clippedCount: number;
+
 	@Column('jsonb', {
 		default: {},
 	})
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index e7fae8662c..00b8bb09a8 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -8,7 +8,7 @@ import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { IdService } from '@/core/IdService.js';
 import { DI } from '@/di-symbols.js';
-import type { ClipNotesRepository, ClipsRepository } from '@/models/_.js';
+import type { ClipNotesRepository, ClipsRepository, NotesRepository } from '@/models/_.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../error.js';
@@ -72,6 +72,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.clipNotesRepository)
 		private clipNotesRepository: ClipNotesRepository,
 
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
 		private idService: IdService,
 		private roleService: RoleService,
 		private getterService: GetterService,
@@ -115,9 +118,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				clipId: clip.id,
 			});
 
-			await this.clipsRepository.update(clip.id, {
+			this.clipsRepository.update(clip.id, {
 				lastClippedAt: new Date(),
 			});
+
+			this.notesRepository.increment({ id: note.id }, 'clippedCount', 1);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
index 53389d0283..65fad5f970 100644
--- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
@@ -52,6 +52,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.clipNotesRepository)
 		private clipNotesRepository: ClipNotesRepository,
 
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
 		private getterService: GetterService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -73,6 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				noteId: note.id,
 				clipId: clip.id,
 			});
+
+			this.notesRepository.decrement({ id: note.id }, 'clippedCount', 1);
 		});
 	}
 }
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index e1f80bf126..ea5e4f5090 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -94,13 +94,14 @@ function fetchNote() {
 		noteId: props.noteId,
 	}).then(res => {
 		note = res;
-		Promise.all([
+		// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
+		if (note.clippedCount > 0 || new Date(note.createdAt).getTime() < new Date('2023-10-01').getTime()) {
 			os.api('notes/clips', {
 				noteId: note.id,
-			}),
-		]).then(([_clips]) => {
-			clips = _clips;
-		});
+			}).then((_clips) => {
+				clips = _clips;
+			});
+		}
 	}).catch(err => {
 		error = err;
 	});
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index f993f59b6f..99b3852b02 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2539,6 +2539,7 @@ type Note = {
     reactions: Record<string, number>;
     renoteCount: number;
     repliesCount: number;
+    clippedCount?: number;
     poll?: {
         expiresAt: DateString | null;
         multiple: boolean;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index c556df8f50..2876339102 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -175,6 +175,7 @@ export type Note = {
 	reactions: Record<string, number>;
 	renoteCount: number;
 	repliesCount: number;
+	clippedCount?: number;
 	poll?: {
 		expiresAt: DateString | null;
 		multiple: boolean;
-- 
GitLab