enable to fetch replies recursively

This commit is contained in:
Johann150 2022-07-24 12:07:29 +02:00
parent 2fe64c1150
commit aca724e0bf
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
2 changed files with 70 additions and 13 deletions
packages/backend
migration
src/server/api/endpoints/notes

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

@ -11,6 +11,8 @@ export const meta = {
requireCredential: false, 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: { res: {
type: 'array', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
@ -26,7 +28,20 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
noteId: { type: 'string', format: 'misskey:id' }, 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' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { 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 // eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere(new Brackets(qb => { qb .andWhere('note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))', { noteId: ps.noteId, depth: ps.depth, limit: ps.limit })
.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');
}));
}));
}))
.innerJoinAndSelect('note.user', 'user') .innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('user.banner', 'banner')
@ -65,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
generateBlockedUserQuery(query, user); generateBlockedUserQuery(query, user);
} }
const notes = await query.take(ps.limit).getMany(); const notes = await query.getMany();
return await Notes.packMany(notes, user); return await Notes.packMany(notes, user);
}); });