forked from FoundKeyGang/FoundKey
enable to fetch replies recursively
This commit is contained in:
parent
2fe64c1150
commit
aca724e0bf
2 changed files with 70 additions and 13 deletions
packages/backend
|
@ -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`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue