Skip to main content

acme_proxy_admin/webadmin/handlers/
upstream_orders.rs

1//! `/api/upstream-orders` — the relay signer's per-order upstream state,
2//! read-only.
3//!
4//! There is no mutating route in this file and there is not meant to be one.
5//! Abandoning an in-flight relay is done through `POST /api/jobs/{id}/cancel`
6//! on the `signer_relay_issue` job, which owns the state machine. Neither
7//! handler here takes
8//! [`AuthenticatedWrite`](crate::webadmin::session::AuthenticatedWrite), and
9//! `tests/admin_api.rs::mutating_endpoints()` has no entry for
10//! `/api/upstream-orders`.
11
12use axum::Json;
13use axum::extract::{Path, Query, State};
14use serde::Deserialize;
15use serde_json::Value;
16
17use crate::admin;
18use crate::webadmin::AdminState;
19use crate::webadmin::error::AdminError;
20use crate::webadmin::handlers::paging::{PageParams, page_envelope};
21use crate::webadmin::handlers::params::{bad_status, empty_is_absent};
22use crate::webadmin::session::Authenticated;
23use acme_proxy_store::status::UnknownStatus;
24use acme_proxy_store::status::UpstreamOrderStatus;
25use acme_proxy_store::upstream_order::UpstreamOrder;
26use acme_proxy_store::upstream_order::UpstreamOrderQuery;
27
28/// The window fields are inline, not `#[serde(flatten)]` — see the note on
29/// [`super::accounts::AccountListParams`].
30#[derive(Debug, Deserialize, Default)]
31pub struct UpstreamOrderListParams {
32    #[serde(default, deserialize_with = "empty_is_absent")]
33    pub profile: Option<String>,
34    #[serde(default, deserialize_with = "empty_is_absent")]
35    pub status: Option<String>,
36    pub limit: Option<i64>,
37    pub offset: Option<i64>,
38}
39
40impl UpstreamOrderListParams {
41    /// Refused by name so `/api` and `/ui` reject a bad `?status=` identically.
42    pub fn parsed_status(&self) -> Result<Option<UpstreamOrderStatus>, UnknownStatus> {
43        self.status.as_deref().map(str::parse).transpose()
44    }
45}
46
47/// The listing query `params` asks for, refusals and all — shared with
48/// `/ui/upstream-orders` (the [`super::orders::order_query`] rule).
49pub(crate) fn upstream_order_query(
50    params: &UpstreamOrderListParams,
51    page: crate::webadmin::handlers::paging::Page,
52) -> Result<UpstreamOrderQuery, AdminError> {
53    Ok(UpstreamOrderQuery {
54        profile: params.profile.clone(),
55        status: params.parsed_status().map_err(bad_status)?,
56        limit: page.limit,
57        offset: page.offset,
58    })
59}
60
61/// `GET /api/upstream-orders?profile=&status=&limit=&offset=`
62pub async fn list_upstream_orders(
63    State(state): State<AdminState>,
64    Query(params): Query<UpstreamOrderListParams>,
65    _auth: Authenticated,
66) -> Result<Json<Value>, AdminError> {
67    let page = PageParams::from(params.limit, params.offset).resolve(&state.config);
68    let query = upstream_order_query(&params, page)?;
69    let (rows, total) = UpstreamOrder::search(&query, &state.database).await?;
70    let items = rows.iter().map(admin::render_upstream_order_json).collect();
71    Ok(Json(page_envelope(items, total, page)))
72}
73
74/// `GET /api/upstream-orders/{id}` — by the **local** order id, cross-linked to
75/// the relay job.
76pub async fn get_upstream_order(
77    State(state): State<AdminState>,
78    Path(id): Path<String>,
79    _auth: Authenticated,
80) -> Result<Json<Value>, AdminError> {
81    let detail = admin::load_upstream_order_detail(&id, state.database.clone())
82        .await?
83        .ok_or_else(|| AdminError::not_found(format!("no upstream order for local order {id}")))?;
84    Ok(Json(admin::render_upstream_order_detail_json(&detail)))
85}