use crate::{
application::services::tenant_service::{Tenant, TenantQuotas},
infrastructure::security::middleware::{Admin, Authenticated},
};
use axum::{Json, extract::State, http::StatusCode};
use serde::{Deserialize, Serialize};
use crate::infrastructure::web::api_v1::AppState;
#[derive(Debug, Deserialize)]
pub struct CreateTenantRequest {
pub id: String,
pub name: String,
pub description: Option<String>,
pub quota_preset: Option<String>, pub quotas: Option<TenantQuotas>,
}
#[derive(Debug, Serialize)]
pub struct TenantResponse {
pub id: String,
pub name: String,
pub description: Option<String>,
pub quotas: TenantQuotas,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub active: bool,
}
impl From<Tenant> for TenantResponse {
fn from(tenant: Tenant) -> Self {
Self {
id: tenant.id,
name: tenant.name,
description: tenant.description,
quotas: tenant.quotas,
created_at: tenant.created_at,
updated_at: tenant.updated_at,
active: tenant.active,
}
}
}
#[derive(Debug, Deserialize)]
pub struct UpdateQuotasRequest {
pub quotas: TenantQuotas,
}
pub async fn create_tenant_handler(
State(state): State<AppState>,
Admin(_): Admin,
Json(req): Json<CreateTenantRequest>,
) -> Result<(StatusCode, Json<TenantResponse>), (StatusCode, String)> {
let quotas = if let Some(quotas) = req.quotas {
quotas
} else if let Some(preset) = req.quota_preset {
match preset.as_str() {
"free" => TenantQuotas::free_tier(),
"professional" => TenantQuotas::professional(),
"unlimited" => TenantQuotas::unlimited(),
_ => TenantQuotas::default(),
}
} else {
TenantQuotas::default()
};
let mut tenant = state
.tenant_manager
.create_tenant(req.id, req.name, quotas)
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
if let Some(desc) = req.description {
tenant.description = Some(desc);
}
Ok((StatusCode::CREATED, Json(tenant.into())))
}
pub async fn get_tenant_handler(
State(state): State<AppState>,
Authenticated(auth_ctx): Authenticated,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
) -> Result<Json<TenantResponse>, (StatusCode, String)> {
if tenant_id != auth_ctx.tenant_id() {
auth_ctx
.require_permission(crate::infrastructure::security::auth::Permission::Admin)
.map_err(|_| {
(
StatusCode::FORBIDDEN,
"Can only view own tenant".to_string(),
)
})?;
}
let tenant = state
.tenant_manager
.get_tenant(&tenant_id)
.map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
Ok(Json(tenant.into()))
}
pub async fn list_tenants_handler(
State(state): State<AppState>,
Admin(_): Admin,
) -> Json<Vec<TenantResponse>> {
let tenants = state.tenant_manager.list_tenants();
Json(tenants.into_iter().map(TenantResponse::from).collect())
}
pub async fn get_tenant_stats_handler(
State(state): State<AppState>,
Authenticated(auth_ctx): Authenticated,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
if tenant_id != auth_ctx.tenant_id() {
auth_ctx
.require_permission(crate::infrastructure::security::auth::Permission::Admin)
.map_err(|_| {
(
StatusCode::FORBIDDEN,
"Can only view own tenant stats".to_string(),
)
})?;
}
let stats = state
.tenant_manager
.get_stats(&tenant_id)
.map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
Ok(Json(stats))
}
pub async fn update_quotas_handler(
State(state): State<AppState>,
Admin(_): Admin,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
Json(req): Json<UpdateQuotasRequest>,
) -> Result<StatusCode, (StatusCode, String)> {
state
.tenant_manager
.update_quotas(&tenant_id, req.quotas)
.map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
Ok(StatusCode::NO_CONTENT)
}
pub async fn deactivate_tenant_handler(
State(state): State<AppState>,
Admin(_): Admin,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
state
.tenant_manager
.deactivate_tenant(&tenant_id)
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(StatusCode::NO_CONTENT)
}
pub async fn activate_tenant_handler(
State(state): State<AppState>,
Admin(_): Admin,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
state
.tenant_manager
.activate_tenant(&tenant_id)
.map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
Ok(StatusCode::NO_CONTENT)
}
pub async fn delete_tenant_handler(
State(state): State<AppState>,
Admin(_): Admin,
axum::extract::Path(tenant_id): axum::extract::Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
state
.tenant_manager
.delete_tenant(&tenant_id)
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(StatusCode::NO_CONTENT)
}