get public reactions from scylla
This commit is contained in:
parent
f399a230f3
commit
a4b5ab42bf
5 changed files with 69 additions and 13 deletions
|
@ -151,10 +151,10 @@ export const scyllaQueries = {
|
||||||
("id", "noteId", "userId", "reaction", "emoji", "createdAt")
|
("id", "noteId", "userId", "reaction", "emoji", "createdAt")
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
select: {
|
select: {
|
||||||
byNoteId: `SELECT * FROM reaction_by_id WHERE "noteId" IN ?`,
|
byNoteId: `SELECT * FROM reaction_by_id WHERE "noteId" = ?`,
|
||||||
byUserId: `SELECT * FROM reaction_by_user_id WHERE "userId" IN ?`,
|
byUserId: `SELECT * FROM reaction_by_user_id WHERE "userId" = ?`,
|
||||||
byNoteAndUser: `SELECT * FROM reaction WHERE "noteId" IN ? AND "userId" IN ?`,
|
byNoteAndUser: `SELECT * FROM reaction WHERE "noteId" IN ? AND "userId" IN ?`,
|
||||||
byId: `SELECT * FROM reaction WHERE "id" IN ?`,
|
byId: `SELECT * FROM reaction WHERE "id" = ?`,
|
||||||
},
|
},
|
||||||
delete: `DELETE FROM reaction WHERE "noteId" = ? AND "userId" = ?`,
|
delete: `DELETE FROM reaction WHERE "noteId" = ? AND "userId" = ?`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -262,7 +262,8 @@ export type FeedType =
|
||||||
| "user"
|
| "user"
|
||||||
| "channel"
|
| "channel"
|
||||||
| "notification"
|
| "notification"
|
||||||
| "list";
|
| "list"
|
||||||
|
| "reaction";
|
||||||
|
|
||||||
export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction {
|
export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction {
|
||||||
return {
|
return {
|
||||||
|
@ -310,6 +311,9 @@ export function preparePaginationQuery(
|
||||||
case "notification":
|
case "notification":
|
||||||
queryParts.push(prepared.notification.select.byTargetId);
|
queryParts.push(prepared.notification.select.byTargetId);
|
||||||
break;
|
break;
|
||||||
|
case "reaction":
|
||||||
|
queryParts.push(prepared.reaction.select.byUserId);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
queryParts.push(prepared.note.select.byDate);
|
queryParts.push(prepared.note.select.byDate);
|
||||||
}
|
}
|
||||||
|
@ -359,17 +363,19 @@ export async function execPaginationQuery(
|
||||||
},
|
},
|
||||||
filter?: {
|
filter?: {
|
||||||
note?: (_: ScyllaNote[]) => Promise<ScyllaNote[]>;
|
note?: (_: ScyllaNote[]) => Promise<ScyllaNote[]>;
|
||||||
|
reaction?: (_: ScyllaNoteReaction[]) => Promise<ScyllaNoteReaction[]>;
|
||||||
notification?: (_: ScyllaNotification[]) => ScyllaNotification[];
|
notification?: (_: ScyllaNotification[]) => ScyllaNotification[];
|
||||||
},
|
},
|
||||||
userId?: User["id"],
|
userId?: User["id"],
|
||||||
maxPartitions = config.scylla?.sparseTimelineDays ?? 14,
|
maxPartitions = config.scylla?.sparseTimelineDays ?? 14,
|
||||||
): Promise<ScyllaNote[] | ScyllaNotification[]> {
|
): Promise<ScyllaNote[] | ScyllaNotification[] | ScyllaNoteReaction[]> {
|
||||||
if (!scyllaClient) return [];
|
if (!scyllaClient) return [];
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "home":
|
case "home":
|
||||||
case "user":
|
case "user":
|
||||||
case "notification":
|
case "notification":
|
||||||
|
case "reaction":
|
||||||
if (!userId) throw new Error(`Feed ${kind} needs userId`);
|
if (!userId) throw new Error(`Feed ${kind} needs userId`);
|
||||||
break;
|
break;
|
||||||
case "renotes":
|
case "renotes":
|
||||||
|
@ -387,7 +393,7 @@ export async function execPaginationQuery(
|
||||||
let { query, untilDate, sinceDate } = preparePaginationQuery(kind, ps);
|
let { query, untilDate, sinceDate } = preparePaginationQuery(kind, ps);
|
||||||
|
|
||||||
let scannedPartitions = 0;
|
let scannedPartitions = 0;
|
||||||
const found: (ScyllaNote | ScyllaNotification)[] = [];
|
const found: ScyllaNote[] | ScyllaNotification[] | ScyllaNoteReaction[] = [];
|
||||||
const queryLimit = config.scylla?.queryLimit ?? 1000;
|
const queryLimit = config.scylla?.queryLimit ?? 1000;
|
||||||
let foundLimit = ps.limit;
|
let foundLimit = ps.limit;
|
||||||
if (kind === "list" && ps.userIds) {
|
if (kind === "list" && ps.userIds) {
|
||||||
|
@ -399,7 +405,7 @@ export async function execPaginationQuery(
|
||||||
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);
|
||||||
} else if (kind === "user" && userId) {
|
} else if (["user", "reaction"].includes(kind) && userId) {
|
||||||
params.push(userId, untilDate);
|
params.push(userId, untilDate);
|
||||||
} else if (kind === "renotes" && ps.noteId) {
|
} else if (kind === "renotes" && ps.noteId) {
|
||||||
params.push(ps.noteId, untilDate);
|
params.push(ps.noteId, untilDate);
|
||||||
|
@ -429,15 +435,19 @@ export async function execPaginationQuery(
|
||||||
if (result.rowLength > 0) {
|
if (result.rowLength > 0) {
|
||||||
if (kind === "notification") {
|
if (kind === "notification") {
|
||||||
const notifications = result.rows.map(parseScyllaNotification);
|
const notifications = result.rows.map(parseScyllaNotification);
|
||||||
found.push(
|
(found as ScyllaNotification[]).push(
|
||||||
...(filter?.notification
|
...(filter?.notification
|
||||||
? filter.notification(notifications)
|
? filter.notification(notifications)
|
||||||
: notifications),
|
: notifications),
|
||||||
);
|
);
|
||||||
untilDate = notifications[notifications.length - 1].createdAt;
|
untilDate = notifications[notifications.length - 1].createdAt;
|
||||||
|
} else if (kind === "reaction") {
|
||||||
|
const reactions = result.rows.map(parseScyllaReaction);
|
||||||
|
(found as ScyllaNoteReaction[]).push(...(filter?.reaction ? await filter.reaction(reactions) : reactions));
|
||||||
|
untilDate = reactions[reactions.length - 1].createdAt;
|
||||||
} else {
|
} else {
|
||||||
const notes = result.rows.map(parseScyllaNote);
|
const notes = result.rows.map(parseScyllaNote);
|
||||||
found.push(...(filter?.note ? await filter.note(notes) : notes));
|
(found as ScyllaNote[]).push(...(filter?.note ? await filter.note(notes) : notes));
|
||||||
untilDate = notes[notes.length - 1].createdAt;
|
untilDate = notes[notes.length - 1].createdAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,6 +464,8 @@ export async function execPaginationQuery(
|
||||||
|
|
||||||
if (kind === "notification") {
|
if (kind === "notification") {
|
||||||
return found as ScyllaNotification[];
|
return found as ScyllaNotification[];
|
||||||
|
} else if (kind === "reaction") {
|
||||||
|
return found as ScyllaNoteReaction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
return found as ScyllaNote[];
|
return found as ScyllaNote[];
|
||||||
|
|
|
@ -39,9 +39,9 @@ export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
|
||||||
|
|
||||||
async packMany(
|
async packMany(
|
||||||
src: NoteReaction[],
|
src: NoteReaction[],
|
||||||
me?: { id: User["id"] } | null | undefined,
|
me?: { id: User["id"] } | null,
|
||||||
options?: {
|
options?: {
|
||||||
withNote: booleam;
|
withNote: boolean;
|
||||||
},
|
},
|
||||||
): Promise<Packed<"NoteReaction">[]> {
|
): Promise<Packed<"NoteReaction">[]> {
|
||||||
const reactions = await Promise.allSettled(
|
const reactions = await Promise.allSettled(
|
||||||
|
|
|
@ -73,7 +73,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
let reactions: NoteReaction[] = [];
|
let reactions: NoteReaction[] = [];
|
||||||
if (scyllaClient) {
|
if (scyllaClient) {
|
||||||
const scyllaQuery = [prepared.reaction.select.byNoteId]
|
const scyllaQuery = [prepared.reaction.select.byNoteId]
|
||||||
const params: (string | string[] | number)[] = [[ps.noteId]];
|
const params: (string | string[] | number)[] = [ps.noteId];
|
||||||
if (ps.type) {
|
if (ps.type) {
|
||||||
scyllaQuery.push(`AND "reaction" = ?`);
|
scyllaQuery.push(`AND "reaction" = ?`);
|
||||||
params.push(query.reaction as string)
|
params.push(query.reaction as string)
|
||||||
|
|
|
@ -3,6 +3,15 @@ import define from "../../define.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 { ApiError } from "../../error.js";
|
import { ApiError } from "../../error.js";
|
||||||
|
import {
|
||||||
|
type ScyllaNoteReaction,
|
||||||
|
execPaginationQuery,
|
||||||
|
filterVisibility,
|
||||||
|
parseScyllaNote,
|
||||||
|
prepared,
|
||||||
|
scyllaClient,
|
||||||
|
} from "@/db/scylla.js";
|
||||||
|
import { LocalFollowingsCache } from "@/misc/cache.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["users", "reactions"],
|
tags: ["users", "reactions"],
|
||||||
|
@ -49,10 +58,45 @@ export const paramDef = {
|
||||||
export default define(meta, paramDef, async (ps, me) => {
|
export default define(meta, paramDef, async (ps, me) => {
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
|
const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
|
||||||
|
|
||||||
if (me.id !== ps.userId && !profile.publicReactions) {
|
if (me?.id !== ps.userId && !profile.publicReactions) {
|
||||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scyllaClient) {
|
||||||
|
let followingUserIds: string[] = [];
|
||||||
|
if (me) {
|
||||||
|
followingUserIds = await LocalFollowingsCache.init(me.id).then((cache) =>
|
||||||
|
cache.getAll(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = async (reactions: ScyllaNoteReaction[]) => {
|
||||||
|
if (!scyllaClient) return reactions;
|
||||||
|
let noteIds = reactions.map(({ noteId }) => noteId);
|
||||||
|
if (me) {
|
||||||
|
const notes = await scyllaClient
|
||||||
|
.execute(prepared.note.select.byIds, [noteIds], { prepare: true })
|
||||||
|
.then((result) => result.rows.map(parseScyllaNote));
|
||||||
|
const filteredNoteIds = await filterVisibility(
|
||||||
|
notes,
|
||||||
|
me,
|
||||||
|
followingUserIds,
|
||||||
|
).then((notes) => notes.map(({ id }) => id));
|
||||||
|
noteIds = noteIds.filter((id) => filteredNoteIds.includes(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return reactions.filter((reaction) => noteIds.includes(reaction.noteId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const foundReactions = (await execPaginationQuery(
|
||||||
|
"reaction",
|
||||||
|
ps,
|
||||||
|
{ reaction: filter },
|
||||||
|
ps.userId,
|
||||||
|
)) as ScyllaNoteReaction[];
|
||||||
|
return await NoteReactions.packMany(foundReactions, me, { withNote: true });
|
||||||
|
}
|
||||||
|
|
||||||
const query = makePaginationQuery(
|
const query = makePaginationQuery(
|
||||||
NoteReactions.createQueryBuilder("reaction"),
|
NoteReactions.createQueryBuilder("reaction"),
|
||||||
ps.sinceId,
|
ps.sinceId,
|
||||||
|
|
Loading…
Reference in a new issue