Skip to main content

postrust_proxy/saas/handlers/
upstreams.rs

1//! Upstream API handlers.
2
3use crate::admin::api::ApiResponse;
4use crate::saas::auth::Auth;
5use crate::saas::handlers::{error_response, require_scope, SaasState};
6use crate::saas::types::{CreateBackendRequest, CreateUpstreamRequest, UpdateUpstreamRequest};
7use axum::{
8    extract::{Path, State},
9    http::StatusCode,
10    response::IntoResponse,
11    Json,
12};
13use uuid::Uuid;
14
15/// List upstreams for the authenticated tenant.
16pub async fn list_upstreams(State(state): State<SaasState>, Auth(auth): Auth) -> impl IntoResponse {
17    if let Err(refusal) = require_scope(&auth, "upstreams:read") {
18        return refusal;
19    }
20
21    match state.domain_manager.list_upstreams(auth.tenant_id).await {
22        Ok(upstreams) => Json(ApiResponse::success(upstreams)).into_response(),
23        Err(e) => error_response(e).into_response(),
24    }
25}
26
27/// Create a new upstream.
28pub async fn create_upstream(
29    State(state): State<SaasState>,
30    Auth(auth): Auth,
31    Json(req): Json<CreateUpstreamRequest>,
32) -> impl IntoResponse {
33    if let Err(refusal) = require_scope(&auth, "upstreams:write") {
34        return refusal;
35    }
36
37    match state
38        .domain_manager
39        .create_upstream(auth.tenant_id, req)
40        .await
41    {
42        Ok(upstream) => (StatusCode::CREATED, Json(ApiResponse::success(upstream))).into_response(),
43        Err(e) => error_response(e).into_response(),
44    }
45}
46
47/// Get an upstream by ID.
48pub async fn get_upstream(
49    State(state): State<SaasState>,
50    Auth(auth): Auth,
51    Path(id): Path<Uuid>,
52) -> impl IntoResponse {
53    if let Err(refusal) = require_scope(&auth, "upstreams:read") {
54        return refusal;
55    }
56
57    match state.domain_manager.get_upstream(id, auth.tenant_id).await {
58        Ok(Some(upstream)) => Json(ApiResponse::success(upstream)).into_response(),
59        Ok(None) => (
60            StatusCode::NOT_FOUND,
61            Json(ApiResponse::<()>::error_with_code(
62                "not_found",
63                "Upstream not found",
64            )),
65        )
66            .into_response(),
67        Err(e) => error_response(e).into_response(),
68    }
69}
70
71/// Update an upstream.
72pub async fn update_upstream(
73    State(state): State<SaasState>,
74    Auth(auth): Auth,
75    Path(id): Path<Uuid>,
76    Json(req): Json<UpdateUpstreamRequest>,
77) -> impl IntoResponse {
78    if let Err(refusal) = require_scope(&auth, "upstreams:write") {
79        return refusal;
80    }
81
82    match state
83        .domain_manager
84        .update_upstream(id, auth.tenant_id, req)
85        .await
86    {
87        Ok(Some(upstream)) => Json(ApiResponse::success(upstream)).into_response(),
88        Ok(None) => (
89            StatusCode::NOT_FOUND,
90            Json(ApiResponse::<()>::error_with_code(
91                "not_found",
92                "Upstream not found",
93            )),
94        )
95            .into_response(),
96        Err(e) => error_response(e).into_response(),
97    }
98}
99
100/// Delete an upstream.
101pub async fn delete_upstream(
102    State(state): State<SaasState>,
103    Auth(auth): Auth,
104    Path(id): Path<Uuid>,
105) -> impl IntoResponse {
106    if let Err(refusal) = require_scope(&auth, "upstreams:write") {
107        return refusal;
108    }
109
110    match state
111        .domain_manager
112        .delete_upstream(id, auth.tenant_id)
113        .await
114    {
115        Ok(true) => Json(ApiResponse::success(())).into_response(),
116        Ok(false) => (
117            StatusCode::NOT_FOUND,
118            Json(ApiResponse::<()>::error_with_code(
119                "not_found",
120                "Upstream not found",
121            )),
122        )
123            .into_response(),
124        Err(e) => error_response(e).into_response(),
125    }
126}
127
128/// Add a backend to an upstream.
129pub async fn add_backend(
130    State(state): State<SaasState>,
131    Auth(auth): Auth,
132    Path(upstream_id): Path<Uuid>,
133    Json(req): Json<CreateBackendRequest>,
134) -> impl IntoResponse {
135    if let Err(refusal) = require_scope(&auth, "upstreams:write") {
136        return refusal;
137    }
138
139    match state
140        .domain_manager
141        .add_backend(upstream_id, auth.tenant_id, req)
142        .await
143    {
144        Ok(backend) => (StatusCode::CREATED, Json(ApiResponse::success(backend))).into_response(),
145        Err(e) => error_response(e).into_response(),
146    }
147}
148
149/// Remove a backend from an upstream.
150pub async fn remove_backend(
151    State(state): State<SaasState>,
152    Auth(auth): Auth,
153    Path((upstream_id, backend_id)): Path<(Uuid, Uuid)>,
154) -> impl IntoResponse {
155    if let Err(refusal) = require_scope(&auth, "upstreams:write") {
156        return refusal;
157    }
158
159    match state
160        .domain_manager
161        .remove_backend(backend_id, upstream_id, auth.tenant_id)
162        .await
163    {
164        Ok(true) => Json(ApiResponse::success(())).into_response(),
165        Ok(false) => (
166            StatusCode::NOT_FOUND,
167            Json(ApiResponse::<()>::error_with_code(
168                "not_found",
169                "Backend not found",
170            )),
171        )
172            .into_response(),
173        Err(e) => error_response(e).into_response(),
174    }
175}