From 15f06a1d50c3a092272143a24b310fa11d7f65af 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 0000000000..de2e28c6f2 --- /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 4412972c33..feaf94dcfa 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -12,6 +12,8 @@ export const meta = { requireCredential: false, requireCredentialPrivateMode: true, + 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, @@ -27,7 +29,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' }, }, @@ -37,17 +52,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') @@ -58,7 +63,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, { detail: false }); });