Files
cobble/src/functions/backend/db/bot-files/purge-storage.ts
T
2026-10-21 17:56:09 +01:00

87 lines
3.0 KiB
TypeScript

import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_COBBLE_BOT_FILES } from "@/db/types/db";
import { rmSync } from "node:fs";
import path from "node:path";
import type { BotFileScope } from "@/src/dict/bot-file-dict";
import grabDirNames from "@/src/utils/grab-dir-names";
import { logError } from "@/src/functions/backend/logger";
import grabStorageDir from "./grab-storage-dir";
type Params = {
scope: BotFileScope;
owner_id: number | string;
/**
* Its files' `media` ids, read before the rows go. Left out, they are
* read here (only useful when the catalog rows still exist).
*/
media_ids?: number[];
};
/**
* The `media` ids of a directory's files, read before the bot or workspace
* (and with it, the catalog rows) is deleted.
*/
export async function grabStorageMediaIds({ scope, owner_id }: { scope: BotFileScope; owner_id: number | string }) {
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_BOT_FILES>({
sql: `SELECT media_id FROM bot_files WHERE scope = ? AND owner_id = ?`,
values: [scope, owner_id],
});
return (res.payload || []).map((file) => Number(file.media_id)).filter((id) => id > 0);
}
/**
* Removes a directory's files for good when the bot or workspace it
* belongs to goes: the disk folder, every file's `media` row, and what is
* left of the catalog. A failure is logged, never thrown: deleting the
* bot must not stop on a stray file.
*/
export default async function purgeStorage({ scope, owner_id, media_ids }: Params) {
try {
const ids = media_ids || (await grabStorageMediaIds({ scope, owner_id }));
if (ids.length) {
const placeholders = ids.map(() => "?").join(", ");
await BunSQLite.sql({
sql: `DELETE FROM media_paradigms WHERE media_id IN (${placeholders})`,
values: ids,
}).catch(() => {});
await BunSQLite.sql({
sql: `DELETE FROM media WHERE id IN (${placeholders})`,
values: ids,
}).catch(() => {});
}
await BunSQLite.sql({
sql: `DELETE FROM bot_file_vectors WHERE scope = ? AND owner_id = ?`,
values: [scope, owner_id],
}).catch(() => {});
/**
* Usually gone already, with the bot or workspace (the cascade).
*/
await BunSQLite.sql({
sql: `DELETE FROM bot_files WHERE scope = ? AND owner_id = ?`,
values: [scope, owner_id],
});
const dir = grabStorageDir({ scope, owner_id, create: false });
const { DATA_DIR } = grabDirNames();
/**
* Never outside the data volume, whatever the path says.
*/
if (path.resolve(dir).startsWith(path.resolve(DATA_DIR))) {
rmSync(dir, { recursive: true, force: true });
}
} catch (error) {
await logError({
title: `${scope} ${owner_id}'s files couldn't be cleaned up`,
error,
metadata: { scope, owner_id },
}).catch(() => {});
}
}