Files
soleprint/soleprint/main.py
buenosairesam d7d3c90152 more decoupling
2026-01-20 09:53:11 -03:00

284 lines
7.6 KiB
Python

"""
Soleprint - Overview and routing hub.
Development workflow and documentation system
👣 Mapping development footprints
Systems:
💉 Artery (artery) - Todo lo vital
🗺️ Atlas (atlas) - Documentación accionable
🎛️ Station (station) - Monitores, Entornos y Herramientas
Routes:
/ → index
/health → health check
/api/data/artery → artery data
/api/data/atlas → atlas data
/api/data/station → station data
/artery/* → proxy to artery service
/atlas/* → proxy to atlas service
/station/* → proxy to station service
"""
import json
import os
from pathlib import Path
from typing import Optional
from dataloader import get_artery_data, get_atlas_data, get_station_data
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
app = FastAPI(title="Soleprint", version="0.1.0")
templates = Jinja2Templates(directory=Path(__file__).parent)
# === Generation Models ===
class FrameworkConfig(BaseModel):
name: str = "soleprint"
icon: Optional[str] = None
class SystemsConfig(BaseModel):
artery: str = "artery"
atlas: str = "atlas"
station: str = "station"
class ManagedConfig(BaseModel):
name: str
repos: dict[str, str]
class GenerationRequest(BaseModel):
room_name: str
framework: FrameworkConfig = FrameworkConfig()
systems: SystemsConfig = SystemsConfig()
managed: Optional[ManagedConfig] = None
# Load config if available
CONFIG_PATH = Path(__file__).parent / "cfg" / "config.json"
CONFIG = {}
if CONFIG_PATH.exists():
CONFIG = json.loads(CONFIG_PATH.read_text())
# Get hub port from config
HUB_PORT = CONFIG.get("framework", {}).get("hub_port", 12000)
@app.get("/health")
def health():
return {
"status": "ok",
"service": "soleprint",
"subsystems": {
"artery": ARTERY_URL,
"atlas": ATLAS_URL,
"station": STATION_URL,
},
}
# === Data API ===
@app.get("/api/data/artery")
def api_artery_data():
"""Data for artery service."""
return get_artery_data()
@app.get("/api/data/atlas")
def api_atlas_data():
"""Data for atlas service."""
return get_atlas_data()
@app.get("/api/data/station")
def api_station_data():
"""Data for station service."""
return get_station_data()
# === Generation API ===
@app.get("/generate")
def generation_ui():
"""Serve the generation UI."""
return FileResponse(Path(__file__).parent / "generate.html")
@app.post("/api/generate")
def generate_config(req: GenerationRequest):
"""Generate a config.json for a new room."""
config = {
"framework": {
"name": req.framework.name,
"slug": req.framework.name.lower().replace(" ", "-"),
"version": "0.1.0",
"description": "Development workflow and documentation system",
"tagline": "Mapping development footprints",
"icon": req.framework.icon or "",
"hub_port": HUB_PORT,
},
"systems": [
{
"key": "data_flow",
"name": req.systems.artery,
"slug": req.systems.artery.lower(),
"title": req.systems.artery.title(),
"tagline": "Todo lo vital",
"icon": "",
},
{
"key": "documentation",
"name": req.systems.atlas,
"slug": req.systems.atlas.lower(),
"title": req.systems.atlas.title(),
"tagline": "Documentacion accionable",
"icon": "",
},
{
"key": "execution",
"name": req.systems.station,
"slug": req.systems.station.lower(),
"title": req.systems.station.title(),
"tagline": "Monitores, Entornos y Herramientas",
"icon": "",
},
],
"components": {
"shared": {
"config": {
"name": "room",
"title": "Room",
"description": "Runtime environment configuration",
"plural": "rooms",
},
"data": {
"name": "depot",
"title": "Depot",
"description": "Data storage / provisions",
"plural": "depots",
},
},
"data_flow": {
"connector": {
"name": "vein",
"title": "Vein",
"description": "Stateless API connector",
"plural": "veins",
},
"mock": {
"name": "shunt",
"title": "Shunt",
"description": "Fake connector for testing",
"plural": "shunts",
},
},
"documentation": {
"library": {
"name": "book",
"title": "Book",
"description": "Documentation library",
},
},
"execution": {
"utility": {
"name": "tool",
"title": "Tool",
"description": "Execution utility",
"plural": "tools",
},
"watcher": {
"name": "monitor",
"title": "Monitor",
"description": "Service monitor",
"plural": "monitors",
},
},
},
}
if req.managed:
config["managed"] = {
"name": req.managed.name,
"repos": req.managed.repos,
}
return {"config": config, "room_name": req.room_name}
@app.post("/api/generate/preview")
def generate_preview(req: GenerationRequest):
"""Preview the generated folder structure."""
room = req.room_name or "room"
fw = req.framework.name or "soleprint"
sys = req.systems
lines = [f'<span class="folder">gen/{room}/</span>']
if req.managed and req.managed.name:
lines.append(f' <span class="folder">{req.managed.name}/</span>')
lines.append(' <span class="folder">link/</span>')
lines.append(f' <span class="folder">{fw}/</span>')
return {"tree": "\n".join(lines)}
@app.get("/")
def index(request: Request):
return templates.TemplateResponse(
"index.html",
{
"request": request,
"artery": ARTERY_EXTERNAL_URL,
"atlas": ATLAS_EXTERNAL_URL,
"station": STATION_EXTERNAL_URL,
},
)
# === Cross-system redirects ===
# These allow soleprint to act as a hub, redirecting to subsystem routes
@app.get("/artery")
@app.get("/artery/{path:path}")
def artery_redirect(path: str = ""):
"""Redirect to artery service."""
return RedirectResponse(url=f"{ARTERY_EXTERNAL_URL}/{path}")
@app.get("/atlas")
@app.get("/atlas/{path:path}")
def atlas_redirect(path: str = ""):
"""Redirect to atlas service."""
return RedirectResponse(url=f"{ATLAS_EXTERNAL_URL}/{path}")
@app.get("/station")
@app.get("/station/{path:path}")
def station_redirect(path: str = ""):
"""Redirect to station service."""
return RedirectResponse(url=f"{STATION_EXTERNAL_URL}/{path}")
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=int(os.getenv("PORT", "12000")),
reload=os.getenv("DEV", "").lower() in ("1", "true"),
)