Skip to main content

allsource_core/infrastructure/web/
tenant_api.rs

1use 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
8// AppState is defined in api_v1.rs
9use crate::infrastructure::web::api_v1::AppState;
10
11// ============================================================================
12// Request/Response Types
13// ============================================================================
14
15#[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>, // "free", "professional", "unlimited"
21    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
54// ============================================================================
55// Handlers
56// ============================================================================
57
58/// Create tenant (admin only)
59/// POST /api/v1/tenants
60pub 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    // Determine quotas
66    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
91/// Get tenant
92/// GET /api/v1/tenants/:id
93pub 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    // Users can only view their own tenant, admins can view any
99    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
118/// List all tenants (admin only)
119/// GET /api/v1/tenants
120pub 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
128/// Get tenant statistics
129/// GET /api/v1/tenants/:id/stats
130pub 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    // Users can only view their own tenant stats
136    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
155/// Update tenant quotas (admin only)
156/// PUT /api/v1/tenants/:id/quotas
157pub 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
171/// Deactivate tenant (admin only)
172/// POST /api/v1/tenants/:id/deactivate
173pub 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
186/// Activate tenant (admin only)
187/// POST /api/v1/tenants/:id/activate
188pub 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
201/// Delete tenant (admin only)
202/// DELETE /api/v1/tenants/:id
203pub 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}