allsource_core/infrastructure/web/
tenant_api.rs

1use 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
6// AppState is defined in api_v1.rs
7use crate::infrastructure::web::api_v1::AppState;
8
9// ============================================================================
10// Request/Response Types
11// ============================================================================
12
13#[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>, // "free", "professional", "unlimited"
19    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
52// ============================================================================
53// Handlers
54// ============================================================================
55
56/// Create tenant (admin only)
57/// POST /api/v1/tenants
58pub 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    // Determine quotas
64    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
89/// Get tenant
90/// GET /api/v1/tenants/:id
91pub 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    // Users can only view their own tenant, admins can view any
97    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
116/// List all tenants (admin only)
117/// GET /api/v1/tenants
118pub 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
126/// Get tenant statistics
127/// GET /api/v1/tenants/:id/stats
128pub 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    // Users can only view their own tenant stats
134    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
153/// Update tenant quotas (admin only)
154/// PUT /api/v1/tenants/:id/quotas
155pub 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
169/// Deactivate tenant (admin only)
170/// POST /api/v1/tenants/:id/deactivate
171pub 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
184/// Activate tenant (admin only)
185/// POST /api/v1/tenants/:id/activate
186pub 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
199/// Delete tenant (admin only)
200/// DELETE /api/v1/tenants/:id
201pub 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}