feat: Add API endpoint for listing oneliners

This commit introduces a new API endpoint `/api/oneliners` that allows
authenticated users to retrieve a list of their recent oneliners.

The endpoint supports conditional GET requests using the `ETag` header
for efficient caching. It also allows specifying a time window for the
oneliners via the `hours` query parameter, with sensible defaults and
clamping.

The implementation includes:
- A new route handler `handle_oneliners`.
- A new type `OnelinerWatermark` for tracking user-specific timestamps.
- Logic for scanning user data directories, filtering by time, and
  handling deleted cases.
- Test cases to cover various scenarios, including caching, time
  windowing, and edge cases.
This commit is contained in:
2026-04-18 09:27:23 +02:00
parent 6fbb549ddf
commit 94392e72d8
10 changed files with 658 additions and 16 deletions
+18
View File
@@ -9,17 +9,27 @@ 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
@@ -60,6 +70,7 @@ 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> {
@@ -98,6 +109,12 @@ 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.
@@ -111,6 +128,7 @@ 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())),
})
}