/// Projection API routes. /// /// POST /projections — register a projection schema /// GET /projections — list all registered schemas /// POST /projections/{name}/query — run activation then project results /// GET /projections/{name}/schema — get a schema definition use axum::{ extract::{Path, State}, http::StatusCode, Json, }; use engram_projection::{ engine::ProjectionEngine, schema::{ProjectionResult, ProjectionSchema}, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; use crate::state::AppState; // ── POST /projections ───────────────────────────────────────────────────────── pub async fn register_projection( State(state): State>, Json(schema): Json, ) -> Result { let mut reg = state .projection_registry .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; reg.upsert(schema) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(StatusCode::CREATED) } // ── GET /projections ────────────────────────────────────────────────────────── #[derive(Serialize)] pub struct ListProjectionsResponse { pub schemas: Vec, } pub async fn list_projections( State(state): State>, ) -> Result, StatusCode> { let reg = state .projection_registry .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let schemas = reg.list().into_iter().cloned().collect(); Ok(Json(ListProjectionsResponse { schemas })) } // ── GET /projections/{name}/schema ──────────────────────────────────────────── pub async fn get_projection_schema( State(state): State>, Path(name): Path, ) -> Result, StatusCode> { let reg = state .projection_registry .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; match reg.get(&name) { Ok(schema) => Ok(Json(schema.clone())), Err(_) => Err(StatusCode::NOT_FOUND), } } // ── POST /projections/{name}/query ──────────────────────────────────────────── #[derive(Deserialize)] pub struct ProjectionQueryRequest { /// Seed node IDs for spreading activation. pub seeds: Vec, /// Query embedding for spreading activation. pub query_embedding: Vec, #[serde(default = "default_depth")] pub max_depth: u8, #[serde(default = "default_limit")] pub limit: usize, } fn default_depth() -> u8 { 3 } fn default_limit() -> usize { 20 } pub async fn query_projection( State(state): State>, Path(name): Path, Json(req): Json, ) -> Result, StatusCode> { // Load schema let schema = { let reg = state .projection_registry .lock() .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; match reg.get(&name) { Ok(s) => s.clone(), Err(_) => return Err(StatusCode::NOT_FOUND), } }; // Run spreading activation let activated = { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; db.activate(&req.seeds, &req.query_embedding, req.max_depth, req.limit) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? }; // Apply projection let result = ProjectionEngine::project(&schema, &activated) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(result)) }