Make document deletion durable before physical file cleanup
This commit is contained in:
+29
-7
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user