diff --git a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql index a02bf67351..8157108527 100644 --- a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql +++ b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/down.cql @@ -2,6 +2,7 @@ DROP MATERIALIZED VIEW IF EXISTS reaction_by_id; DROP MATERIALIZED VIEW IF EXISTS reaction_by_userid; DROP INDEX IF EXISTS reaction_by_id; DROP TABLE IF EXISTS reaction; +DROP TABLE IF EXISTS deleted_note; DROP TABLE IF EXISTS home_timeline; DROP MATERIALIZED VIEW IF EXISTS local_timeline; DROP MATERIALIZED VIEW IF EXISTS global_timeline; diff --git a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql index 99603a3fa4..2071a26ae8 100644 --- a/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql +++ b/packages/backend/native-utils/scylla-migration/cql/1689400417034_timeline/up.cql @@ -168,9 +168,15 @@ CREATE TABLE IF NOT EXISTS home_timeline ( "reactions" map, -- Reactions "noteEdit" set>, -- Edit History "updatedAt" timestamp, - PRIMARY KEY (("feedUserId", "createdAtDate"), "createdAt") + PRIMARY KEY (("feedUserId", "createdAtDate"), "createdAt", "userId") ) WITH CLUSTERING ORDER BY ("createdAt" DESC); +CREATE TABLE IF NOT EXISTS deleted_note ( + "noteId" ascii, + "deletedAt" timestamp, + PRIMARY KEY ("noteId", "deletedAt") +); + CREATE TABLE IF NOT EXISTS reaction ( "id" text, "noteId" ascii, diff --git a/packages/backend/src/db/cql.ts b/packages/backend/src/db/cql.ts index 96f8e0c17b..4d33ed238d 100644 --- a/packages/backend/src/db/cql.ts +++ b/packages/backend/src/db/cql.ts @@ -49,7 +49,7 @@ export const scyllaQueries = { byUserId: `SELECT * FROM note_by_user_id WHERE "userId" IN ?`, byRenoteId: `SELECT * FROM note_by_renote_id WHERE "renoteId" = ?`, }, - delete: `DELETE FROM note WHERE "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ?`, + delete: `DELETE FROM note WHERE "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? AND "userHost" = ? AND "visibility" = ?`, update: { renoteCount: `UPDATE note SET "renoteCount" = ?, @@ -122,6 +122,10 @@ export const scyllaQueries = { byDate: `SELECT * FROM global_timeline WHERE "createdAtDate" = ?`, }, }, + deletedNote: { + insert: `INSERT INTO deleted_note ("noteId", "deletedAt") VALUES (?, ?)`, + select: `SELECT "noteId" FROM deleted_note`, + }, reaction: { insert: `INSERT INTO reaction ("id", "noteId", "userId", "reaction", "emoji", "createdAt") diff --git a/packages/backend/src/db/scylla.ts b/packages/backend/src/db/scylla.ts index 2f6256896f..3ee2b181da 100644 --- a/packages/backend/src/db/scylla.ts +++ b/packages/backend/src/db/scylla.ts @@ -155,13 +155,30 @@ export function parseScyllaNote(row: types.Row): ScyllaNote { }; } +export interface DeletedNote { + noteId: string; + deletedAt: string; +} + +export function parseDeletedNote(row: types.Row): DeletedNote { + return { + noteId: row.get("noteId"), + deletedAt: row.get("deletedAt"), + }; +} + export interface ScyllaNoteReaction extends NoteReaction { emoji: PopulatedEmoji; } const QUERY_LIMIT = 1000; // TODO: should this be configurable? -export type TimelineKind = "home" | "local" | "recommended" | "global" | "renotes"; +export type TimelineKind = + | "home" + | "local" + | "recommended" + | "global" + | "renotes"; export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction { return { @@ -177,16 +194,17 @@ export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction { export function prepareNoteQuery( kind: TimelineKind, ps: { - untilId?: string; - untilDate?: number; - sinceId?: string; - sinceDate?: number; -}): { query: string; untilDate: Date; sinceDate: Date | null } { + untilId?: string; + untilDate?: number; + sinceId?: string; + sinceDate?: number; + }, +): { query: string; untilDate: Date; sinceDate: Date | null } { const queryParts: string[] = []; switch (kind) { case "home": - queryParts.push(prepared.homeTimeline.select.byUserAndDate) + queryParts.push(prepared.homeTimeline.select.byUserAndDate); break; case "local": queryParts.push(prepared.localTimeline.select.byDate); @@ -283,7 +301,18 @@ export async function execNotePaginationQuery( if (result.rowLength > 0) { const notes = result.rows.map(parseScyllaNote); - foundNotes.push(...(filter ? await filter(notes) : notes)); + const candidates = filter ? await filter(notes) : notes; + // foundNotes.push(...(filter ? await filter(notes) : notes)); + const deletedNotes = await scyllaClient + .execute(`${prepared.deletedNote.select} WHERE "noteId" IN ?`, [ + candidates.map(({ id }) => id), + ]) + .then((result) => + result.rows.map((row) => parseDeletedNote(row).noteId), + ); + foundNotes.push( + ...candidates.filter((note) => !deletedNotes.includes(note.id)), + ); untilDate = notes[notes.length - 1].createdAt; } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index 4ddf6ec85c..543de13312 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -119,9 +119,17 @@ export default async function ( if (scyllaClient) { const date = new Date(note.createdAt.getTime()); - await scyllaClient.execute(prepared.note.delete, [date, date, note.userId], { - prepare: true, - }); + await scyllaClient.execute( + prepared.note.delete, + [date, date, note.userId, note.userHost ?? "local", note.visibility], + { + prepare: true, + }, + ); + await scyllaClient.execute(prepared.deletedNote.insert, [ + note.id, + new Date(), + ]); } await Notes.delete({