Skip to main content

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