enable to fetch replies recursively

This commit is contained in:
Johann150 2022-07-24 12:07:29 +02:00 committed by ThatOneCalculator
parent 86342538aa
commit 15f06a1d50
2 changed files with 70 additions and 13 deletions

View file

@ -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`);
}
}

View file

@ -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 });
});