diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd1de4fde..ef1a81a173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Fix: ヘッダーメニューのチャンネルの新規作成の項目でチャンネル作成ページに飛べない問題を修正 #16816 ### Server -- +- Enhance: `clips/my-favorites` APIがページネーションに対応しました ## 2025.11.0 diff --git a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts index 44719592d1..057b567312 100644 --- a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts +++ b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; import type { ClipFavoritesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; @@ -30,6 +31,11 @@ export const meta = { export const paramDef = { type: 'object', properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + sinceDate: { type: 'integer' }, + untilDate: { type: 'integer' }, }, required: [], } as const; @@ -40,14 +46,16 @@ export default class extends Endpoint { // eslint- @Inject(DI.clipFavoritesRepository) private clipFavoritesRepository: ClipFavoritesRepository, + private queryService: QueryService, private clipEntityService: ClipEntityService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.clipFavoritesRepository.createQueryBuilder('favorite') + const query = this.queryService.makePaginationQuery(this.clipFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('favorite.userId = :meId', { meId: me.id }) .leftJoinAndSelect('favorite.clip', 'clip'); const favorites = await query + .limit(ps.limit) .getMany(); return this.clipEntityService.packMany(favorites.map(x => x.clip!), me); diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index fe9a217ee8..fec83c2433 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -506,10 +506,10 @@ describe('クリップ', () => { }); }; - const myFavorites = async (request: Partial> = {}): Promise => { + const myFavorites = async (parameters: Misskey.entities.ClipsMyFavoritesRequest, request: Partial> = {}): Promise => { return successfulApiCall({ endpoint: 'clips/my-favorites', - parameters: {}, + parameters, user: alice, ...request, }); @@ -562,8 +562,9 @@ describe('クリップ', () => { await favorite({ clipId: clip.id }); } - // pagenationはない。全部一気にとれる。 - const favorited = await myFavorites(); + const favorited = await myFavorites({ + limit: 30, + }); assert.strictEqual(favorited.length, clips.length); for (const clip of favorited) { assert.strictEqual(clip.favoritedCount, 1); @@ -617,7 +618,7 @@ describe('クリップ', () => { const clip = await show({ clipId: aliceClip.id }); assert.strictEqual(clip.favoritedCount, 0); assert.strictEqual(clip.isFavorited, false); - assert.deepStrictEqual(await myFavorites(), []); + assert.deepStrictEqual(await myFavorites({}), []); }); test.each([ @@ -651,13 +652,13 @@ describe('クリップ', () => { test('を取得できる。', async () => { await favorite({ clipId: aliceClip.id }); - const favorited = await myFavorites(); + const favorited = await myFavorites({}); assert.deepStrictEqual(favorited, [await show({ clipId: aliceClip.id })]); }); test('を取得したとき他人のお気に入りは含まない。', async () => { await favorite({ clipId: aliceClip.id }); - const favorited = await myFavorites({ user: bob }); + const favorited = await myFavorites({}, { user: bob }); assert.deepStrictEqual(favorited, []); }); }); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index fe19c00a80..74f69f67c1 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1223,6 +1223,9 @@ type ClipsListRequest = operations['clips___list']['requestBody']['content']['ap // @public (undocumented) type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; +// @public (undocumented) +type ClipsMyFavoritesRequest = operations['clips___my-favorites']['requestBody']['content']['application/json']; + // @public (undocumented) type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; @@ -1774,6 +1777,7 @@ declare namespace entities { ClipsFavoriteRequest, ClipsListRequest, ClipsListResponse, + ClipsMyFavoritesRequest, ClipsMyFavoritesResponse, ClipsNotesRequest, ClipsNotesResponse, diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index c3ef3de4e6..6bcdb45200 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -269,6 +269,7 @@ import type { ClipsFavoriteRequest, ClipsListRequest, ClipsListResponse, + ClipsMyFavoritesRequest, ClipsMyFavoritesResponse, ClipsNotesRequest, ClipsNotesResponse, @@ -838,7 +839,7 @@ export type Endpoints = { 'clips/delete': { req: ClipsDeleteRequest; res: EmptyResponse }; 'clips/favorite': { req: ClipsFavoriteRequest; res: EmptyResponse }; 'clips/list': { req: ClipsListRequest; res: ClipsListResponse }; - 'clips/my-favorites': { req: EmptyRequest; res: ClipsMyFavoritesResponse }; + 'clips/my-favorites': { req: ClipsMyFavoritesRequest; res: ClipsMyFavoritesResponse }; 'clips/notes': { req: ClipsNotesRequest; res: ClipsNotesResponse }; 'clips/remove-note': { req: ClipsRemoveNoteRequest; res: EmptyResponse }; 'clips/show': { req: ClipsShowRequest; res: ClipsShowResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 0d57b065dc..acb4c1a802 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -272,6 +272,7 @@ export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['co export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; export type ClipsListRequest = operations['clips___list']['requestBody']['content']['application/json']; export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; +export type ClipsMyFavoritesRequest = operations['clips___my-favorites']['requestBody']['content']['application/json']; export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; export type ClipsNotesResponse = operations['clips___notes']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 2650869590..9b67b93602 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -18638,6 +18638,20 @@ export interface operations { }; }; 'clips___my-favorites': { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + }; + }; + }; responses: { /** @description OK (with results) */ 200: {