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`.
This commit is contained in:
2026-04-13 16:45:24 +02:00
parent 84f8df2bf4
commit bcae4e5466
7 changed files with 158 additions and 19 deletions
+2 -4
View File
@@ -3,14 +3,12 @@ mod health;
mod upload;
mod web;
use std::sync::Arc;
use axum::routing::{get, post};
use axum::Router;
use crate::config::Config;
use crate::AppState;
pub fn api_router() -> Router<Arc<Config>> {
pub fn api_router() -> Router<AppState> {
Router::new()
.route("/api/health", get(health::handle_health))
.route("/api/debug/whoami", get(debug::handle_whoami))
+17 -2
View File
@@ -1,15 +1,18 @@
use std::path::{Path, PathBuf};
use axum::extract::Multipart;
use axum::extract::{Multipart, State};
use axum::Json;
use tracing::info;
use tokio::sync::mpsc::error::TrySendError;
use tracing::{info, warn};
use crate::auth::AuthenticatedUser;
use crate::error::AppError;
use crate::models::{AckResponse, AckStatus};
use crate::transcribe::{TranscribeJob, TranscribeSender};
pub async fn handle_upload(
user: AuthenticatedUser,
State(transcribe_tx): State<TranscribeSender>,
mut multipart: Multipart,
) -> Result<Json<AckResponse>, AppError> {
let mut case_id: Option<String> = None;
@@ -81,6 +84,18 @@ pub async fn handle_upload(
"Recording received"
);
// Enqueue transcription. Failure here is non-fatal: on restart the recovery
// scan will re-enqueue any .m4a without a .transcript.txt sibling.
match transcribe_tx.try_send(TranscribeJob { audio_path: file_path.clone() }) {
Ok(()) => {}
Err(TrySendError::Full(_)) => {
warn!(file = %file_path.display(), "Transcription queue full; will be picked up on restart");
}
Err(TrySendError::Closed(_)) => {
warn!(file = %file_path.display(), "Transcription queue closed (no worker)");
}
}
Ok(Json(AckResponse {
case_id,
recorded_at,