allsource_core/infrastructure/web/
tenant_api.rs1use crate::application::services::tenant_service::{Tenant, TenantQuotas};
2use crate::infrastructure::security::middleware::{Admin, Authenticated};
3use axum::{extract::State, http::StatusCode, Json};
4use serde::{Deserialize, Serialize};
5
6use crate::infrastructure::web::api_v1::AppState;
8
9#[derive(Debug, Deserialize)]
14pub struct CreateTenantRequest {
15 pub id: String,
16 pub name: String,
17 pub description: Option<String>,
18 pub quota_preset: Option<String>, pub quotas: Option<TenantQuotas>,
20}
21
22#[derive(Debug, Serialize)]
23pub struct TenantResponse {
24 pub id: String,
25 pub name: String,
26 pub description: Option<String>,
27 pub quotas: TenantQuotas,
28 pub created_at: chrono::DateTime<chrono::Utc>,
29 pub updated_at: chrono::DateTime<chrono::Utc>,
30 pub active: bool,
31}
32
33impl From<Tenant> for TenantResponse {
34 fn from(tenant: Tenant) -> Self {
35 Self {
36 id: tenant.id,
37 name: tenant.name,
38 description: tenant.description,
39 quotas: tenant.quotas,
40 created_at: tenant.created_at,
41 updated_at: tenant.updated_at,
42 active: tenant.active,
43 }
44 }
45}
46
47#[derive(Debug, Deserialize)]
48pub struct UpdateQuotasRequest {
49 pub quotas: TenantQuotas,
50}
51
52pub async fn create_tenant_handler(
59 State(state): State<AppState>,
60 Admin(_): Admin,
61 Json(req): Json<CreateTenantRequest>,
62) -> Result<(StatusCode, Json<TenantResponse>), (StatusCode, String)> {
63 let quotas = if let Some(quotas) = req.quotas {
65 quotas
66 } else if let Some(preset) = req.quota_preset {
67 match preset.as_str() {
68 "free" => TenantQuotas::free_tier(),
69 "professional" => TenantQuotas::professional(),
70 "unlimited" => TenantQuotas::unlimited(),
71 _ => TenantQuotas::default(),
72 }
73 } else {
74 TenantQuotas::default()
75 };
76
77 let mut tenant = state
78 .tenant_manager
79 .create_tenant(req.id, req.name, quotas)
80 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
81
82 if let Some(desc) = req.description {
83 tenant.description = Some(desc);
84 }
85
86 Ok((StatusCode::CREATED, Json(tenant.into())))
87}
88
89pub async fn get_tenant_handler(
92 State(state): State<AppState>,
93 Authenticated(auth_ctx): Authenticated,
94 axum::extract::Path(tenant_id): axum::extract::Path<String>,
95) -> Result<Json<TenantResponse>, (StatusCode, String)> {
96 if tenant_id != auth_ctx.tenant_id() {
98 auth_ctx
99 .require_permission(crate::infrastructure::security::auth::Permission::Admin)
100 .map_err(|_| {
101 (
102 StatusCode::FORBIDDEN,
103 "Can only view own tenant".to_string(),
104 )
105 })?;
106 }
107
108 let tenant = state
109 .tenant_manager
110 .get_tenant(&tenant_id)
111 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
112
113 Ok(Json(tenant.into()))
114}
115
116pub async fn list_tenants_handler(
119 State(state): State<AppState>,
120 Admin(_): Admin,
121) -> Json<Vec<TenantResponse>> {
122 let tenants = state.tenant_manager.list_tenants();
123 Json(tenants.into_iter().map(TenantResponse::from).collect())
124}
125
126pub async fn get_tenant_stats_handler(
129 State(state): State<AppState>,
130 Authenticated(auth_ctx): Authenticated,
131 axum::extract::Path(tenant_id): axum::extract::Path<String>,
132) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
133 if tenant_id != auth_ctx.tenant_id() {
135 auth_ctx
136 .require_permission(crate::infrastructure::security::auth::Permission::Admin)
137 .map_err(|_| {
138 (
139 StatusCode::FORBIDDEN,
140 "Can only view own tenant stats".to_string(),
141 )
142 })?;
143 }
144
145 let stats = state
146 .tenant_manager
147 .get_stats(&tenant_id)
148 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
149
150 Ok(Json(stats))
151}
152
153pub async fn update_quotas_handler(
156 State(state): State<AppState>,
157 Admin(_): Admin,
158 axum::extract::Path(tenant_id): axum::extract::Path<String>,
159 Json(req): Json<UpdateQuotasRequest>,
160) -> Result<StatusCode, (StatusCode, String)> {
161 state
162 .tenant_manager
163 .update_quotas(&tenant_id, req.quotas)
164 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
165
166 Ok(StatusCode::NO_CONTENT)
167}
168
169pub async fn deactivate_tenant_handler(
172 State(state): State<AppState>,
173 Admin(_): Admin,
174 axum::extract::Path(tenant_id): axum::extract::Path<String>,
175) -> Result<StatusCode, (StatusCode, String)> {
176 state
177 .tenant_manager
178 .deactivate_tenant(&tenant_id)
179 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
180
181 Ok(StatusCode::NO_CONTENT)
182}
183
184pub async fn activate_tenant_handler(
187 State(state): State<AppState>,
188 Admin(_): Admin,
189 axum::extract::Path(tenant_id): axum::extract::Path<String>,
190) -> Result<StatusCode, (StatusCode, String)> {
191 state
192 .tenant_manager
193 .activate_tenant(&tenant_id)
194 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
195
196 Ok(StatusCode::NO_CONTENT)
197}
198
199pub async fn delete_tenant_handler(
202 State(state): State<AppState>,
203 Admin(_): Admin,
204 axum::extract::Path(tenant_id): axum::extract::Path<String>,
205) -> Result<StatusCode, (StatusCode, String)> {
206 state
207 .tenant_manager
208 .delete_tenant(&tenant_id)
209 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
210
211 Ok(StatusCode::NO_CONTENT)
212}