Refactor: Simplify case directory structure

The distinction between `open/` and `done/` directories for cases has
been removed.
All cases for a user now reside directly under the user's directory
(e.g., `<data_path>/<slug>/<case_id>/`).

This simplifies path management and eliminates redundant directory
traversals.

Key changes include:
- Removed `open/` and `done/` subdirectories in path resolution.
- Introduced a `paths::case_dir` function as a single source of truth
  for case directory layout.
- Updated various modules (`recovery`, `auth`, `routes`, `transcribe`,
  `tests`) to use the new path structure.
- Adjusted templates to reflect the simplified case status
  representation.
This commit is contained in:
2026-04-15 22:15:13 +02:00
parent 70eed20909
commit 11eb645f3f
17 changed files with 190 additions and 324 deletions
+2 -81
View File
@@ -100,7 +100,7 @@ async fn upload_creates_new_case() {
// Verify file on disk
let file = data_path
.join("dr_test/open")
.join("dr_test")
.join(case_id)
.join("2026-04-13T10-30-00Z.m4a");
assert!(file.exists(), "Audio file should exist at {file:?}");
@@ -242,7 +242,7 @@ async fn upload_second_recording_same_case() {
assert_eq!(response.status(), StatusCode::OK);
// Both files should exist
let case_dir = data_path.join("dr_test/open").join(case_id);
let case_dir = data_path.join("dr_test").join(case_id);
assert!(case_dir.join("2026-04-13T10-30-00Z.m4a").exists());
assert!(case_dir.join("2026-04-13T10-45-00Z.m4a").exists());
@@ -250,82 +250,3 @@ async fn upload_second_recording_same_case() {
let _ = std::fs::remove_dir_all(&data_path);
}
#[tokio::test]
async fn upload_late_recording_to_done_case() {
let config = test_config();
let data_path = config.data_path.clone();
let app = doctate_server::create_router(config);
let case_id = "770e8400-e29b-41d4-a716-446655440000";
// Pre-create a done/ case directory (simulating a closed case).
let done_dir = data_path.join("dr_test/done").join(case_id);
std::fs::create_dir_all(&done_dir).unwrap();
let (boundary, body) = multipart_body(case_id, "2026-04-13T11:00:00Z", b"late recording");
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/upload")
.header("X-API-Key", "test-key-123")
.header(
"Content-Type",
format!("multipart/form-data; boundary={boundary}"),
)
.body(Body::from(body))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// File should be in done/, not open/
assert!(done_dir.join("2026-04-13T11-00-00Z.m4a").exists());
assert!(!data_path.join("dr_test/open").join(case_id).exists());
// Cleanup
let _ = std::fs::remove_dir_all(&data_path);
}
#[tokio::test]
async fn upload_removes_remove_marker_on_late_upload() {
let config = test_config();
let data_path = config.data_path.clone();
let app = doctate_server::create_router(config);
let case_id = "880e8400-e29b-41d4-a716-446655440000";
// Pre-create a done/ case with .remove marker.
let done_dir = data_path.join("dr_test/done").join(case_id);
std::fs::create_dir_all(&done_dir).unwrap();
std::fs::write(done_dir.join(".remove"), b"").unwrap();
assert!(done_dir.join(".remove").exists());
let (boundary, body) = multipart_body(case_id, "2026-04-13T12:00:00Z", b"surprise recording");
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/upload")
.header("X-API-Key", "test-key-123")
.header(
"Content-Type",
format!("multipart/form-data; boundary={boundary}"),
)
.body(Body::from(body))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// .remove marker should be gone, audio should be there
assert!(!done_dir.join(".remove").exists());
assert!(done_dir.join("2026-04-13T12-00-00Z.m4a").exists());
// Cleanup
let _ = std::fs::remove_dir_all(&data_path);
}