hippofish/packages/backend/src/db/scylla.ts

212 lines
5.6 KiB
TypeScript
Raw Normal View History

2023-07-19 16:35:47 +02:00
import config from "@/config/index.js";
2023-07-24 18:23:57 +02:00
import type { PopulatedEmoji } from "@/misc/populate-emojis.js";
2023-07-25 13:28:08 +02:00
import type { Note } from "@/models/entities/note.js";
2023-07-24 18:23:57 +02:00
import type { NoteReaction } from "@/models/entities/note-reaction.js";
import { Client, types } from "cassandra-driver";
2023-07-27 13:15:58 +02:00
import type { User } from "@/models/entities/user.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
2023-07-19 16:35:47 +02:00
function newClient(): Client | null {
if (!config.scylla) {
return null;
}
return new Client({
contactPoints: config.scylla.nodes,
2023-07-24 11:35:44 +02:00
localDataCenter: config.scylla.localDataCentre,
2023-07-19 16:35:47 +02:00
keyspace: config.scylla.keyspace,
});
}
export const scyllaClient = newClient();
export const prepared = {
2023-07-24 11:35:44 +02:00
note: {
2023-07-19 16:35:47 +02:00
insert: `INSERT INTO note (
2023-07-24 11:35:44 +02:00
"createdAtDate",
"createdAt",
"id",
"visibility",
"content",
"name",
"cw",
"localOnly",
"renoteCount",
"repliesCount",
"uri",
"url",
"score",
"files",
2023-07-25 13:28:08 +02:00
"visibleUserIds",
2023-07-24 11:35:44 +02:00
"mentions",
2023-07-31 03:41:45 +02:00
"mentionedRemoteUsers",
2023-07-24 11:35:44 +02:00
"emojis",
"tags",
"hasPoll",
"threadId",
"channelId",
"userId",
2023-07-27 03:58:38 +02:00
"userHost",
2023-07-24 11:35:44 +02:00
"replyId",
2023-07-27 03:58:38 +02:00
"replyUserId",
"replyUserHost",
2023-07-24 11:35:44 +02:00
"renoteId",
2023-07-27 03:58:38 +02:00
"renoteUserId",
"renoteUserHost",
2023-07-24 11:35:44 +02:00
"reactions",
"noteEdit",
"updatedAt"
2023-07-19 16:35:47 +02:00
)
VALUES
2023-07-31 03:41:45 +02:00
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2023-07-19 16:35:47 +02:00
select: {
2023-07-30 23:35:34 +02:00
byDate: `SELECT * FROM note WHERE "createdAtDate" = ?`,
2023-07-24 18:23:57 +02:00
byUri: `SELECT * FROM note WHERE "uri" IN ?`,
byUrl: `SELECT * FROM note WHERE "url" IN ?`,
2023-07-31 03:41:45 +02:00
byId: `SELECT * FROM note_by_id WHERE "id" IN ?`,
2023-07-24 18:23:57 +02:00
byUserId: `SELECT * FROM note_by_userid WHERE "userId" IN ?`,
2023-07-19 16:35:47 +02:00
},
2023-07-25 13:28:08 +02:00
delete: `DELETE FROM note WHERE "createdAtDate" = ? AND "createdAt" = ? AND "id" = ?`,
2023-07-24 11:35:44 +02:00
update: {
2023-07-25 13:28:08 +02:00
renoteCount: `UPDATE note SET
"renoteCount" = ?,
"score" = ?
WHERE "createdAtDate" = ? AND "createdAt" = ? AND "id" = ? IF EXISTS`,
reactions: `UPDATE note SET
"emojis" = ?,
"reactions" = ?,
"score" = ?
WHERE "createdAtDate" = ? AND "createdAt" = ? AND "id" = ? IF EXISTS`,
2023-07-24 13:52:35 +02:00
},
2023-07-19 16:35:47 +02:00
},
2023-07-24 11:35:44 +02:00
reaction: {
2023-07-24 13:52:35 +02:00
insert: `INSERT INTO reaction
("id", "noteId", "userId", "reaction", "emoji", "createdAt")
VALUES (?, ?, ?, ?, ?, ?)`,
2023-07-24 18:23:57 +02:00
select: {
byNoteId: `SELECT * FROM reaction WHERE "noteId" IN ?`,
byUserId: `SELECT * FROM reaction_by_userid WHERE "userId" IN ?`,
byNoteAndUser: `SELECT * FROM reaction WHERE "noteId" = ? AND "userId" = ?`,
byId: `SELECT * FROM reaction WHERE "id" IN ?`,
},
delete: `DELETE FROM reaction WHERE "noteId" = ? AND "userId" = ?`,
2023-07-24 11:35:44 +02:00
},
};
2023-07-24 04:29:38 +02:00
export interface ScyllaDriveFile {
id: string;
type: string;
createdAt: Date;
name: string;
comment: string | null;
blurhash: string | null;
url: string;
thumbnailUrl: string;
isSensitive: boolean;
isLink: boolean;
md5: string;
size: number;
2023-07-31 03:41:45 +02:00
width: number | null;
height: number | null;
2023-07-24 04:29:38 +02:00
}
2023-07-24 18:23:57 +02:00
2023-07-25 13:28:08 +02:00
export interface ScyllaNoteEditHistory {
content: string;
cw: string;
files: ScyllaDriveFile[];
updatedAt: Date;
}
2023-07-27 00:59:23 +02:00
export type ScyllaNote = Note & {
2023-07-25 13:28:08 +02:00
createdAtDate: Date;
files: ScyllaDriveFile[];
noteEdit: ScyllaNoteEditHistory[];
};
export function parseScyllaNote(row: types.Row): ScyllaNote {
2023-07-31 03:41:45 +02:00
const files: ScyllaDriveFile[] = row.get("files") ?? [];
2023-07-25 13:28:08 +02:00
return {
createdAtDate: row.get("createdAtDate"),
createdAt: row.get("createdAt"),
id: row.get("id"),
visibility: row.get("visibility"),
2023-07-31 03:41:45 +02:00
text: row.get("content") ?? null,
name: row.get("name") ?? null,
cw: row.get("cw") ?? null,
2023-07-25 13:28:08 +02:00
localOnly: row.get("localOnly"),
renoteCount: row.get("renoteCount"),
repliesCount: row.get("repliesCount"),
2023-07-31 03:41:45 +02:00
uri: row.get("uri") ?? null,
url: row.get("url") ?? null,
2023-07-25 13:28:08 +02:00
score: row.get("score"),
files,
fileIds: files.map((file) => file.id),
2023-07-31 03:41:45 +02:00
attachedFileTypes: files.map((file) => file.type) ?? [],
visibleUserIds: row.get("visibleUserIds") ?? [],
mentions: row.get("mentions") ?? [],
emojis: row.get("emojis") ?? [],
tags: row.get("tags") ?? [],
hasPoll: row.get("hasPoll") ?? false,
threadId: row.get("threadId") ?? null,
channelId: row.get("channelId") ?? null,
2023-07-25 13:28:08 +02:00
userId: row.get("userId"),
2023-07-31 03:41:45 +02:00
userHost: row.get("userHost") ?? null,
replyId: row.get("replyId") ?? null,
replyUserId: row.get("replyUserId") ?? null,
replyUserHost: row.get("replyUserHost") ?? null,
renoteId: row.get("renoteId") ?? null,
renoteUserId: row.get("renoteUserId") ?? null,
renoteUserHost: row.get("renoteUserHost") ?? null,
reactions: row.get("reactions") ?? {},
noteEdit: row.get("noteEdit") ?? [],
updatedAt: row.get("updatedAt") ?? null,
mentionedRemoteUsers: row.get("mentionedRemoteUsers") ?? "[]",
2023-07-27 00:59:23 +02:00
/* unused postgres denormalization */
channel: null,
renote: null,
reply: null,
user: null,
};
2023-07-25 13:28:08 +02:00
}
export interface ScyllaNoteReaction extends NoteReaction {
emoji: PopulatedEmoji;
2023-07-24 18:23:57 +02:00
}
export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction {
return {
id: row.get("id"),
noteId: row.get("noteId"),
userId: row.get("userId"),
reaction: row.get("reaction"),
createdAt: row.get("createdAt"),
emoji: row.get("emoji"),
2023-07-25 13:28:08 +02:00
};
2023-07-24 18:23:57 +02:00
}
2023-07-27 13:15:58 +02:00
export async function isVisible(
note: ScyllaNote,
user: { id: User["id"] } | null,
): Promise<boolean> {
let visible = false;
if (
["public", "home"].includes(note.visibility) // public post
) {
visible = true;
} else if (user) {
const cache = await LocalFollowingsCache.init(user.id);
visible =
note.userId === user.id || // my own post
note.visibleUserIds.includes(user.id) || // visible to me
note.mentions.includes(user.id) || // mentioned me
(note.visibility === "followers" &&
(await cache.isFollowing(note.userId))) || // following
note.replyUserId === user.id; // replied to myself
}
return visible;
}