refactor(server): split scan_user_cases into filter + view helpers

The 73-line function mixed three concerns: directory traversal,
UUID/deletion filtering, and view assembly. Split into:

- scan_user_cases: orchestrator (loop + sort).
- filter_valid_case_dir: gate that returns (case_id, case_path) for
  every valid case directory, skipping non-UUID names, files, and
  soft-deleted cases.
- compute_case_view: view assembly that returns None for empty
  cases (no recordings yet).

Each helper is now individually inspectable; nesting depth drops
from 3-4 to 2 levels. Let-else guards replace nested matches.
This commit is contained in:
2026-04-19 15:19:20 +02:00
parent e995cdd7c4
commit bce93811a0
+79 -56
View File
@@ -347,66 +347,89 @@ async fn scan_user_cases(config: &Config, slug: &str, worker_busy: bool) -> Vec<
Err(_) => return cases,
};
while let Ok(Some(case_entry)) = entries.next_entry().await {
let case_id = match case_entry.file_name().into_string() {
Ok(s) => s,
Err(_) => continue,
while let Ok(Some(entry)) = entries.next_entry().await {
let Some((case_id, case_path)) = filter_valid_case_dir(entry).await else {
continue;
};
if uuid::Uuid::parse_str(&case_id).is_err() {
warn!(case_id = %case_id, "Skipping non-UUID directory");
continue;
if let Some(view) =
compute_case_view(case_id, &case_path, llm_configured, worker_busy).await
{
cases.push(view);
}
let case_path = case_entry.path();
if !case_path.is_dir() {
continue;
}
if crate::paths::is_deleted(&case_path).await {
continue;
}
let recordings = scan_recordings(&case_path).await;
if recordings.is_empty() {
continue;
}
let most_recent = recordings
.last()
.map(|r| r.filename.clone())
.unwrap_or_default();
let recordings_count = recordings.len();
let non_failed_count = recordings.iter().filter(|r| !r.failed).count();
let all_transcribed = non_failed_count > 0
&& recordings
.iter()
.filter(|r| !r.failed)
.all(|r| r.transcript.is_some());
let oneliner = tokio::fs::read_to_string(case_path.join("oneliner.txt"))
.await
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let has_document = any_document_exists(&case_path).await;
let analyzing =
!has_document && worker_busy && any_analysis_input_exists(&case_path).await;
let can_analyze = !analyzing && all_transcribed && llm_configured;
let time_hms_utc = extract_hhmm_utc(&most_recent);
let recorded_at_iso = recorded_at_iso_of(&most_recent);
cases.push(UserCaseView {
case_id,
most_recent,
time_hms_utc,
recorded_at_iso,
oneliner,
recordings_count,
analyzing,
has_document,
can_analyze,
});
}
cases.sort_by(|a, b| b.most_recent.cmp(&a.most_recent));
cases
}
/// Accept an entry only if it is a non-deleted directory whose name is
/// 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)> {
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");
return None;
}
let case_path = entry.path();
if !case_path.is_dir() {
return None;
}
if crate::paths::is_deleted(&case_path).await {
return None;
}
Some((case_id, case_path))
}
/// Build a `UserCaseView` for a single case directory. Returns `None`
/// for empty cases (no recordings yet) so the caller can skip them.
async fn compute_case_view(
case_id: String,
case_path: &Path,
llm_configured: bool,
worker_busy: bool,
) -> Option<UserCaseView> {
let recordings = scan_recordings(case_path).await;
if recordings.is_empty() {
return None;
}
let most_recent = recordings
.last()
.map(|r| r.filename.clone())
.unwrap_or_default();
let recordings_count = recordings.len();
let non_failed_count = recordings.iter().filter(|r| !r.failed).count();
let all_transcribed = non_failed_count > 0
&& recordings
.iter()
.filter(|r| !r.failed)
.all(|r| r.transcript.is_some());
let oneliner = tokio::fs::read_to_string(case_path.join("oneliner.txt"))
.await
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let has_document = any_document_exists(case_path).await;
let analyzing = !has_document && worker_busy && any_analysis_input_exists(case_path).await;
let can_analyze = !analyzing && all_transcribed && llm_configured;
let time_hms_utc = extract_hhmm_utc(&most_recent);
let recorded_at_iso = recorded_at_iso_of(&most_recent);
Some(UserCaseView {
case_id,
most_recent,
time_hms_utc,
recorded_at_iso,
oneliner,
recordings_count,
analyzing,
has_document,
can_analyze,
})
}