allsource_core/
tenant_api.rs

1use crate::middleware::{Admin, Authenticated};
2use crate::tenant::{Tenant, TenantQuotas};
3use axum::{
4    extract::State,
5    http::StatusCode,
6    Json,
7};
8use serde::{Deserialize, Serialize};
9
10// AppState is defined in api_v1.rs
11use crate::api_v1::AppState;
12
13// ============================================================================
14// Request/Response Types
15// ============================================================================
16
17#[derive(Debug, Deserialize)]
18pub struct CreateTenantRequest {
19    pub id: String,
20    pub name: String,
21    pub description: Option<String>,
22    pub quota_preset: Option<String>, // "free", "professional", "unlimited"
23    pub quotas: Option<TenantQuotas>,
24}
25
26#[derive(Debug, Serialize)]
27pub struct TenantResponse {
28    pub id: String,
29    pub name: String,
30    pub description: Option<String>,
31    pub quotas: TenantQuotas,
32    pub created_at: chrono::DateTime<chrono::Utc>,
33    pub updated_at: chrono::DateTime<chrono::Utc>,
34    pub active: bool,
35}
36
37impl From<Tenant> for TenantResponse {
38    fn from(tenant: Tenant) -> Self {
39        Self {
40            id: tenant.id,
41            name: tenant.name,
42            description: tenant.description,
43            quotas: tenant.quotas,
44            created_at: tenant.created_at,
45            updated_at: tenant.updated_at,
46            active: tenant.active,
47        }
48    }
49}
50
51#[derive(Debug, Deserialize)]
52pub struct UpdateQuotasRequest {
53    pub quotas: TenantQuotas,
54}
55
56// ============================================================================
57// Handlers
58// ============================================================================
59
60/// Create tenant (admin only)
61/// POST /api/v1/tenants
62pub async fn create_tenant_handler(
63    State(state): State<AppState>,
64    Admin(_): Admin,
65    Json(req): Json<CreateTenantRequest>,
66) -> Result<(StatusCode, Json<TenantResponse>), (StatusCode, String)> {
67    // Determine quotas
68    let quotas = if let Some(quotas) = req.quotas {
69        quotas
70    } else if let Some(preset) = req.quota_preset {
71        match preset.as_str() {
72            "free" => TenantQuotas::free_tier(),
73            "professional" => TenantQuotas::professional(),
74            "unlimited" => TenantQuotas::unlimited(),
75            _ => TenantQuotas::default(),
76        }
77    } else {
78        TenantQuotas::default()
79    };
80
81    let mut tenant = state
82        .tenant_manager
83        .create_tenant(req.id, req.name, quotas)
84        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
85
86    if let Some(desc) = req.description {
87        tenant.description = Some(desc);
88    }
89
90    Ok((StatusCode::CREATED, Json(tenant.into())))
91}
92
93/// Get tenant
94/// GET /api/v1/tenants/:id
95pub async fn get_tenant_handler(
96    State(state): State<AppState>,
97    Authenticated(auth_ctx): Authenticated,
98    axum::extract::Path(tenant_id): axum::extract::Path<String>,
99) -> Result<Json<TenantResponse>, (StatusCode, String)> {
100    // Users can only view their own tenant, admins can view any
101    if tenant_id != auth_ctx.tenant_id() {
102        auth_ctx
103            .require_permission(crate::auth::Permission::Admin)
104            .map_err(|_| (StatusCode::FORBIDDEN, "Can only view own tenant".to_string()))?;
105    }
106
107    let tenant = state
108        .tenant_manager
109        .get_tenant(&tenant_id)
110        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
111
112    Ok(Json(tenant.into()))
113}
114
115/// List all tenants (admin only)
116/// GET /api/v1/tenants
117pub async fn list_tenants_handler(
118    State(state): State<AppState>,
119    Admin(_): Admin,
120) -> Json<Vec<TenantResponse>> {
121    let tenants = state.tenant_manager.list_tenants();
122    Json(tenants.into_iter().map(TenantResponse::from).collect())
123}
124
125/// Get tenant statistics
126/// GET /api/v1/tenants/:id/stats
127pub async fn get_tenant_stats_handler(
128    State(state): State<AppState>,
129    Authenticated(auth_ctx): Authenticated,
130    axum::extract::Path(tenant_id): axum::extract::Path<String>,
131) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
132    // Users can only view their own tenant stats
133    if tenant_id != auth_ctx.tenant_id() {
134        auth_ctx
135            .require_permission(crate::auth::Permission::Admin)
136            .map_err(|_| (StatusCode::FORBIDDEN, "Can only view own tenant stats".to_string()))?;
137    }
138
139    let stats = state
140        .tenant_manager
141        .get_stats(&tenant_id)
142        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
143
144    Ok(Json(stats))
145}
146
147/// Update tenant quotas (admin only)
148/// PUT /api/v1/tenants/:id/quotas
149pub async fn update_quotas_handler(
150    State(state): State<AppState>,
151    Admin(_): Admin,
152    axum::extract::Path(tenant_id): axum::extract::Path<String>,
153    Json(req): Json<UpdateQuotasRequest>,
154) -> Result<StatusCode, (StatusCode, String)> {
155    state
156        .tenant_manager
157        .update_quotas(&tenant_id, req.quotas)
158        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
159
160    Ok(StatusCode::NO_CONTENT)
161}
162
163/// Deactivate tenant (admin only)
164/// POST /api/v1/tenants/:id/deactivate
165pub async fn deactivate_tenant_handler(
166    State(state): State<AppState>,
167    Admin(_): Admin,
168    axum::extract::Path(tenant_id): axum::extract::Path<String>,
169) -> Result<StatusCode, (StatusCode, String)> {
170    state
171        .tenant_manager
172        .deactivate_tenant(&tenant_id)
173        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
174
175    Ok(StatusCode::NO_CONTENT)
176}
177
178/// Activate tenant (admin only)
179/// POST /api/v1/tenants/:id/activate
180pub async fn activate_tenant_handler(
181    State(state): State<AppState>,
182    Admin(_): Admin,
183    axum::extract::Path(tenant_id): axum::extract::Path<String>,
184) -> Result<StatusCode, (StatusCode, String)> {
185    state
186        .tenant_manager
187        .activate_tenant(&tenant_id)
188        .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
189
190    Ok(StatusCode::NO_CONTENT)
191}
192
193/// Delete tenant (admin only)
194/// DELETE /api/v1/tenants/:id
195pub async fn delete_tenant_handler(
196    State(state): State<AppState>,
197    Admin(_): Admin,
198    axum::extract::Path(tenant_id): axum::extract::Path<String>,
199) -> Result<StatusCode, (StatusCode, String)> {
200    state
201        .tenant_manager
202        .delete_tenant(&tenant_id)
203        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
204
205    Ok(StatusCode::NO_CONTENT)
206}