List every photo's open Enhance jobs for a jobs manager

GET /enhance/jobs without a path now answers the jobs manager (FVR #99):
every photo's queued / running / unavailable jobs, plus ready / failed /
expired ones touched in the last 3 days, newest first, discarded and
approved hidden, at most 100. DB only; the poller keeps rows current.
An optional status=a,b filter applies to both forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cameron CordesandClaude Opus 5.5 committed 2026-11-01 22:14:27 -05:00
1 parent f121457c43
commit 37a2386458
5 files changed
+156 -8

No files matched your search

+36 -8
View File
@@ -347,12 +347,20 @@ pub async fn get_job(
#[derive(Debug, Deserialize)]
pub struct JobsQuery {
pub path: String,
/// A photo (original or version): that photo's jobs. Omitted: every
/// photo's open jobs, for the jobs manager.
pub path: Option<String>,
pub library: Option<String>,
/// Comma-separated statuses to keep (e.g. `queued,running,ready`);
/// omitted = all. Applies to both forms.
pub status: Option<String>,
}
/// A photo's recent jobs, newest first, so the viewer can pick up an enhance
/// in progress after reopening. Discarded jobs are not listed.
/// With `path`: a photo's recent jobs, newest first, so the viewer can pick up
/// an enhance in progress after reopening. Without: the jobs manager's list,
/// every photo's queued / running / unavailable jobs plus ready / failed /
/// expired ones from the last few days. Discarded and approved-and-done jobs
/// are not listed. DB only.
#[get("/enhance/jobs")]
pub async fn list_jobs(
_: Claims,
@@ -361,13 +369,33 @@ pub async fn list_jobs(
app_state: web::Data<AppState>,
exif_dao: ExifData,
) -> impl Responder {
let file = match resolve_file(&app_state, &exif_dao, &q.path, q.library.as_deref()) {
Ok(f) => f,
Err(r) => return r,
let jobs = match q.path.as_deref() {
Some(path) => {
let file = match resolve_file(&app_state, &exif_dao, path, q.library.as_deref()) {
Ok(f) => f,
Err(r) => return r,
};
svc.jobs_for_hash(&file.hash)
}
None => svc.open_jobs(),
};
match svc.jobs_for_hash(&file.hash) {
let wanted: Option<Vec<&str>> = q.status.as_deref().map(|s| {
s.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect()
});
match jobs {
Ok(jobs) => {
let views: Vec<JobView> = jobs.iter().map(job_view).collect();
let views: Vec<JobView> = jobs
.iter()
.filter(|j| {
wanted
.as_ref()
.is_none_or(|w| w.contains(&j.status.as_str()))
})
.map(job_view)
.collect();
HttpResponse::Ok().json(json!({ "jobs": views }))
}
Err(e) => error_response(&e),