Refactor ETag generation for oneliners API

Remove the per-user oneliner watermark, which was previously used to
generate ETags for the `/api/oneliners` endpoint. The watermark was
intended to track the last successful oneliner write for each user.

The ETag generation has been refactored to use a deterministic FNV-1a
hash. This hash is computed over a sorted list of tuples containing
`(case_id, last_recording_at_ns, oneliner_mtime_ns)` for all visible
cases. This approach ensures that the ETag changes whenever any
listen-relevant file system modification occurs, such as a new
recording, oneliner regeneration, soft-delete, undo, or
upload-auto-reopen. This new method is more robust and avoids the
complexity of managing per-user state.

The `OnelinerWatermark` type alias and its associated logic have been
removed from `AppState` and the relevant modules (`transcribe::worker`,
`transcribe::recovery`, `routes::oneliners`, `main`). New integration
tests have been added to verify that various delete operations (single
delete, undo delete, bulk delete) correctly trigger an ETag update,
ensuring cache invalidation.
This commit is contained in:
2026-04-19 14:47:30 +02:00
parent 5e8d2f8e58
commit a1ff410d1b
11 changed files with 425 additions and 80 deletions
-18
View File
@@ -9,27 +9,17 @@ pub mod routes;
pub mod transcribe;
pub mod web_session;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use axum::extract::FromRef;
use axum::Router;
use time::OffsetDateTime;
use tokio::sync::RwLock;
use analyze::AnalyzeSender;
use config::Config;
use transcribe::TranscribeSender;
use web_session::SessionStore;
/// Per-user UTC timestamp of the last successful oneliner write.
/// Read on every `/api/oneliners` request to build the ETag; written
/// from the transcribe worker and the boot recovery scan. Missing entry
/// → the user has no known oneliner activity yet; the endpoint returns
/// a full response and sets an ETag based on a `0` seed.
pub type OnelinerWatermark = Arc<RwLock<HashMap<String, OffsetDateTime>>>;
/// Live "is this worker currently processing a job?" flag.
/// Set true before each job, false after. UI uses it to distinguish a real
/// in-flight job from a stale on-disk marker (orphan input, missing
@@ -70,7 +60,6 @@ pub struct AppState {
pub session_store: SessionStore,
pub analyze_busy: AnalyzeBusy,
pub transcribe_busy: TranscribeBusy,
pub oneliner_watermark: OnelinerWatermark,
}
impl FromRef<AppState> for Arc<Config> {
@@ -109,12 +98,6 @@ impl FromRef<AppState> for TranscribeBusy {
}
}
impl FromRef<AppState> for OnelinerWatermark {
fn from_ref(state: &AppState) -> Self {
state.oneliner_watermark.clone()
}
}
/// Test/simple entrypoint: jobs pushed into either channel are dropped
/// because the receivers are not retained. Use [`create_router_with_state`]
/// from `main.rs` where real workers own the receivers.
@@ -128,7 +111,6 @@ pub fn create_router(config: Arc<Config>) -> Router {
session_store: web_session::new_store(),
analyze_busy: AnalyzeBusy(Arc::new(AtomicBool::new(false))),
transcribe_busy: TranscribeBusy(Arc::new(AtomicBool::new(false))),
oneliner_watermark: Arc::new(RwLock::new(HashMap::new())),
})
}