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:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user