allsource_core/
tenant_api.rs1use 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
10use crate::api_v1::AppState;
12
13#[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>, 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
56pub 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 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
93pub 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 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
115pub 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
125pub 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 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
147pub 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
163pub 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
178pub 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
193pub 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}