Make document deletion durable before physical file cleanup

This commit is contained in:
2026-09-06 10:44:47 +03:00
parent fa56e0fe83
commit 3b40061d96
5 changed files with 81 additions and 13 deletions
+29 -7
View File
@@ -116,12 +116,16 @@ export function openDb() {
.run('20260906_document_processing', new Date().toISOString());
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)')
.run('20260906_document_text_reviews', new Date().toISOString());
db.exec('CREATE TABLE IF NOT EXISTS document_file_cleanup (dosya TEXT PRIMARY KEY, queued_at TEXT NOT NULL)');
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)').run('20260906_document_file_cleanup', now());
db.exec('COMMIT');
} catch (error) {
try { db.exec('ROLLBACK'); } catch { /* original error is authoritative */ }
db.close();
throw error;
}
// Cleanup is recoverable maintenance; a failure cannot undo committed deletion.
try { cleanupDocumentFiles(db); } catch { /* durable queue retries on the next database open */ }
return db;
}
@@ -334,18 +338,36 @@ export function updateDocument(email, id, patch = {}) {
export function listUsers() {
return withDb((db) => db.prepare('SELECT email, ad FROM kullanicilar').all(), []);
}
function cleanupDocumentFiles(db) {
db.exec('BEGIN IMMEDIATE');
try {
for (const row of db.prepare('SELECT dosya FROM document_file_cleanup ORDER BY queued_at LIMIT 100').all()) {
// Legacy records may share a path. Never remove a still-referenced file.
if (db.prepare('SELECT 1 FROM belgeler WHERE dosya=? LIMIT 1').get(row.dosya)) continue;
const destination = path.resolve(BELGE_DIR,row.dosya);
if (path.dirname(destination) !== path.resolve(BELGE_DIR)) continue;
try { fs.unlinkSync(destination); }
catch (error) { if (error.code !== 'ENOENT') continue; }
db.prepare('DELETE FROM document_file_cleanup WHERE dosya=?').run(row.dosya);
}
db.exec('COMMIT');
} catch (error) { db.exec('ROLLBACK'); throw error; }
}
export function deleteDocument(email, id) {
const rec = getDocument(email, id);
if (!rec) return false;
try { fs.unlinkSync(path.join(BELGE_DIR, rec.dosya)); }
catch (error) { if (error.code !== 'ENOENT') throw error; }
return withDb((db) => {
db.exec('BEGIN IMMEDIATE');
try {
const rec = db.prepare('SELECT dosya FROM belgeler WHERE id=? AND email=?').get(id,email);
if (!rec) { db.exec('COMMIT'); return false; }
db.prepare('INSERT OR IGNORE INTO document_file_cleanup (dosya,queued_at) VALUES (?,?)').run(rec.dosya,now());
db.prepare('DELETE FROM document_text_reviews WHERE document_id=? AND email=?').run(id,email);
const changed=db.prepare('DELETE FROM belgeler WHERE id=? AND email=?').run(id,email).changes>0;
db.exec('COMMIT');return changed;
} catch(error) {db.exec('ROLLBACK');throw error;}
db.prepare('DELETE FROM belgeler WHERE id=? AND email=?').run(id,email);
db.exec('COMMIT');
} catch(error) { db.exec('ROLLBACK'); throw error; }
// The document is now inaccessible. Unlink only after the durable commit.
try { cleanupDocumentFiles(db); } catch { /* retry through the durable queue */ }
return true;
});
}