New module retention::sweep_user_retention is invoked at the top of
handle_my_cases, using the current user's retention policy. Per case:
step 1 auto-closes when the newest .m4a mtime is older than
auto_close_days; step 2 auto-purges when closed_at is older than
auto_delete_days. Order matters - a case auto-closed in the same sweep
has closed_at ~= now and naturally survives the purge check,
preserving the full grace period after a vacation.
Race guard: purge re-reads the close marker immediately before
remove_dir_all so a concurrent upload that reopens the case cancels
the purge. Every automatic action emits an info! line with reason and
threshold; failures warn but never abort the sweep.
Seven integration tests cover both threshold paths, the disable-via-0
escape hatch, the newest-recording rule, the vacation scenario, and
the open-case skip. Tests age the comparison objects (mtime via
filetime, closed_at as handwritten RFC3339) instead of faking "now" -
no clock abstraction needed.
This commit introduces a new API endpoint for deleting recordings. The
endpoint
handles the removal of the audio file and its associated sidecar files
(transcript,
duration). It also invalidates derived artifacts like oneliner.json,
document.md,
and analysis_input.json. Additionally, it includes checks for path
traversal,
correct file extensions, and case ownership to ensure security and data
integrity.
A new `RecordingDeleted` event is also added to the `CaseEventKind`
enum.
Introduces a system for broadcasting real-time events to the web UI.
This enables features like automatic page reloads when case data changes
in the background, improving the user experience by keeping the UI
synchronized with the backend.
Key components:
- `events` module: Contains the `CaseEventKind` enum, `CaseEvent`
struct, and `EventSender` type for managing the broadcast channel.
- SSE endpoint (`/web/events`): Streams events to connected browsers.
- Client-side JavaScript: Listens for events and triggers debounced page
reloads.
- Integration points: Workers and route handlers now emit events when
relevant state changes occur.
This commit introduces a new magic link authentication flow.
The desktop client can now request a temporary, one-time-use token from
the server.
This token is then used to open a URL in the system browser, which
redirects to the server.
The server consumes the token, installs a regular web session, and
redirects the user
Group the four worker handles (analyze/transcribe x busy/tx) into a
new PipelineState struct with a FromRef<AppState> impl. Convert
heal_orphans_if_idle from a free function with 6 parameters into a
method on PipelineState. The two web handlers (handle_my_cases,
handle_case_detail) now take one State<PipelineState> instead of
four separate State extractors.
The original FromRef impls for AnalyzeBusy/Sender and TranscribeBusy/
Sender remain intact so handlers that need only one of them keep
their narrower signature.
Replace five duplicated uuid::Uuid::parse_str calls in web handlers
(analyze, document view, delete, reset, case detail) with a
CaseIdPath extractor that validates the URL segment once and
returns the legacy "Invalid case_id" 400 body byte-for-byte.
The extractor uses axum's FromRequestParts and maps any failure
to AppError::BadRequest, so IntoResponse handling is unchanged.
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 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.
The `WorkerBusy` type was previously a single `Arc<AtomicBool>` shared
between the analyze and transcribe workers. This commit refactors this
to:
- Introduce `AnalyzeBusy` and `TranscribeBusy` newtype wrappers around
`WorkerBusy` to distinguish between the two flags. This allows
`axum::extract::State` to target them individually.
- Move the `BusyGuard` RAII guard into `src/lib.rs` and make it generic
to work with any `WorkerBusy` instance.
- Update the `main.rs`, `worker.rs`, and `routes/user_web.rs` files to
use the new types and guards, ensuring correct state management for
both pipelines.
- Enhance the transcription recovery scan
(`transcribe::recovery::scan_and_enqueue`) to iterate over user
directories and call `enqueue_pending_for_user` for each, making it
more robust and aligned with the per-user self-healing mechanism.
- Add a check for `transcribe_busy` in the `case_detail.html` template
to correctly display "Transkription läuft…" only when the transcribe
worker is actually active.
The analyze recovery scan has been refactored to iterate through user
directories and enqueue pending analysis jobs more efficiently. The
`WorkerBusy` status has been introduced as an `Arc<AtomicBool>` to track
whether the analyze worker is currently processing a job.
This `WorkerBusy` flag is used in the `analyze::worker::run` function
and managed by a `BusyGuard` RAII struct, ensuring the flag is correctly
set and unset even in case of panics.
The `handle_my_cases` and `handle_case_detail` handlers in `user_web.rs`
now utilize the `WorkerBusy` flag to accurately display the "analyzing"
status and to trigger a self-heal of orphaned analysis inputs when the
worker is idle.
Additionally, the `WorkerBusy` type is now exported from
`server/src/lib.rs` to be accessible by other modules. The test cases
have been updated to include the `WorkerBusy` parameter when spawning
the analyze worker.
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.
Introduces a new channel for the analyze pipeline, including its sender
in the `AppState`.
Also updates the test router to create both transcribe and analyze
channels.
This commit introduces the `analyze` module, which orchestrates
communication with Large Language Models (LLMs) for text summarization
and analysis.
Key components include:
- `llm.rs`: Contains the `LlmError` enum and the `chat_once` function
for interacting with OpenAI-compatible LLM APIs.
- `prompt.rs`: Defines the system prompt and logic for rendering user
content from analysis inputs.
- `recovery.rs`: Implements logic to scan for and re-enqueue pending
analysis jobs upon server startup.
- `worker.rs`: The core worker that consumes analysis jobs, processes
them with the LLM, and writes the output.
- `mod.rs`: Defines `AnalyzeJob` and associated channel types for
inter-component communication.
This module enables the server to process dictated recordings, summarize
them using an LLM, and store the results.
This commit introduces the foundation for web-based authentication and
user interface. It includes:
- **Session Management**: Securely handling user sessions using
cryptographically generated tokens, cookies with appropriate security
flags, and server-side storage.
- **Login/Logout Functionality**: Endpoints for users to log in with
their credentials and log out, clearing their session.
- **User Interface**: Basic templates for the login page, a list of
cases, and a detailed view of a single case, allowing users to
navigate and view their data.
- **Authorization**: An `AuthenticatedWebUser` extractor to ensure only
logged-in users can access protected web routes.
- **Configuration**: Added a `cookie_secure` option to the configuration
to control the `Secure` flag on session cookies, useful for local
development.
- **Dependencies**: Added necessary dependencies for password hashing,
time handling, and TOML file manipulation.
- **Helper Binary**: Included a `hash-password` binary to simplify
generating bcrypt hashes for user passwords.
Introduce an `AppState` struct to hold shared application state,
including configuration and the transcription sender.
Modify `create_router` and `create_router_with_state` to use `AppState`.
Update `main.rs` to initialize the transcription worker and pass the
`AppState` to the router.
Implement the transcription worker logic in
`server/src/transcribe/worker.rs`, handling job queues, FFmpeg remuxing,
and Whisper transcription.
Update `server/src/auth.rs` to use `Arc<Config>` from the shared state.
Modify `server/src/routes/upload.rs` to enqueue transcription jobs and
handle potential errors.
Add `TranscribeJob` and channel types in `server/src/transcribe/mod.rs`.
This commit introduces a new API endpoint `/api/upload` for handling
audio file uploads. It supports multipart form data, extracts `case_id`,
`recorded_at`, and `audio` fields. The audio data is saved to a
filesystem path determined by the user and case ID, within either the
`open/` or `done/` directories.
New features include:
- Integration with `axum`'s `Multipart` extractor for parsing form data.
- Validation of `case_id` as a UUID.
- Creation of case directories if they do not exist.
- Handling of late uploads to already closed cases by placing files in
the `done/` directory and removing any `.remove` marker.
- Logging of received recordings.
- Definition of `AckResponse` and `AckStatus` models for API responses.
- Addition of comprehensive unit tests for various upload scenarios,
including new case creation, invalid inputs, authentication, and late
uploads.
Refactor configuration loading to separate user credentials from server
settings.
Update dependencies to their latest compatible versions to leverage new
features
and security patches.
Key changes include:
- Moving user credentials (`api_key`, `web_password`, `role`) from
`.env`
to a dedicated `users.toml` file for better manageability and
security.
- Introducing an `AuthenticatedUser` struct and extractor for API
key-based
authentication.
- Updating `axum` and related crates to leverage Rust edition 2024
improvements.
- Adjusting configuration values and tests to reflect these changes.
Introduce a new health check endpoint at `/api/health`. This endpoint
returns a JSON response indicating the server's status and configuration
details, such as the port, data path, and configured URLs for external
services.
This change also refactors the application setup by:
- Creating a `lib.rs` file to house the `create_router` function, making
it
reusable for both the main application and integration tests.
- Moving configuration loading logic into `main.rs` and passing it as a
shared state (`Arc<Config>`) to the router.
- Adding a unit test (`health_test.rs`) to verify the functionality of
the
health check endpoint.
- Updating `Cargo.toml` and `Cargo.lock` to include the `tower`
dependency,
which is used for testing.