83 lines
2.6 KiB
TypeScript
83 lines
2.6 KiB
TypeScript
/**
|
|
* Cleans up all S3 storage artifacts belonging to a user.
|
|
* Called from Better Auth's `beforeDelete` hook so that blobs are removed
|
|
* before the DB cascade wipes the metadata rows we query against.
|
|
*/
|
|
|
|
import { db } from '@/db';
|
|
import { documents, audiobooks } from '@/db/schema';
|
|
import { eq } from 'drizzle-orm';
|
|
import { isS3Configured } from '@/lib/server/storage/s3';
|
|
import { deleteDocumentBlob } from '@/lib/server/documents/blobstore';
|
|
import { deleteDocumentPreviewArtifacts } from '@/lib/server/documents/previews-blobstore';
|
|
import { audiobookPrefix, deleteAudiobookPrefix } from '@/lib/server/audiobooks/blobstore';
|
|
|
|
type DocumentRow = { id: string };
|
|
type AudiobookRow = { id: string };
|
|
|
|
/**
|
|
* Delete all S3 blobs owned by `userId`.
|
|
*
|
|
* This covers:
|
|
* - Document file blobs
|
|
* - Document preview images
|
|
* - Audiobook audio files (chapter mp3s, metadata json, etc.)
|
|
*
|
|
* Each item is cleaned up independently; a failure on one does not block the rest.
|
|
*/
|
|
export async function deleteUserStorageData(
|
|
userId: string,
|
|
namespace: string | null,
|
|
): Promise<void> {
|
|
if (!isS3Configured()) return;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const database = db as any;
|
|
|
|
// --- Documents & previews ---
|
|
const userDocs: DocumentRow[] = await database
|
|
.select({ id: documents.id })
|
|
.from(documents)
|
|
.where(eq(documents.userId, userId));
|
|
|
|
let docsDeleted = 0;
|
|
for (const doc of userDocs) {
|
|
try {
|
|
await deleteDocumentBlob(doc.id, namespace);
|
|
docsDeleted++;
|
|
} catch (error) {
|
|
console.error(`[user-data-cleanup] Failed to delete document blob ${doc.id}:`, error);
|
|
}
|
|
|
|
try {
|
|
await deleteDocumentPreviewArtifacts(doc.id, namespace);
|
|
} catch (error) {
|
|
console.error(`[user-data-cleanup] Failed to delete preview for ${doc.id}:`, error);
|
|
}
|
|
}
|
|
|
|
// --- Audiobooks ---
|
|
const userBooks: AudiobookRow[] = await database
|
|
.select({ id: audiobooks.id })
|
|
.from(audiobooks)
|
|
.where(eq(audiobooks.userId, userId));
|
|
|
|
let booksDeleted = 0;
|
|
for (const book of userBooks) {
|
|
try {
|
|
const prefix = audiobookPrefix(book.id, userId, namespace);
|
|
await deleteAudiobookPrefix(prefix);
|
|
booksDeleted++;
|
|
} catch (error) {
|
|
console.error(`[user-data-cleanup] Failed to delete audiobook blobs ${book.id}:`, error);
|
|
}
|
|
}
|
|
|
|
if (docsDeleted > 0 || booksDeleted > 0) {
|
|
console.log(
|
|
`[user-data-cleanup] Cleaned up S3 data for user ${userId}: ` +
|
|
`${docsDeleted}/${userDocs.length} document(s), ` +
|
|
`${booksDeleted}/${userBooks.length} audiobook(s)`,
|
|
);
|
|
}
|
|
}
|