use axum::{
extract::{Path, State},
http::HeaderMap,
Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
error::{ApiError, ApiResult},
middleware::{resolve_org_context, AuthUser},
models::{AuditEventType, OrgRole, OrgTemplate},
AppState,
};
pub async fn list_templates(
State(state): State<AppState>,
AuthUser(user_id): AuthUser,
headers: HeaderMap,
Path(org_id): Path<Uuid>,
) -> ApiResult<Json<TemplateListResponse>> {
let org_ctx = resolve_org_context(&state, user_id, &headers, None)
.await
.map_err(|_| ApiError::AuthRequired)?;
if org_ctx.org_id != org_id {
return Err(ApiError::PermissionDenied);
}
let templates = state.store.list_org_templates_by_org(org_ctx.org_id).await?;
Ok(Json(TemplateListResponse { templates }))
}
pub async fn get_template(
State(state): State<AppState>,
AuthUser(user_id): AuthUser,
headers: HeaderMap,
Path((org_id, template_id)): Path<(Uuid, Uuid)>,
) -> ApiResult<Json<OrgTemplate>> {
let org_ctx = resolve_org_context(&state, user_id, &headers, None)
.await
.map_err(|_| ApiError::AuthRequired)?;
if org_ctx.org_id != org_id {
return Err(ApiError::PermissionDenied);
}
let template = state
.store
.find_org_template_by_id(template_id)
.await?
.ok_or_else(|| ApiError::InvalidRequest("Template not found".to_string()))?;
if template.org_id != org_ctx.org_id {
return Err(ApiError::PermissionDenied);
}
Ok(Json(template))
}
pub async fn create_template(
State(state): State<AppState>,
AuthUser(user_id): AuthUser,
headers: HeaderMap,
Path(org_id): Path<Uuid>,
Json(request): Json<CreateTemplateRequest>,
) -> ApiResult<Json<OrgTemplate>> {
let org_ctx = resolve_org_context(&state, user_id, &headers, None)
.await
.map_err(|_| ApiError::AuthRequired)?;
if org_ctx.org_id != org_id {
return Err(ApiError::PermissionDenied);
}
let is_owner = org_ctx.org.owner_id == user_id;
let is_admin = if !is_owner {
if let Ok(Some(member)) = state.store.find_org_member(org_ctx.org_id, user_id).await {
matches!(member.role(), OrgRole::Admin | OrgRole::Owner)
} else {
false
}
} else {
false
};
if !is_owner && !is_admin {
return Err(ApiError::PermissionDenied);
}
let template = state
.store
.create_org_template(
org_ctx.org_id,
&request.name,
request.description.as_deref(),
request.blueprint_config,
request.security_baseline,
user_id,
request.is_default.unwrap_or(false),
)
.await?;
state
.store
.record_audit_event(
org_ctx.org_id,
Some(user_id),
AuditEventType::SettingsUpdated,
format!("Organization template '{}' created", request.name),
Some(serde_json::json!({
"template_id": template.id,
"template_name": request.name,
"action": "create",
})),
None,
None,
)
.await;
Ok(Json(template))
}
pub async fn update_template(
State(state): State<AppState>,
AuthUser(user_id): AuthUser,
headers: HeaderMap,
Path((org_id, template_id)): Path<(Uuid, Uuid)>,
Json(request): Json<UpdateTemplateRequest>,
) -> ApiResult<Json<OrgTemplate>> {
let org_ctx = resolve_org_context(&state, user_id, &headers, None)
.await
.map_err(|_| ApiError::AuthRequired)?;
if org_ctx.org_id != org_id {
return Err(ApiError::PermissionDenied);
}
let is_owner = org_ctx.org.owner_id == user_id;
let is_admin = if !is_owner {
if let Ok(Some(member)) = state.store.find_org_member(org_ctx.org_id, user_id).await {
matches!(member.role(), OrgRole::Admin | OrgRole::Owner)
} else {
false
}
} else {
false
};
if !is_owner && !is_admin {
return Err(ApiError::PermissionDenied);
}
let template = state
.store
.find_org_template_by_id(template_id)
.await?
.ok_or_else(|| ApiError::InvalidRequest("Template not found".to_string()))?;
if template.org_id != org_ctx.org_id {
return Err(ApiError::PermissionDenied);
}
let updated = state
.store
.update_org_template(
&template,
request.name.as_deref(),
request.description.as_deref(),
request.blueprint_config,
request.security_baseline,
request.is_default,
)
.await?;
state
.store
.record_audit_event(
org_ctx.org_id,
Some(user_id),
AuditEventType::SettingsUpdated,
format!("Organization template '{}' updated", updated.name),
Some(serde_json::json!({
"template_id": template_id,
"action": "update",
})),
None,
None,
)
.await;
Ok(Json(updated))
}
pub async fn delete_template(
State(state): State<AppState>,
AuthUser(user_id): AuthUser,
headers: HeaderMap,
Path((org_id, template_id)): Path<(Uuid, Uuid)>,
) -> ApiResult<Json<DeleteTemplateResponse>> {
let org_ctx = resolve_org_context(&state, user_id, &headers, None)
.await
.map_err(|_| ApiError::AuthRequired)?;
if org_ctx.org_id != org_id {
return Err(ApiError::PermissionDenied);
}
let is_owner = org_ctx.org.owner_id == user_id;
let is_admin = if !is_owner {
if let Ok(Some(member)) = state.store.find_org_member(org_ctx.org_id, user_id).await {
matches!(member.role(), OrgRole::Admin | OrgRole::Owner)
} else {
false
}
} else {
false
};
if !is_owner && !is_admin {
return Err(ApiError::PermissionDenied);
}
let template = state
.store
.find_org_template_by_id(template_id)
.await?
.ok_or_else(|| ApiError::InvalidRequest("Template not found".to_string()))?;
if template.org_id != org_ctx.org_id {
return Err(ApiError::PermissionDenied);
}
state.store.delete_org_template(template_id).await?;
state
.store
.record_audit_event(
org_ctx.org_id,
Some(user_id),
AuditEventType::SettingsUpdated,
format!("Organization template '{}' deleted", template.name),
Some(serde_json::json!({
"template_id": template_id,
"action": "delete",
})),
None,
None,
)
.await;
Ok(Json(DeleteTemplateResponse {
success: true,
message: "Template deleted successfully".to_string(),
}))
}
#[derive(Debug, Deserialize)]
pub struct CreateTemplateRequest {
pub name: String,
pub description: Option<String>,
pub blueprint_config: Option<serde_json::Value>,
pub security_baseline: Option<serde_json::Value>,
pub is_default: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateTemplateRequest {
pub name: Option<String>,
pub description: Option<String>,
pub blueprint_config: Option<serde_json::Value>,
pub security_baseline: Option<serde_json::Value>,
pub is_default: Option<bool>,
}
#[derive(Debug, Serialize)]
pub struct TemplateListResponse {
pub templates: Vec<OrgTemplate>,
}
#[derive(Debug, Serialize)]
pub struct DeleteTemplateResponse {
pub success: bool,
pub message: String,
}