Commit Graph

21 Commits

Author SHA1 Message Date
Brummel f358da215d feat: lazy retention sweep on /web/cases
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.
2026-04-21 10:51:27 +02:00
Brummel 5effa7c96e feat: Add endpoint to delete recordings
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.
2026-04-20 15:48:34 +02:00
Brummel 1d702e2d85 Add event bus for live UI updates
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.
2026-04-19 23:33:53 +02:00
Brummel 76e8ee18e9 feat: Implement magic link authentication
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
2026-04-19 16:00:12 +02:00
Brummel 0d5c2f5888 Formatting 2026-04-19 15:35:10 +02:00
Brummel e995cdd7c4 refactor(server): bundle worker pipeline into PipelineState
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.
2026-04-19 15:14:22 +02:00
Brummel 16c6fb2e07 refactor(server): introduce CaseId newtype with typed Path extractor
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.
2026-04-19 15:05:46 +02:00
Brummel a1ff410d1b 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.
2026-04-19 14:47:30 +02:00
Brummel 94392e72d8 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.
2026-04-18 09:27:23 +02:00
Brummel 9c11892e85 Add gazetteer module to lib.rs 2026-04-16 17:06:02 +02:00
Brummel 2f62f11563 Refactor busy flags and worker guards
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.
2026-04-16 00:32:45 +02:00
Brummel 9072d7a833 Refactor analyze recovery and worker busy status
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.
2026-04-16 00:19:08 +02:00
Brummel 11eb645f3f 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.
2026-04-15 22:15:13 +02:00
Brummel aa6b8fd798 Add analyze channel to AppState
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.
2026-04-15 19:16:27 +02:00
Brummel e2a05a108a feat: Add analyze module for LLM integration
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.
2026-04-15 19:14:28 +02:00
Brummel d489716c9b Feat: Implement basic web authentication and UI
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.
2026-04-14 15:09:23 +02:00
Brummel bcae4e5466 feat: Add transcription worker and state management
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`.
2026-04-13 16:45:24 +02:00
Brummel 73692b0a02 feat: Add ffmpeg remuxing for faststart 2026-04-13 16:37:52 +02:00
Brummel 1051e83da3 feat: Add file upload endpoint
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.
2026-04-13 13:01:29 +02:00
Brummel 08c6e12a74 Update config and dependencies for auth
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.
2026-04-13 12:51:27 +02:00
Brummel 80a6537e16 feat: Add health check endpoint
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.
2026-04-13 12:41:40 +02:00