add list timeline

This commit is contained in:
Namekuji 2023-08-25 15:05:22 -04:00
parent f57a27c5ed
commit 5493fefe0f
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
2 changed files with 70 additions and 14 deletions

View file

@ -259,7 +259,8 @@ export type FeedType =
| "renotes" | "renotes"
| "user" | "user"
| "channel" | "channel"
| "notification"; | "notification"
| "list";
export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction { export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction {
return { return {
@ -298,6 +299,7 @@ export function preparePaginationQuery(
queryParts.push(prepared.note.select.byRenoteId); queryParts.push(prepared.note.select.byRenoteId);
break; break;
case "user": case "user":
case "list":
queryParts.push(prepared.note.select.byUserId); queryParts.push(prepared.note.select.byUserId);
break; break;
case "channel": case "channel":
@ -330,8 +332,7 @@ export function preparePaginationQuery(
queryParts.push(`AND "createdAt" > ?`); queryParts.push(`AND "createdAt" > ?`);
} }
const queryLimit = config.scylla?.queryLimit ?? 1000; queryParts.push("LIMIT ?");
queryParts.push(`LIMIT ${queryLimit}`);
const query = queryParts.join(" "); const query = queryParts.join(" ");
@ -352,6 +353,7 @@ export async function execPaginationQuery(
sinceDate?: number; sinceDate?: number;
noteId?: string; noteId?: string;
channelId?: string; channelId?: string;
userIds?: string[];
}, },
filter?: { filter?: {
note?: (_: ScyllaNote[]) => Promise<ScyllaNote[]>; note?: (_: ScyllaNote[]) => Promise<ScyllaNote[]>;
@ -374,6 +376,10 @@ export async function execPaginationQuery(
case "channel": case "channel":
if (!ps.channelId) throw new Error(`Feed ${kind} needs channelId`); if (!ps.channelId) throw new Error(`Feed ${kind} needs channelId`);
break; break;
case "list":
if (!ps.userIds || ps.userIds.length === 0)
throw new Error(`Feed ${kind} needs userIds`);
break;
} }
let { query, untilDate, sinceDate } = preparePaginationQuery(kind, ps); let { query, untilDate, sinceDate } = preparePaginationQuery(kind, ps);
@ -381,9 +387,13 @@ export async function execPaginationQuery(
let scannedPartitions = 0; let scannedPartitions = 0;
const found: (ScyllaNote | ScyllaNotification)[] = []; const found: (ScyllaNote | ScyllaNotification)[] = [];
const queryLimit = config.scylla?.queryLimit ?? 1000; const queryLimit = config.scylla?.queryLimit ?? 1000;
let foundLimit = ps.limit;
if (kind === "list" && ps.userIds) {
foundLimit *= ps.userIds.length;
}
// Try to get posts of at most <maxPartitions> in the single request // Try to get posts of at most <maxPartitions> in the single request
while (found.length < ps.limit && scannedPartitions < maxPartitions) { while (found.length < foundLimit && scannedPartitions < maxPartitions) {
const params: (Date | string | string[] | number)[] = []; const params: (Date | string | string[] | number)[] = [];
if (kind === "home" && userId) { if (kind === "home" && userId) {
params.push(userId, untilDate, untilDate); params.push(userId, untilDate, untilDate);
@ -395,6 +405,8 @@ export async function execPaginationQuery(
params.push(ps.channelId, untilDate); params.push(ps.channelId, untilDate);
} else if (kind === "notification" && userId) { } else if (kind === "notification" && userId) {
params.push(userId, untilDate, untilDate); params.push(userId, untilDate, untilDate);
} else if (kind === "list" && ps.userIds) {
params.push(ps.userIds.pop() as string, untilDate);
} else { } else {
params.push(untilDate, untilDate); params.push(untilDate, untilDate);
} }
@ -403,6 +415,8 @@ export async function execPaginationQuery(
params.push(sinceDate); params.push(sinceDate);
} }
params.push(kind === "list" ? ps.limit : queryLimit);
const result = await scyllaClient.execute(query, params, { const result = await scyllaClient.execute(query, params, {
prepare: true, prepare: true,
}); });

View file

@ -5,6 +5,13 @@ import define from "../../define.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
import {
type ScyllaNote,
scyllaClient,
filterVisibility,
execPaginationQuery,
} from "@/db/scylla.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
export const meta = { export const meta = {
tags: ["notes", "lists"], tags: ["notes", "lists"],
@ -64,10 +71,54 @@ export default define(meta, paramDef, async (ps, user) => {
userId: user.id, userId: user.id,
}); });
if (list == null) { if (!list) {
throw new ApiError(meta.errors.noSuchList); throw new ApiError(meta.errors.noSuchList);
} }
if (scyllaClient) {
const followingUserIds = await LocalFollowingsCache.init(user.id).then(
(cache) => cache.getAll(),
);
const optFilter = (n: ScyllaNote) =>
!n.renoteId || !!n.text || n.files.length > 0 || n.hasPoll;
const filter = async (notes: ScyllaNote[]) => {
let filtered = await filterVisibility(notes, user, followingUserIds);
if (!ps.includeMyRenotes) {
filtered = filtered.filter((n) => n.userId !== user.id || optFilter(n));
}
if (!ps.includeRenotedMyNotes) {
filtered = filtered.filter(
(n) => n.renoteUserId !== user.id || optFilter(n),
);
}
if (!ps.includeLocalRenotes) {
filtered = filtered.filter((n) => n.renoteUserHost || optFilter(n));
}
if (ps.withFiles) {
filtered = filtered.filter((n) => n.files.length > 0);
}
filtered = filtered.filter((n) => n.visibility !== "hidden");
return filtered;
};
const foundPacked = [];
while (foundPacked.length < ps.limit) {
const foundNotes = (
(await execPaginationQuery(
"list",
ps,
{ note: filter },
user.id,
)) as ScyllaNote[]
).slice(0, ps.limit * 1.5); // Some may filtered out by Notes.packMany, thus we take more than ps.limit.
foundPacked.push(...(await Notes.packMany(foundNotes, user)));
if (foundNotes.length < ps.limit) break;
ps.untilDate = foundNotes[foundNotes.length - 1].createdAt.getTime();
}
return foundPacked.slice(0, ps.limit);
}
//#region Construct query //#region Construct query
const query = makePaginationQuery( const query = makePaginationQuery(
Notes.createQueryBuilder("note"), Notes.createQueryBuilder("note"),
@ -79,17 +130,8 @@ export default define(meta, paramDef, async (ps, user) => {
"userListJoining", "userListJoining",
"userListJoining.userId = note.userId", "userListJoining.userId = note.userId",
) )
.innerJoinAndSelect("note.user", "user")
.leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner")
.leftJoinAndSelect("note.reply", "reply") .leftJoinAndSelect("note.reply", "reply")
.leftJoinAndSelect("note.renote", "renote") .leftJoinAndSelect("note.renote", "renote")
.leftJoinAndSelect("reply.user", "replyUser")
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
.leftJoinAndSelect("renote.user", "renoteUser")
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
.andWhere("userListJoining.userListId = :userListId", { .andWhere("userListJoining.userListId = :userListId", {
userListId: list.id, userListId: list.id,
}); });