diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js index cbbb07d39307..1bd8979d9b90 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 000000000000..1ad8e04ce083 --- /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 add50457c27e..242ef07e7cd3 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 42343f015b8f..effc1509e562 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 e7fae8662cb6..00b8bb09a852 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 { // 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 { // 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 53389d0283f1..65fad5f9703c 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 { // 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 { // 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 e1f80bf126d8..ea5e4f509020 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 f993f59b6f3b..99b3852b02e6 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; 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 c556df8f507b..28763391021c 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -175,6 +175,7 @@ export type Note = { reactions: Record; renoteCount: number; repliesCount: number; + clippedCount?: number; poll?: { expiresAt: DateString | null; multiple: boolean;