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