Formatting

This commit is contained in:
2026-04-19 15:35:10 +02:00
parent 041f9015ca
commit 0d5c2f5888
42 changed files with 612 additions and 357 deletions
+9 -5
View File
@@ -4,16 +4,18 @@ use axum::extract::State;
use axum::response::Redirect;
use axum_extra::extract::Form;
use serde::Deserialize;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use tracing::{info, warn};
use crate::analyze::{AnalyzeJob, AnalyzeSender, ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
use crate::analyze::{ANALYSIS_INPUT_FILE, AnalyzeJob, AnalyzeSender, DOCUMENT_FILE};
use crate::auth::AuthenticatedWebUser;
use crate::config::Config;
use crate::error::AppError;
use crate::paths::{write_delete_marker, DeleteMarker};
use crate::routes::case_actions::{build_analysis_input, reset_case_artefacts, write_input_create_new};
use crate::paths::{DeleteMarker, write_delete_marker};
use crate::routes::case_actions::{
build_analysis_input, reset_case_artefacts, write_input_create_new,
};
use crate::routes::user_web::locate_case;
#[derive(Debug, Deserialize)]
@@ -37,7 +39,9 @@ pub async fn handle_bulk_action(
let user_root = config.data_path.join(&user.slug);
match form.action.as_str() {
"analyze" => bulk_analyze(&config, &analyze_tx, &user.slug, &user_root, &form.case_ids).await,
"analyze" => {
bulk_analyze(&config, &analyze_tx, &user.slug, &user_root, &form.case_ids).await
}
"delete" => bulk_delete(&user.slug, &user_root, &form.case_ids).await,
"reset" => {
if !user.is_admin() {
+9 -12
View File
@@ -5,21 +5,21 @@ use std::time::SystemTime;
use askama::Template;
use axum::extract::State;
use axum::response::{Html, Redirect};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use tokio::io::AsyncWriteExt;
use tracing::{info, warn};
use doctate_common::timestamp::{filename_stem_to_recorded_at, now_rfc3339};
use crate::analyze::{
AnalysisInput, AnalyzeJob, AnalyzeSender, RecordingInput, ANALYSIS_INPUT_FILE, DOCUMENT_FILE,
ANALYSIS_INPUT_FILE, AnalysisInput, AnalyzeJob, AnalyzeSender, DOCUMENT_FILE, RecordingInput,
};
use crate::auth::AuthenticatedWebUser;
use crate::case_id::CaseIdPath;
use crate::config::Config;
use crate::error::AppError;
use crate::paths::{read_delete_marker, write_delete_marker, DeleteMarker, DELETE_MARKER};
use crate::paths::{DELETE_MARKER, DeleteMarker, read_delete_marker, write_delete_marker};
use crate::routes::user_web::locate_case_or_404;
#[derive(Template)]
@@ -157,17 +157,13 @@ fn compute_last_mtime(m4as: &[(PathBuf, SystemTime)]) -> SystemTime {
/// transkribiert" body. Blank transcripts are silently skipped — they
/// represent recordings the transcriber classified as silence and must
/// not be pushed to the LLM.
async fn read_recordings(
m4as: &[(PathBuf, SystemTime)],
) -> Result<Vec<RecordingInput>, AppError> {
async fn read_recordings(m4as: &[(PathBuf, SystemTime)]) -> Result<Vec<RecordingInput>, AppError> {
let mut recordings: Vec<RecordingInput> = Vec::new();
for (m4a_path, _) in m4as {
let transcript_path = m4a_path.with_extension("transcript.txt");
let text = tokio::fs::read_to_string(&transcript_path)
.await
.map_err(|_| {
AppError::BadRequest("Nicht alle Aufnahmen sind transkribiert".into())
})?;
.map_err(|_| AppError::BadRequest("Nicht alle Aufnahmen sind transkribiert".into()))?;
if text.trim().is_empty() {
continue;
@@ -240,7 +236,9 @@ pub(crate) async fn write_input_create_new(
/// Read `case_dir/document.md`. Returns `None` if no document exists.
pub(crate) async fn read_document(case_dir: &Path) -> Option<String> {
tokio::fs::read_to_string(case_dir.join(DOCUMENT_FILE)).await.ok()
tokio::fs::read_to_string(case_dir.join(DOCUMENT_FILE))
.await
.ok()
}
/// Reset a case to its raw audio: delete all derived artefacts
@@ -388,8 +386,7 @@ async fn latest_delete_batch(user_root: &Path) -> Option<(uuid::Uuid, String)> {
None => Some(candidate),
// Order: deleted_at desc, then batch desc as tie-breaker.
Some(ref cur)
if candidate.1 > cur.1
|| (candidate.1 == cur.1 && candidate.0 > cur.0) =>
if candidate.1 > cur.1 || (candidate.1 == cur.1 && candidate.0 > cur.0) =>
{
Some(candidate)
}
+1 -1
View File
@@ -1,5 +1,5 @@
use axum::Json;
use serde_json::{json, Value};
use serde_json::{Value, json};
use crate::auth::AuthenticatedUser;
use crate::error::AppError;
+2 -2
View File
@@ -1,8 +1,8 @@
use std::sync::Arc;
use axum::extract::State;
use axum::Json;
use serde_json::{json, Value};
use axum::extract::State;
use serde_json::{Value, json};
use crate::config::Config;
+6 -3
View File
@@ -2,12 +2,12 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use askama::Template;
use axum::Form;
use axum::extract::State;
use axum::response::{Html, IntoResponse, Redirect, Response};
use axum::Form;
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
use rand::distributions::Alphanumeric;
use rand::Rng;
use rand::distributions::Alphanumeric;
use serde::Deserialize;
use tracing::{info, warn};
@@ -49,7 +49,10 @@ pub async fn handle_login_submit(
if !valid {
if user.is_none() {
// Dummy verify so timing is similar regardless of slug existence.
let _ = bcrypt::verify(&form.password, "$2b$12$invalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidii");
let _ = bcrypt::verify(
&form.password,
"$2b$12$invalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidii",
);
}
warn!(slug = %form.slug, "login failed");
return render_login(Some("Login fehlgeschlagen")).map(IntoResponse::into_response);
+5 -2
View File
@@ -8,8 +8,8 @@ mod upload;
pub(crate) mod user_web;
pub(crate) mod web;
use axum::routing::{get, post};
use axum::Router;
use axum::routing::{get, post};
use crate::AppState;
@@ -19,7 +19,10 @@ pub fn api_router() -> Router<AppState> {
.route("/api/debug/whoami", get(debug::handle_whoami))
.route("/api/upload", post(upload::handle_upload))
.route("/api/oneliners", get(oneliners::handle_oneliners))
.route("/web/login", get(login::handle_login_page).post(login::handle_login_submit))
.route(
"/web/login",
get(login::handle_login_page).post(login::handle_login_submit),
)
.route("/web/logout", post(login::handle_logout))
.route("/web/cases", get(user_web::handle_my_cases))
.route("/web/cases/{case_id}", get(user_web::handle_case_detail))
+3 -3
View File
@@ -20,10 +20,10 @@
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use axum::extract::Query;
use axum::http::{header, HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::extract::Query;
use axum::http::{HeaderMap, StatusCode, header};
use axum::response::{IntoResponse, Response};
use doctate_common::oneliners::{OnelinerEntry, OnelinersResponse};
use serde::Deserialize;
use time::format_description::well_known::Rfc3339;
+1 -1
View File
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
use axum::extract::{Multipart, State};
use axum::Json;
use axum::extract::{Multipart, State};
use doctate_common::timestamp::recorded_at_to_filename_stem;
use tokio::sync::mpsc::error::TrySendError;
use tracing::{info, warn};
+6 -8
View File
@@ -1,22 +1,22 @@
use std::path::Path;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use askama::Template;
use axum::extract::State;
use axum::response::Html;
use tracing::{info, warn};
use time::{format_description::well_known::Rfc3339, Date, OffsetDateTime};
use time::{Date, OffsetDateTime, format_description::well_known::Rfc3339};
use crate::analyze::{recovery as analyze_recovery, ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
use crate::PipelineState;
use crate::analyze::{ANALYSIS_INPUT_FILE, DOCUMENT_FILE, recovery as analyze_recovery};
use crate::auth::AuthenticatedWebUser;
use crate::case_id::{CaseId, CaseIdPath};
use crate::config::Config;
use crate::error::AppError;
use crate::routes::web::{scan_recordings, RecordingView};
use crate::routes::web::{RecordingView, scan_recordings};
use crate::transcribe::recovery as transcribe_recovery;
use crate::PipelineState;
struct UserCaseView {
case_id: String,
@@ -366,9 +366,7 @@ async fn scan_user_cases(config: &Config, slug: &str, worker_busy: bool) -> Vec<
/// a valid UUID. Returns the (case_id-string, absolute path) tuple so
/// the caller does not need to recompute either. Non-UUID names are
/// logged at WARN level; other rejections are silent (common case).
async fn filter_valid_case_dir(
entry: tokio::fs::DirEntry,
) -> Option<(String, std::path::PathBuf)> {
async fn filter_valid_case_dir(entry: tokio::fs::DirEntry) -> Option<(String, std::path::PathBuf)> {
let case_id = entry.file_name().into_string().ok()?;
if uuid::Uuid::parse_str(&case_id).is_err() {
warn!(case_id = %case_id, "Skipping non-UUID directory");
+2 -8
View File
@@ -36,9 +36,7 @@ struct CasesTemplate {
cases: Vec<CaseView>,
}
pub async fn handle_case_list(
State(config): State<Arc<Config>>,
) -> Result<Html<String>, AppError> {
pub async fn handle_case_list(State(config): State<Arc<Config>>) -> Result<Html<String>, AppError> {
let cases = scan_cases(&config.data_path).await;
let template = CasesTemplate { cases };
template
@@ -82,11 +80,7 @@ fn validate_user_slug(user: &str) -> Result<(), AppError> {
fn validate_filename(filename: &str) -> Result<(), AppError> {
let ok_suffix = filename.ends_with(".m4a") || filename.ends_with(".m4a.failed");
if !ok_suffix
|| filename.contains('/')
|| filename.contains('\\')
|| filename.contains("..")
{
if !ok_suffix || filename.contains('/') || filename.contains('\\') || filename.contains("..") {
return Err(AppError::BadRequest("Invalid filename".into()));
}
Ok(())