feat(api): add API routes with full state wiring and integration test fixtures

- Add /api routes for agents and proxy resources

- Wire all services into ApiState in service/mod.rs

- Update route tests with all mock services
This commit is contained in:
GW_MC
2026-07-05 05:29:22 +00:00
parent a7863b9e87
commit 6f560c981b
26 changed files with 2877 additions and 4 deletions

View File

@@ -0,0 +1,133 @@
use std::sync::Arc;
use axum::{
Json,
extract::{Path, State},
http::StatusCode,
response::IntoResponse,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::routes::api::{AppError, ApiRouter};
use crate::service::proxy::limit_zone::{
CreateLimitZoneParams, LimitZoneService, UpdateLimitZoneParams,
};
use crate::service::proxy::types::LimitZoneConfig;
#[derive(Serialize)]
pub(crate) struct LimitZoneResponse {
pub id: Uuid,
pub name: String,
pub key: String,
pub rate: String,
pub override_of_id: Option<Uuid>,
}
impl From<LimitZoneConfig> for LimitZoneResponse {
fn from(c: LimitZoneConfig) -> Self {
Self {
id: c.id,
name: c.name,
key: c.key,
rate: c.rate,
override_of_id: c.override_of_id,
}
}
}
#[derive(Deserialize)]
pub(crate) struct CreateLimitZoneRequest {
pub name: String,
pub key: String,
pub rate: String,
pub override_of_id: Option<Uuid>,
}
impl From<CreateLimitZoneRequest> for CreateLimitZoneParams {
fn from(r: CreateLimitZoneRequest) -> Self {
Self {
name: r.name,
key: r.key,
rate: r.rate,
override_of_id: r.override_of_id,
}
}
}
#[derive(Deserialize)]
pub(crate) struct UpdateLimitZoneRequest {
pub name: Option<String>,
pub key: Option<String>,
pub rate: Option<String>,
pub override_of_id: Option<Option<Uuid>>,
}
impl From<UpdateLimitZoneRequest> for UpdateLimitZoneParams {
fn from(r: UpdateLimitZoneRequest) -> Self {
Self {
name: r.name,
key: r.key,
rate: r.rate,
override_of_id: r.override_of_id,
}
}
}
async fn list_limit_zones(
State(svc): State<Arc<dyn LimitZoneService>>,
) -> Result<Json<Vec<LimitZoneResponse>>, AppError> {
let zones = svc.list().await?;
Ok(Json(zones.into_iter().map(Into::into).collect()))
}
async fn create_limit_zone(
State(svc): State<Arc<dyn LimitZoneService>>,
Json(body): Json<CreateLimitZoneRequest>,
) -> Result<impl IntoResponse, AppError> {
let zone = svc.create(body.into()).await?;
Ok((StatusCode::CREATED, Json(LimitZoneResponse::from(zone))))
}
async fn get_limit_zone(
State(svc): State<Arc<dyn LimitZoneService>>,
Path(id): Path<Uuid>,
) -> Result<Json<LimitZoneResponse>, AppError> {
let zone = svc.get(id).await?;
Ok(Json(zone.into()))
}
async fn update_limit_zone(
State(svc): State<Arc<dyn LimitZoneService>>,
Path(id): Path<Uuid>,
Json(body): Json<UpdateLimitZoneRequest>,
) -> Result<Json<LimitZoneResponse>, AppError> {
let zone = svc.update(id, body.into()).await?;
Ok(Json(zone.into()))
}
async fn delete_limit_zone(
State(svc): State<Arc<dyn LimitZoneService>>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, AppError> {
let deleted = svc.delete(id).await?;
if deleted {
Ok((StatusCode::NO_CONTENT,))
} else {
Err(AppError::NotFound)
}
}
pub(super) fn routes() -> ApiRouter {
ApiRouter::new()
.route(
"/limit-zones",
axum::routing::get(list_limit_zones).post(create_limit_zone),
)
.route(
"/limit-zones/{id}",
axum::routing::get(get_limit_zone)
.put(update_limit_zone)
.delete(delete_limit_zone),
)
}