/// Transaction API routes. /// /// POST /tx/apply — apply a command /// POST /tx/rollback/{id} — roll back a command /// GET /tx/history?since={ms} — command history /// GET /tx/chain/{id} — causal chain for a command use axum::{ extract::{Path, Query, State}, http::StatusCode, Json, }; use engram_tx::{Command, CommandResult}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; use crate::state::AppState; // ── POST /tx/apply ──────────────────────────────────────────────────────────── pub async fn tx_apply( State(state): State>, Json(cmd): Json, ) -> Result, StatusCode> { let mut engine = state .tx_engine .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let result = engine .apply(cmd) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(result)) } // ── POST /tx/rollback/{command_id} ─────────────────────────────────────────── #[derive(Serialize)] pub struct RollbackResponse { pub rollback_command_id: Uuid, pub status: String, } pub async fn tx_rollback( State(state): State>, Path(command_id): Path, ) -> Result, StatusCode> { let mut engine = state .tx_engine .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let rb = engine .rollback(command_id) .map_err(|e| { tracing::warn!("rollback failed: {}", e); StatusCode::BAD_REQUEST })?; Ok(Json(RollbackResponse { rollback_command_id: rb.id, status: format!("{:?}", rb.status), })) } // ── GET /tx/history ─────────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct HistoryParams { pub since: Option, } #[derive(Serialize)] pub struct HistoryResponse { pub commands: Vec, } pub async fn tx_history( State(state): State>, Query(params): Query, ) -> Result, StatusCode> { let engine = state .tx_engine .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let since = params.since.unwrap_or(0); let commands = engine .history(since) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(HistoryResponse { commands })) } // ── GET /tx/chain/{command_id} ──────────────────────────────────────────────── #[derive(Serialize)] pub struct CausalChainResponse { pub chain: Vec, } pub async fn tx_causal_chain( State(state): State>, Path(command_id): Path, ) -> Result, StatusCode> { let engine = state .tx_engine .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let chain = engine .causal_chain(command_id) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(CausalChainResponse { chain })) }