From aca724e0bfff3e58b4d273f3ee744e3f3aa9c39b Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 24 Jul 2022 12:07:29 +0200 Subject: [PATCH] enable to fetch replies recursively --- .../1658656633972-note-replies-function.js | 52 +++++++++++++++++++ .../server/api/endpoints/notes/children.ts | 31 ++++++----- 2 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 packages/backend/migration/1658656633972-note-replies-function.js diff --git a/packages/backend/migration/1658656633972-note-replies-function.js b/packages/backend/migration/1658656633972-note-replies-function.js new file mode 100644 index 000000000..de2e28c6f --- /dev/null +++ b/packages/backend/migration/1658656633972-note-replies-function.js @@ -0,0 +1,52 @@ +export class noteRepliesFunction1658656633972 { + name = 'noteRepliesFunction1658656633972' + + async up(queryRunner) { + await queryRunner.query(` + CREATE OR REPLACE FUNCTION note_replies(start_id varchar, max_depth integer, max_breadth integer) RETURNS TABLE (id VARCHAR) AS + $$ + SELECT DISTINCT id FROM ( + WITH RECURSIVE tree (id, ancestors, depth) AS ( + SELECT start_id, '{}'::VARCHAR[], 0 + UNION + SELECT + note.id, + CASE + WHEN note."replyId" = tree.id THEN tree.ancestors || note."replyId" + ELSE tree.ancestors || note."renoteId" + END, + depth + 1 + FROM note, tree + WHERE ( + note."replyId" = tree.id + OR + ( + -- get renotes but not pure renotes + note."renoteId" = tree.id + AND + ( + note.text IS NOT NULL + OR + CARDINALITY(note."fileIds") != 0 + OR + note."hasPoll" = TRUE + ) + ) + ) AND depth < max_depth + ) + SELECT + id, + -- apply the limit per node + row_number() OVER (PARTITION BY ancestors[array_upper(ancestors, 1)]) AS nth_child + FROM tree + WHERE depth > 0 + ) AS recursive WHERE nth_child < max_breadth + $$ + LANGUAGE SQL + `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP FUNCTION note_replies`); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index efc109105..8580d8756 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: false, + description: 'Get a list of children of a notes. Children includes replies as well as quote renotes that quote the respective post. A post will not be duplicated if it is a reply and a quote of a note in this thread. For depths larger than 1 the threading has to be computed by the client.', + res: { type: 'array', optional: false, nullable: false, @@ -26,7 +28,20 @@ export const paramDef = { type: 'object', properties: { noteId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + limit: { + description: 'The maximum number of replies/quotes to show per parent note, i.e. the maximum number of children each note may have.', + type: 'integer', + minimum: 1, + maximum: 100, + default: 10, + }, + depth: { + description: 'The number of layers of replies to fetch at once. Defaults to 1 for backward compatibility.', + type: 'integer', + minimum: 1, + maximum: 100, + default: 1, + }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, }, @@ -36,17 +51,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where('note.replyId = :noteId', { noteId: ps.noteId }) - .orWhere(new Brackets(qb => { qb - .where('note.renoteId = :noteId', { noteId: ps.noteId }) - .andWhere(new Brackets(qb => { qb - .where('note.text IS NOT NULL') - .orWhere('note.fileIds != \'{}\'') - .orWhere('note.hasPoll = TRUE'); - })); - })); - })) + .andWhere('note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))', { noteId: ps.noteId, depth: ps.depth, limit: ps.limit }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') @@ -65,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => { generateBlockedUserQuery(query, user); } - const notes = await query.take(ps.limit).getMany(); + const notes = await query.getMany(); return await Notes.packMany(notes, user); });