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",
|
|
|
|
"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-30 23:35:34 +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 13:52:35 +02:00
|
|
|
byId: `SELECT * FROM note WHERE "id" IN ?`,
|
2023-07-24 18:23:57 +02:00
|
|
|
byUri: `SELECT * FROM note WHERE "uri" IN ?`,
|
|
|
|
byUrl: `SELECT * FROM note WHERE "url" IN ?`,
|
|
|
|
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-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 {
|
|
|
|
const files: ScyllaDriveFile[] = row.get("files");
|
|
|
|
return {
|
|
|
|
createdAtDate: row.get("createdAtDate"),
|
|
|
|
createdAt: row.get("createdAt"),
|
|
|
|
id: row.get("id"),
|
|
|
|
visibility: row.get("visibility"),
|
|
|
|
text: row.get("content"),
|
|
|
|
name: row.get("name"),
|
|
|
|
cw: row.get("cw"),
|
|
|
|
localOnly: row.get("localOnly"),
|
|
|
|
renoteCount: row.get("renoteCount"),
|
|
|
|
repliesCount: row.get("repliesCount"),
|
|
|
|
uri: row.get("uri"),
|
|
|
|
url: row.get("url"),
|
|
|
|
score: row.get("score"),
|
|
|
|
files,
|
|
|
|
fileIds: files.map((file) => file.id),
|
|
|
|
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"),
|
|
|
|
threadId: row.get("threadId"),
|
|
|
|
channelId: row.get("channelId"),
|
|
|
|
userId: row.get("userId"),
|
2023-07-27 03:58:38 +02:00
|
|
|
userHost: row.get("userHost"),
|
2023-07-25 13:28:08 +02:00
|
|
|
replyId: row.get("replyId"),
|
2023-07-27 03:58:38 +02:00
|
|
|
replyUserId: row.get("replyUserId"),
|
|
|
|
replyUserHost: row.get("replyUserHost"),
|
2023-07-25 13:28:08 +02:00
|
|
|
renoteId: row.get("replyId"),
|
2023-07-27 03:58:38 +02:00
|
|
|
renoteUserId: row.get("renoteUserId"),
|
|
|
|
renoteUserHost: row.get("renoteUserHost"),
|
2023-07-25 13:28:08 +02:00
|
|
|
reactions: row.get("reactions"),
|
|
|
|
noteEdit: row.get("noteEdit"),
|
|
|
|
updatedAt: row.get("updatedAt"),
|
2023-07-27 00:59:23 +02:00
|
|
|
/* unused postgres denormalization */
|
|
|
|
channel: null,
|
|
|
|
renote: null,
|
|
|
|
reply: null,
|
|
|
|
mentionedRemoteUsers: "",
|
|
|
|
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;
|
|
|
|
}
|