Refactor user config validation and add transcript display

Introduces a dedicated function `validate_and_index_users` to
encapsulate the logic for validating user configurations, checking for
duplicate slugs and API keys. This function returns a `Result` to handle
errors gracefully, and its usage in `load_users` is updated to panic
with a more informative error message.

Additionally, this commit modifies the web routing to fetch and display
audio transcripts. When scanning recordings, it now attempts to read a
corresponding `.transcript.txt` file. If found, the transcript content
is included in the `RecordingView` and rendered in the `cases.html`
template. If the transcript file is not found, a "Transcription pending"
message is displayed.

The `transcribe` function in `whisper.rs` is updated to explicitly set
the language to German (`language=de`), which can improve transcription
accuracy by skipping language detection. The `encode=false` query
parameter has been removed, as the service is designed to handle encoded
audio.

Finally, tests have been added for the new `validate_and_index_users`
function to ensure duplicate slugs and API keys are correctly rejected.
Integration tests in `web_test.rs` have been updated to verify the
rendering of transcripts and the pending status.
This commit is contained in:
2026-04-13 17:08:21 +02:00
parent 13375b8297
commit 1e3cc9574c
6 changed files with 93 additions and 13 deletions
+6 -2
View File
@@ -13,6 +13,7 @@ use crate::error::AppError;
struct RecordingView {
filename: String,
transcript: Option<String>,
}
struct CaseView {
@@ -170,9 +171,12 @@ async fn scan_recordings(case_dir: &Path) -> Vec<RecordingView> {
Ok(s) => s,
Err(_) => continue,
};
if filename.ends_with(".m4a") {
recordings.push(RecordingView { filename });
if !filename.ends_with(".m4a") {
continue;
}
let transcript_path = case_dir.join(&filename).with_extension("transcript.txt");
let transcript = tokio::fs::read_to_string(&transcript_path).await.ok();
recordings.push(RecordingView { filename, transcript });
}
recordings.sort_by(|a, b| a.filename.cmp(&b.filename));