From 072f4b460865320ae437c0c838f588a632086db7 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Mon, 30 Sep 2024 00:49:45 -0400 Subject: [PATCH] add /notes/following endpoint --- .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 2 + .../server/api/endpoints/notes/following.ts | 88 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 packages/backend/src/server/api/endpoints/notes/following.ts diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 4a08410ceb..c90a23d7b5 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -290,6 +290,7 @@ import * as ep___notes_delete from './endpoints/notes/delete.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'; +import * as ep___notes_following from './endpoints/notes/following.js'; import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js'; import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; @@ -686,6 +687,7 @@ const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___not 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 }; +const $notes_following: Provider = { provide: 'ep:notes/following', useClass: ep___notes_following.default }; const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default }; const $notes_bubbleTimeline: Provider = { provide: 'ep:notes/bubble-timeline', useClass: ep___notes_bubbleTimeline.default }; const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default }; @@ -1086,6 +1088,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $notes_favorites_create, $notes_favorites_delete, $notes_featured, + $notes_following, $notes_globalTimeline, $notes_bubbleTimeline, $notes_hybridTimeline, @@ -1480,6 +1483,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $notes_favorites_create, $notes_favorites_delete, $notes_featured, + $notes_following, $notes_globalTimeline, $notes_bubbleTimeline, $notes_hybridTimeline, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index e2fcd1a9d0..e93e57f907 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -296,6 +296,7 @@ import * as ep___notes_delete from './endpoints/notes/delete.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'; +import * as ep___notes_following from './endpoints/notes/following.js'; import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js'; import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; @@ -690,6 +691,7 @@ const eps = [ ['notes/favorites/create', ep___notes_favorites_create], ['notes/favorites/delete', ep___notes_favorites_delete], ['notes/featured', ep___notes_featured], + ['notes/following', ep___notes_following], ['notes/global-timeline', ep___notes_globalTimeline], ['notes/bubble-timeline', ep___notes_bubbleTimeline], ['notes/hybrid-timeline', ep___notes_hybridTimeline], diff --git a/packages/backend/src/server/api/endpoints/notes/following.ts b/packages/backend/src/server/api/endpoints/notes/following.ts new file mode 100644 index 0000000000..57ab5a6aeb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/following.ts @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { LatestNote, MiFollowing, MiBlocking, MiMuting } from '@/models/_.js'; +import type { NotesRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { QueryService } from '@/core/QueryService.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: true, + kind: 'read:account', + allowGet: true, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; + +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; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private noteEntityService: NoteEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.notesRepository + .createQueryBuilder('note') + + // Limit to latest notes + .innerJoin(LatestNote, 'latest', 'note.id = latest.note_id') + + // Avoid N+1 queries from the "pack" method + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('note.channel', 'channel') + + // Respect blocks and mutes + .leftJoin(MiBlocking, 'b', 'note."userId" = b."blockerId"') + .leftJoin(MiMuting, 'm', 'note."userId" = m."muteeId"') + .where('b.id IS NULL AND m.id IS NULL') + + // Limit to followers + .innerJoin(MiFollowing, 'following', 'latest.user_id = following."followeeId"') + .andWhere('following."followerId" = :me', { me: me.id }) + + // Support pagination + .orderBy('note.id', 'DESC') + .take(ps.limit); + + // Query and return the next page + const notes = await this.queryService + .makePaginationQuery(query, ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .getMany(); + return await this.noteEntityService.packMany(notes, me); + }); + } +}