use axum::{
extract::{Path, State},
http::StatusCode,
Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::router::AppState;
use crate::db::{ArtifactsRepository, DataPlane, DataPlanesRepository, ProjectsRepository};
use crate::error::ProblemDetails;
pub async fn list_data_planes(
State(state): State<AppState>,
Path(project_id): Path<Uuid>,
) -> Result<Json<Vec<DataPlane>>, ProblemDetails> {
let projects_repo = ProjectsRepository::new(state.pool.clone());
let _ = projects_repo
.get_by_id(project_id)
.await?
.ok_or_else(|| ProblemDetails::not_found(format!("Project {} not found", project_id)))?;
let repo = DataPlanesRepository::new(state.pool.clone());
let data_planes = repo.list_for_project(project_id).await.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!("Failed to list data planes: {}", e))
})?;
Ok(Json(data_planes))
}
pub async fn get_data_plane(
State(state): State<AppState>,
Path((project_id, dp_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<DataPlane>, ProblemDetails> {
let repo = DataPlanesRepository::new(state.pool.clone());
let data_plane = repo
.get(dp_id)
.await
.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!("Failed to get data plane: {}", e))
})?
.ok_or_else(|| ProblemDetails::not_found(format!("Data plane {} not found", dp_id)))?;
if data_plane.project_id != project_id {
return Err(ProblemDetails::not_found(format!(
"Data plane {} not found in project {}",
dp_id, project_id
)));
}
Ok(Json(data_plane))
}
pub async fn disconnect_data_plane(
State(state): State<AppState>,
Path((project_id, dp_id)): Path<(Uuid, Uuid)>,
) -> Result<StatusCode, ProblemDetails> {
let repo = DataPlanesRepository::new(state.pool.clone());
let data_plane = repo
.get(dp_id)
.await
.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!("Failed to get data plane: {}", e))
})?
.ok_or_else(|| ProblemDetails::not_found(format!("Data plane {} not found", dp_id)))?;
if data_plane.project_id != project_id {
return Err(ProblemDetails::not_found(format!(
"Data plane {} not found in project {}",
dp_id, project_id
)));
}
state.connection_manager.remove(dp_id);
repo.delete(dp_id).await.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!("Failed to delete data plane: {}", e))
})?;
Ok(StatusCode::NO_CONTENT)
}
#[derive(Debug, Deserialize)]
pub struct DeployRequest {
pub artifact_id: Option<Uuid>,
}
#[derive(Debug, Serialize)]
pub struct DeployResponse {
pub artifact_id: Uuid,
pub data_planes_notified: usize,
}
pub async fn deploy_to_data_planes(
State(state): State<AppState>,
Path(project_id): Path<Uuid>,
Json(request): Json<DeployRequest>,
) -> Result<(StatusCode, Json<DeployResponse>), ProblemDetails> {
let artifacts_repo = ArtifactsRepository::new(state.pool.clone());
let artifact = if let Some(artifact_id) = request.artifact_id {
artifacts_repo
.get(artifact_id)
.await
.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!("Failed to get artifact: {}", e))
})?
.ok_or_else(|| {
ProblemDetails::not_found(format!("Artifact {} not found", artifact_id))
})?
} else {
let artifacts = artifacts_repo
.list_for_project(project_id)
.await
.map_err(|e| {
ProblemDetails::internal_error_with_detail(format!(
"Failed to list artifacts: {}",
e
))
})?;
artifacts.into_iter().next().ok_or_else(|| {
ProblemDetails::not_found(format!("No artifacts found for project {}", project_id))
})?
};
if artifact.project_id != Some(project_id) {
return Err(ProblemDetails::bad_request(format!(
"Artifact {} does not belong to project {}",
artifact.id, project_id
)));
}
let download_url = format!("/artifacts/{}/download", artifact.id);
let data_planes_notified = state
.connection_manager
.project_connection_count(project_id);
state
.connection_manager
.notify_artifact_available(
project_id,
artifact.id,
download_url,
artifact.sha256.clone(),
)
.await;
Ok((
StatusCode::OK,
Json(DeployResponse {
artifact_id: artifact.id,
data_planes_notified,
}),
))
}