Refactor case store to support deletions

Introduce `reconcile_with_server_snapshot` to the `CaseStore`. This
function
removes local markers that are synced to the server and within the
server's reported window but are no longer present in the server's
snapshot. This ensures that cases deleted server-side are also removed
locally, fixing an issue where deleted cases would still appear on the
client.

This change also modifies the `poll_once` function in `server_sync`
to call both `merge_server_snapshot` and the new
`reconcile_with_server_snapshot` after a successful poll.

Additionally, a test case `poll_once_reconciles_missing_synced_marker`
has been added to verify the correct behavior of the reconciliation
process.

The server-side upload handler is updated to automatically reopen
soft-deleted cases if a new upload is received for them. This ensures
that soft-deleted cases reappear in the API and UI without manual
intervention. Two new tests, `upload_reopens_soft_deleted_case` and
`upload_resurrects_hard_deleted_case`, are added to cover the
soft-delete
reopening and hard-delete resurrection scenarios, respectively.
This commit is contained in:
2026-04-19 13:43:26 +02:00
parent 5d308df2b5
commit 5e8d2f8e58
4 changed files with 425 additions and 7 deletions
+13
View File
@@ -72,6 +72,19 @@ pub async fn handle_upload(
// Find or create the case directory.
let case_dir = resolve_case_dir(&user.data_dir, &case_id).await?;
// Auto-reopen soft-deleted cases: a new upload clears the `.deleted`
// marker so the case reappears in /api/oneliners and the web UI.
// Hard-deleted cases (directory already gone) are handled transparently
// by `resolve_case_dir` above — nothing to clear.
if crate::paths::is_deleted(&case_dir).await {
tokio::fs::remove_file(case_dir.join(crate::paths::DELETE_MARKER)).await?;
info!(
user = %user.slug,
case_id = %case_id,
"Reopened soft-deleted case via new upload"
);
}
// Build a filesystem-safe filename from the timestamp (colons → hyphens).
let safe_timestamp = recorded_at_to_filename_stem(&recorded_at);
let file_path = case_dir.join(format!("{safe_timestamp}.m4a"));