allsource_core/infrastructure/web/
tenant_api.rs1use crate::{
2 application::services::tenant_service::{Tenant, TenantQuotas},
3 infrastructure::security::middleware::{Admin, Authenticated},
4};
5use axum::{Json, extract::State, http::StatusCode};
6use chrono::Utc;
7use serde::{Deserialize, Serialize};
8
9use crate::infrastructure::web::api_v1::AppState;
11
12#[derive(Debug, Deserialize)]
17pub struct CreateTenantRequest {
18 pub id: String,
19 pub name: String,
20 pub description: Option<String>,
21 pub quota_preset: Option<String>, pub quotas: Option<TenantQuotas>,
23 #[serde(default)]
24 pub is_demo: bool,
25}
26
27#[derive(Debug, Serialize)]
28pub struct TenantResponse {
29 pub id: String,
30 pub name: String,
31 pub description: Option<String>,
32 pub quotas: TenantQuotas,
33 pub created_at: chrono::DateTime<chrono::Utc>,
34 pub updated_at: chrono::DateTime<chrono::Utc>,
35 pub active: bool,
36 pub is_demo: bool,
37}
38
39impl From<Tenant> for TenantResponse {
40 fn from(tenant: Tenant) -> Self {
41 Self {
42 id: tenant.id,
43 name: tenant.name,
44 description: tenant.description,
45 quotas: tenant.quotas,
46 created_at: tenant.created_at,
47 updated_at: tenant.updated_at,
48 active: tenant.active,
49 is_demo: tenant.is_demo,
50 }
51 }
52}
53
54#[derive(Debug, Deserialize)]
55pub struct UpdateTenantRequest {
56 pub name: Option<String>,
57 pub description: Option<String>,
58 pub is_demo: Option<bool>,
59 pub quotas: Option<TenantQuotas>,
60}
61
62#[derive(Debug, Deserialize)]
63pub struct UpdateQuotasRequest {
64 pub quotas: TenantQuotas,
65}
66
67pub async fn create_tenant_handler(
74 State(state): State<AppState>,
75 Admin(_): Admin,
76 Json(req): Json<CreateTenantRequest>,
77) -> Result<(StatusCode, Json<TenantResponse>), (StatusCode, String)> {
78 let quotas = if let Some(quotas) = req.quotas {
80 quotas
81 } else if let Some(preset) = req.quota_preset {
82 match preset.as_str() {
83 "free" => TenantQuotas::free_tier(),
84 "professional" => TenantQuotas::professional(),
85 "unlimited" => TenantQuotas::unlimited(),
86 _ => TenantQuotas::default(),
87 }
88 } else {
89 TenantQuotas::default()
90 };
91
92 let mut tenant = state
93 .tenant_manager
94 .create_tenant(req.id, req.name, quotas)
95 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
96
97 if let Some(desc) = req.description {
98 tenant.description = Some(desc);
99 }
100 tenant.is_demo = req.is_demo;
101
102 state
104 .tenant_manager
105 .save_tenant(&tenant)
106 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
107
108 Ok((StatusCode::CREATED, Json(tenant.into())))
109}
110
111pub async fn get_tenant_handler(
114 State(state): State<AppState>,
115 Authenticated(auth_ctx): Authenticated,
116 axum::extract::Path(tenant_id): axum::extract::Path<String>,
117) -> Result<Json<TenantResponse>, (StatusCode, String)> {
118 if tenant_id != auth_ctx.tenant_id() {
120 auth_ctx
121 .require_permission(crate::infrastructure::security::auth::Permission::Admin)
122 .map_err(|_| {
123 (
124 StatusCode::FORBIDDEN,
125 "Can only view own tenant".to_string(),
126 )
127 })?;
128 }
129
130 let tenant = state
131 .tenant_manager
132 .get_tenant(&tenant_id)
133 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
134
135 Ok(Json(tenant.into()))
136}
137
138pub async fn list_tenants_handler(
141 State(state): State<AppState>,
142 Admin(_): Admin,
143) -> Json<Vec<TenantResponse>> {
144 let tenants = state.tenant_manager.list_tenants();
145 Json(tenants.into_iter().map(TenantResponse::from).collect())
146}
147
148pub async fn get_tenant_stats_handler(
151 State(state): State<AppState>,
152 Authenticated(auth_ctx): Authenticated,
153 axum::extract::Path(tenant_id): axum::extract::Path<String>,
154) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
155 if tenant_id != auth_ctx.tenant_id() {
157 auth_ctx
158 .require_permission(crate::infrastructure::security::auth::Permission::Admin)
159 .map_err(|_| {
160 (
161 StatusCode::FORBIDDEN,
162 "Can only view own tenant stats".to_string(),
163 )
164 })?;
165 }
166
167 let stats = state
168 .tenant_manager
169 .get_stats(&tenant_id)
170 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
171
172 Ok(Json(stats))
173}
174
175pub async fn update_quotas_handler(
178 State(state): State<AppState>,
179 Admin(_): Admin,
180 axum::extract::Path(tenant_id): axum::extract::Path<String>,
181 Json(req): Json<UpdateQuotasRequest>,
182) -> Result<StatusCode, (StatusCode, String)> {
183 state
184 .tenant_manager
185 .update_quotas(&tenant_id, req.quotas)
186 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
187
188 Ok(StatusCode::NO_CONTENT)
189}
190
191pub async fn deactivate_tenant_handler(
194 State(state): State<AppState>,
195 Admin(_): Admin,
196 axum::extract::Path(tenant_id): axum::extract::Path<String>,
197) -> Result<StatusCode, (StatusCode, String)> {
198 state
199 .tenant_manager
200 .deactivate_tenant(&tenant_id)
201 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
202
203 Ok(StatusCode::NO_CONTENT)
204}
205
206pub async fn activate_tenant_handler(
209 State(state): State<AppState>,
210 Admin(_): Admin,
211 axum::extract::Path(tenant_id): axum::extract::Path<String>,
212) -> Result<StatusCode, (StatusCode, String)> {
213 state
214 .tenant_manager
215 .activate_tenant(&tenant_id)
216 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
217
218 Ok(StatusCode::NO_CONTENT)
219}
220
221pub async fn update_tenant_handler(
224 State(state): State<AppState>,
225 Admin(_): Admin,
226 axum::extract::Path(tenant_id): axum::extract::Path<String>,
227 Json(req): Json<UpdateTenantRequest>,
228) -> Result<Json<TenantResponse>, (StatusCode, String)> {
229 let mut tenant = state
230 .tenant_manager
231 .get_tenant(&tenant_id)
232 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
233
234 if let Some(name) = req.name {
235 tenant.name = name;
236 }
237 if let Some(desc) = req.description {
238 tenant.description = Some(desc);
239 }
240 if let Some(is_demo) = req.is_demo {
241 tenant.is_demo = is_demo;
242 }
243 if let Some(quotas) = req.quotas {
244 tenant.quotas = quotas;
245 }
246 tenant.updated_at = Utc::now();
247
248 state
249 .tenant_manager
250 .save_tenant(&tenant)
251 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
252
253 Ok(Json(tenant.into()))
254}
255
256pub async fn delete_tenant_handler(
259 State(state): State<AppState>,
260 Admin(_): Admin,
261 axum::extract::Path(tenant_id): axum::extract::Path<String>,
262) -> Result<StatusCode, (StatusCode, String)> {
263 state
264 .tenant_manager
265 .delete_tenant(&tenant_id)
266 .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
267
268 Ok(StatusCode::NO_CONTENT)
269}