manta_server/server/handlers/
image.rs

1//! IMS image handlers.
2//!
3//! - `GET    /v2/images` → [`get_images`] —
4//!   wraps `service::image::get_images`. Orders oldest-first (newest
5//!   last) and filters by name glob and `since`/`until` creation-date
6//!   bounds.
7//! - `DELETE /v2/images` → [`delete_images`] —
8//!   wraps `service::image::delete_images`; with `?dry_run=true`
9//!   returns the validation result without deleting.
10
11use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
12
13use super::{
14  ErrorResponse, RequestCtx, SiteHeader, parse_iso_datetime, to_handler_error,
15};
16use crate::service;
17
18// ---------------------------------------------------------------------------
19// GET /v2/images
20// ---------------------------------------------------------------------------
21
22pub use manta_shared::types::api::queries::{DeleteImagesQuery, ImageQuery};
23
24/// GET /images — list IMS images, oldest-first (newest last).
25#[utoipa::path(get, path = "/images", tag = "images",
26  params(ImageQuery, SiteHeader),
27  security(("bearerAuth" = [])),
28  responses(
29    (status = 200, description = "List of images", body = Vec<serde_json::Value>),
30    (status = 400, description = "Malformed since/until, or since after until", body = ErrorResponse),
31    (status = 401, description = "Unauthorized",   body = ErrorResponse),
32    (status = 500, description = "Internal error", body = ErrorResponse),
33  )
34)]
35#[tracing::instrument(skip_all)]
36pub async fn get_images(
37  ctx: RequestCtx,
38  Query(q): Query<ImageQuery>,
39) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
40  let infra = ctx.infra();
41
42  let since = q
43    .since
44    .as_deref()
45    .map(|s| parse_iso_datetime("since", s))
46    .transpose()?;
47  let until = q
48    .until
49    .as_deref()
50    .map(|s| parse_iso_datetime("until", s))
51    .transpose()?;
52
53  let params = service::image::GetImagesParams {
54    id: q.id,
55    pattern: q.pattern,
56    since,
57    until,
58    limit: q.limit,
59  };
60
61  let images = service::image::get_images(&infra, &ctx.token, &params)
62    .await
63    .map_err(to_handler_error)?;
64
65  Ok(Json(images))
66}
67
68// ---------------------------------------------------------------------------
69// DELETE /v2/images — with ?ids=id1,id2&dry_run=true
70// ---------------------------------------------------------------------------
71
72/// `DELETE /v2/images` — delete IMS images by ID; validates only when `dry_run=true`.
73#[utoipa::path(delete, path = "/images", tag = "images",
74  params(DeleteImagesQuery, SiteHeader),
75  security(("bearerAuth" = [])),
76  responses(
77    // dry_run/real result union — kept as Value until the union shape is formalised
78    (status = 200, description = "Images deleted or validation result", body = serde_json::Value),
79    (status = 400, description = "Bad request",                         body = ErrorResponse),
80    (status = 401, description = "Unauthorized",                        body = ErrorResponse),
81    (status = 500, description = "Internal error",                      body = ErrorResponse),
82  )
83)]
84#[tracing::instrument(skip_all)]
85pub async fn delete_images(
86  ctx: RequestCtx,
87  Query(q): Query<DeleteImagesQuery>,
88) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
89  tracing::info!("delete_images ids={} dry_run={}", q.ids, q.dry_run);
90  let infra = ctx.infra();
91
92  let id_strings: Vec<String> =
93    q.ids.split(',').map(|s| s.trim().to_string()).collect();
94  let id_refs: Vec<&str> =
95    id_strings.iter().map(std::string::String::as_str).collect();
96
97  if q.dry_run {
98    service::image::validate_image_deletion(&infra, &ctx.token, &id_refs, None)
99      .await
100      .map_err(to_handler_error)?;
101    return Ok((
102      StatusCode::OK,
103      Json(serde_json::json!({ "validated_ids": id_strings })),
104    ));
105  }
106
107  let deleted =
108    service::image::delete_images(&infra, &ctx.token, &id_refs, None)
109      .await
110      .map_err(to_handler_error)?;
111
112  Ok((
113    StatusCode::OK,
114    Json(serde_json::json!({ "deleted": deleted })),
115  ))
116}