more decoupling
This commit is contained in:
@@ -36,11 +36,8 @@ DB_DUMP=test.sql
|
||||
BACKEND_PORT=8000
|
||||
FRONTEND_PORT=3000
|
||||
|
||||
# Soleprint ports
|
||||
# Soleprint port
|
||||
SOLEPRINT_PORT=12000
|
||||
ARTERY_PORT=12001
|
||||
ATLAS_PORT=12002
|
||||
STATION_PORT=12003
|
||||
|
||||
# =============================================================================
|
||||
# BACKEND SERVER (Uvicorn)
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"slug": "artery",
|
||||
"title": "Artery",
|
||||
"tagline": "Todo lo vital",
|
||||
"port": 12001,
|
||||
"icon": "💉"
|
||||
},
|
||||
{
|
||||
@@ -31,7 +30,6 @@
|
||||
"slug": "atlas",
|
||||
"title": "Atlas",
|
||||
"tagline": "Documentación accionable",
|
||||
"port": 12002,
|
||||
"icon": "🗺️"
|
||||
},
|
||||
{
|
||||
@@ -40,7 +38,6 @@
|
||||
"slug": "station",
|
||||
"title": "Station",
|
||||
"tagline": "Monitores, Entornos y Herramientas",
|
||||
"port": 12003,
|
||||
"icon": "🎛️"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -21,9 +21,6 @@ SOLEPRINT_BARE_PATH=/home/mariano/wdir/spr/gen
|
||||
# PORTS
|
||||
# =============================================================================
|
||||
SOLEPRINT_PORT=12000
|
||||
ARTERY_PORT=12001
|
||||
ATLAS_PORT=12002
|
||||
STATION_PORT=12003
|
||||
|
||||
# =============================================================================
|
||||
# DATABASE (amar's DB for station tools)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"slug": "artery",
|
||||
"title": "Artery",
|
||||
"tagline": "Todo lo vital",
|
||||
"port": 12001,
|
||||
"icon": "💉"
|
||||
},
|
||||
{
|
||||
@@ -24,7 +23,6 @@
|
||||
"slug": "atlas",
|
||||
"title": "Atlas",
|
||||
"tagline": "Documentación accionable",
|
||||
"port": 12002,
|
||||
"icon": "🗺️"
|
||||
},
|
||||
{
|
||||
@@ -33,7 +31,6 @@
|
||||
"slug": "station",
|
||||
"title": "Station",
|
||||
"tagline": "Monitores, Entornos y Herramientas",
|
||||
"port": 12003,
|
||||
"icon": "🎛️"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,79 +1,14 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "arch-model",
|
||||
"slug": "arch-model",
|
||||
"title": "Architecture Model",
|
||||
"status": "ready",
|
||||
"template": null,
|
||||
"larder": {
|
||||
"name": "arch-model",
|
||||
"slug": "arch-model",
|
||||
"title": "Architecture Model",
|
||||
"status": "ready",
|
||||
"source_template": null,
|
||||
"data_path": "album/book/arch-model"
|
||||
},
|
||||
"output_larder": null,
|
||||
"system": "album"
|
||||
},
|
||||
{
|
||||
"name": "feature-flow",
|
||||
"slug": "feature-flow",
|
||||
"title": "Feature Flow Pipeline",
|
||||
"status": "ready",
|
||||
"template": null,
|
||||
"larder": {
|
||||
"name": "feature-flow",
|
||||
"slug": "feature-flow",
|
||||
"title": "Feature Flow Pipeline",
|
||||
"status": "ready",
|
||||
"source_template": null,
|
||||
"data_path": "album/book/feature-flow"
|
||||
},
|
||||
"larder": null,
|
||||
"output_larder": null,
|
||||
"system": "album"
|
||||
},
|
||||
{
|
||||
"name": "gherkin-samples",
|
||||
"slug": "gherkin-samples",
|
||||
"title": "Gherkin Samples",
|
||||
"status": "ready",
|
||||
"template": null,
|
||||
"larder": {
|
||||
"name": "gherkin-samples",
|
||||
"slug": "gherkin-samples",
|
||||
"title": "Gherkin Samples",
|
||||
"status": "ready",
|
||||
"source_template": null,
|
||||
"data_path": "album/book/gherkin-samples"
|
||||
},
|
||||
"output_larder": null,
|
||||
"system": "album"
|
||||
},
|
||||
{
|
||||
"name": "feature-form-samples",
|
||||
"slug": "feature-form-samples",
|
||||
"title": "Feature Form Samples",
|
||||
"status": "ready",
|
||||
"template": {
|
||||
"name": "feature-form",
|
||||
"slug": "feature-form",
|
||||
"title": "Feature Form Template",
|
||||
"status": "ready",
|
||||
"template_path": "album/template/feature-form",
|
||||
"system": "album"
|
||||
},
|
||||
"larder": {
|
||||
"name": "feature-form",
|
||||
"slug": "feature-form",
|
||||
"title": "Feature Forms",
|
||||
"status": "ready",
|
||||
"source_template": "feature-form",
|
||||
"data_path": "album/book/feature-form-samples/feature-form"
|
||||
},
|
||||
"output_larder": null,
|
||||
"system": "album"
|
||||
"system": "atlas"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<ul class="books">
|
||||
{% for book in books %}
|
||||
<li>
|
||||
<a href="/book/{{ book.slug }}/">{{ book.title }}</a>
|
||||
<a href="/atlas/book/{{ book.slug }}/">{{ book.title }}</a>
|
||||
<span
|
||||
class="status {% if book.status == 'ready' %}ready{% elif book.status == 'building' %}building{% endif %}"
|
||||
>{{ book.status | capitalize }}</span
|
||||
|
||||
@@ -3,8 +3,9 @@ Album - Documentation system.
|
||||
"""
|
||||
|
||||
import os
|
||||
import httpx
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -47,11 +48,14 @@ def health():
|
||||
@app.get("/")
|
||||
def index(request: Request):
|
||||
data = get_data()
|
||||
return templates.TemplateResponse("index.html", {
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{
|
||||
"request": request,
|
||||
"pawprint_url": os.getenv("PAWPRINT_EXTERNAL_URL", PAWPRINT_URL),
|
||||
**data,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/data")
|
||||
@@ -103,13 +107,18 @@ def feature_flow_es():
|
||||
def feature_form_samples_index(request: Request):
|
||||
"""Templated book landing page"""
|
||||
data = get_data()
|
||||
book = next((b for b in data.get("books", []) if b["slug"] == "feature-form-samples"), None)
|
||||
book = next(
|
||||
(b for b in data.get("books", []) if b["slug"] == "feature-form-samples"), None
|
||||
)
|
||||
if not book:
|
||||
return HTMLResponse("<h1>Book not found</h1>", status_code=404)
|
||||
return templates.TemplateResponse("book-template.html", {
|
||||
return templates.TemplateResponse(
|
||||
"book-template.html",
|
||||
{
|
||||
"request": request,
|
||||
"book": book,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/book/feature-form-samples/template/", response_class=HTMLResponse)
|
||||
@@ -297,7 +306,10 @@ def feature_form_samples_larder():
|
||||
return HTMLResponse("<h1>Larder index not found</h1>", status_code=404)
|
||||
|
||||
|
||||
@app.get("/book/feature-form-samples/larder/{user_type}/{filename}", response_class=HTMLResponse)
|
||||
@app.get(
|
||||
"/book/feature-form-samples/larder/{user_type}/{filename}",
|
||||
response_class=HTMLResponse,
|
||||
)
|
||||
def feature_form_samples_detail(request: Request, user_type: str, filename: str):
|
||||
"""View a specific feature form"""
|
||||
# Look in the larder subfolder (feature-form)
|
||||
@@ -308,12 +320,15 @@ def feature_form_samples_detail(request: Request, user_type: str, filename: str)
|
||||
|
||||
content = file_path.read_text()
|
||||
detail_file = BOOK_DIR / "feature-form-samples" / "detail.html"
|
||||
return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), {
|
||||
return templates.TemplateResponse(
|
||||
str(detail_file.relative_to(BASE_DIR)),
|
||||
{
|
||||
"request": request,
|
||||
"user_type": user_type,
|
||||
"filename": filename,
|
||||
"content": content,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# --- Book: Gherkin Samples ---
|
||||
@@ -327,8 +342,12 @@ def gherkin_samples_index():
|
||||
return HTMLResponse(html_file.read_text())
|
||||
|
||||
|
||||
@app.get("/book/gherkin-samples/{lang}/{user_type}/{filename}", response_class=HTMLResponse)
|
||||
@app.get("/book/gherkin/{lang}/{user_type}/{filename}", response_class=HTMLResponse) # Alias
|
||||
@app.get(
|
||||
"/book/gherkin-samples/{lang}/{user_type}/{filename}", response_class=HTMLResponse
|
||||
)
|
||||
@app.get(
|
||||
"/book/gherkin/{lang}/{user_type}/{filename}", response_class=HTMLResponse
|
||||
) # Alias
|
||||
def gherkin_samples_detail(request: Request, lang: str, user_type: str, filename: str):
|
||||
"""View a specific gherkin file"""
|
||||
file_path = BOOK_DIR / "gherkin-samples" / lang / user_type / filename
|
||||
@@ -337,17 +356,24 @@ def gherkin_samples_detail(request: Request, lang: str, user_type: str, filename
|
||||
|
||||
content = file_path.read_text()
|
||||
detail_file = BOOK_DIR / "gherkin-samples" / "detail.html"
|
||||
return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), {
|
||||
return templates.TemplateResponse(
|
||||
str(detail_file.relative_to(BASE_DIR)),
|
||||
{
|
||||
"request": request,
|
||||
"lang": lang,
|
||||
"user_type": user_type,
|
||||
"filename": filename,
|
||||
"content": content,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# --- Book: Architecture Model (static site) ---
|
||||
app.mount("/book/arch-model", StaticFiles(directory=str(BOOK_DIR / "arch-model"), html=True), name="arch-model")
|
||||
app.mount(
|
||||
"/book/arch-model",
|
||||
StaticFiles(directory=str(BOOK_DIR / "arch-model"), html=True),
|
||||
name="arch-model",
|
||||
)
|
||||
|
||||
|
||||
# --- Book: Drive Index ---
|
||||
@@ -361,9 +387,10 @@ def drive_index():
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv("PORT", "12002")),
|
||||
port=int(os.getenv("PORT", "12000")),
|
||||
reload=os.getenv("DEV", "").lower() in ("1", "true"),
|
||||
)
|
||||
|
||||
@@ -68,22 +68,8 @@ CONFIG = {}
|
||||
if CONFIG_PATH.exists():
|
||||
CONFIG = json.loads(CONFIG_PATH.read_text())
|
||||
|
||||
# Get ports from config or use defaults
|
||||
# Get hub port from config
|
||||
HUB_PORT = CONFIG.get("framework", {}).get("hub_port", 12000)
|
||||
SYSTEM_PORTS = {s["key"]: s["port"] for s in CONFIG.get("systems", [])}
|
||||
ARTERY_PORT = SYSTEM_PORTS.get("data_flow", 12001)
|
||||
ATLAS_PORT = SYSTEM_PORTS.get("documentation", 12002)
|
||||
STATION_PORT = SYSTEM_PORTS.get("execution", 12003)
|
||||
|
||||
# Service URLs (internal for API calls)
|
||||
ARTERY_URL = os.getenv("ARTERY_URL", f"http://localhost:{ARTERY_PORT}")
|
||||
ATLAS_URL = os.getenv("ATLAS_URL", f"http://localhost:{ATLAS_PORT}")
|
||||
STATION_URL = os.getenv("STATION_URL", f"http://localhost:{STATION_PORT}")
|
||||
|
||||
# External URLs (for frontend links, falls back to internal)
|
||||
ARTERY_EXTERNAL_URL = os.getenv("ARTERY_EXTERNAL_URL", ARTERY_URL)
|
||||
ATLAS_EXTERNAL_URL = os.getenv("ATLAS_EXTERNAL_URL", ATLAS_URL)
|
||||
STATION_EXTERNAL_URL = os.getenv("STATION_EXTERNAL_URL", STATION_URL)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
@@ -149,7 +135,6 @@ def generate_config(req: GenerationRequest):
|
||||
"slug": req.systems.artery.lower(),
|
||||
"title": req.systems.artery.title(),
|
||||
"tagline": "Todo lo vital",
|
||||
"port": ARTERY_PORT,
|
||||
"icon": "",
|
||||
},
|
||||
{
|
||||
@@ -158,7 +143,6 @@ def generate_config(req: GenerationRequest):
|
||||
"slug": req.systems.atlas.lower(),
|
||||
"title": req.systems.atlas.title(),
|
||||
"tagline": "Documentacion accionable",
|
||||
"port": ATLAS_PORT,
|
||||
"icon": "",
|
||||
},
|
||||
{
|
||||
@@ -167,7 +151,6 @@ def generate_config(req: GenerationRequest):
|
||||
"slug": req.systems.station.lower(),
|
||||
"title": req.systems.station.title(),
|
||||
"tagline": "Monitores, Entornos y Herramientas",
|
||||
"port": STATION_PORT,
|
||||
"icon": "",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,8 +16,8 @@ import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
# Add current directory to path for imports (artery, atlas, station)
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
@@ -163,7 +163,7 @@ def artery_index(request: Request):
|
||||
@app.get("/artery/{path:path}")
|
||||
def artery_route(path: str):
|
||||
"""Artery sub-routes."""
|
||||
return {"system": "artery", "path": path, "message": "Artery route placeholder"}
|
||||
return {"system": "artery", "path": path}
|
||||
|
||||
|
||||
# === Atlas ===
|
||||
@@ -208,10 +208,32 @@ def atlas_index(request: Request):
|
||||
)
|
||||
|
||||
|
||||
@app.get("/atlas/book/{book_name:path}")
|
||||
def atlas_book(book_name: str):
|
||||
"""Serve atlas books."""
|
||||
book_name = book_name.rstrip("/")
|
||||
book_path = SPR_ROOT / "atlas" / "books" / book_name
|
||||
|
||||
if not book_path.exists():
|
||||
return JSONResponse({"detail": "Book not found"}, status_code=404)
|
||||
|
||||
index_html = book_path / "index.html"
|
||||
if index_html.exists():
|
||||
return HTMLResponse(index_html.read_text())
|
||||
|
||||
readme = book_path / "README.md"
|
||||
if readme.exists():
|
||||
return HTMLResponse(f"<pre>{readme.read_text()}</pre>")
|
||||
|
||||
return JSONResponse(
|
||||
{"book": book_name, "files": [f.name for f in book_path.iterdir()]}
|
||||
)
|
||||
|
||||
|
||||
@app.get("/atlas/{path:path}")
|
||||
def atlas_route(path: str):
|
||||
"""Atlas sub-routes."""
|
||||
return {"system": "atlas", "path": path, "message": "Atlas route placeholder"}
|
||||
return {"system": "atlas", "path": path}
|
||||
|
||||
|
||||
# === Station ===
|
||||
@@ -271,7 +293,7 @@ def station_index(request: Request):
|
||||
@app.get("/station/{path:path}")
|
||||
def station_route(path: str):
|
||||
"""Station sub-routes."""
|
||||
return {"system": "station", "path": path, "message": "Station route placeholder"}
|
||||
return {"system": "station", "path": path}
|
||||
|
||||
|
||||
# === Generate ===
|
||||
@@ -290,7 +312,6 @@ def generate_config(req: dict):
|
||||
"""Generate a config.json for a new room."""
|
||||
config = load_config()
|
||||
hub_port = config.get("framework", {}).get("hub_port", 12000)
|
||||
system_ports = {s["key"]: s["port"] for s in config.get("systems", [])}
|
||||
|
||||
framework = req.get("framework", {})
|
||||
systems = req.get("systems", {})
|
||||
@@ -307,17 +328,14 @@ def generate_config(req: dict):
|
||||
{
|
||||
"key": "data_flow",
|
||||
"name": systems.get("artery", "artery"),
|
||||
"port": system_ports.get("data_flow", 12001),
|
||||
},
|
||||
{
|
||||
"key": "documentation",
|
||||
"name": systems.get("atlas", "atlas"),
|
||||
"port": system_ports.get("documentation", 12002),
|
||||
},
|
||||
{
|
||||
"key": "execution",
|
||||
"name": systems.get("station", "station"),
|
||||
"port": system_ports.get("execution", 12003),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ Loads and validates framework configuration files.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrameworkConfig:
|
||||
"""Framework metadata"""
|
||||
|
||||
name: str
|
||||
slug: str
|
||||
version: str
|
||||
@@ -25,18 +26,19 @@ class FrameworkConfig:
|
||||
@dataclass
|
||||
class SystemConfig:
|
||||
"""System configuration"""
|
||||
|
||||
key: str
|
||||
name: str
|
||||
slug: str
|
||||
title: str
|
||||
tagline: str
|
||||
port: int
|
||||
icon: str
|
||||
slug: str = ""
|
||||
title: str = ""
|
||||
tagline: str = ""
|
||||
icon: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComponentConfig:
|
||||
"""Component configuration"""
|
||||
|
||||
name: str
|
||||
title: str
|
||||
description: str
|
||||
@@ -54,7 +56,7 @@ class ConfigLoader:
|
||||
self.systems: List[SystemConfig] = []
|
||||
self.components: Dict[str, Dict[str, ComponentConfig]] = {}
|
||||
|
||||
def load(self) -> 'ConfigLoader':
|
||||
def load(self) -> "ConfigLoader":
|
||||
"""Load configuration from file"""
|
||||
with open(self.config_path) as f:
|
||||
self.raw_config = json.load(f)
|
||||
@@ -67,25 +69,25 @@ class ConfigLoader:
|
||||
|
||||
def _parse_framework(self):
|
||||
"""Parse framework metadata"""
|
||||
fw = self.raw_config['framework']
|
||||
fw = self.raw_config["framework"]
|
||||
self.framework = FrameworkConfig(**fw)
|
||||
|
||||
def _parse_systems(self):
|
||||
"""Parse system configurations"""
|
||||
for sys in self.raw_config['systems']:
|
||||
for sys in self.raw_config["systems"]:
|
||||
self.systems.append(SystemConfig(**sys))
|
||||
|
||||
def _parse_components(self):
|
||||
"""Parse component configurations"""
|
||||
comps = self.raw_config['components']
|
||||
comps = self.raw_config["components"]
|
||||
|
||||
# Shared components
|
||||
self.components['shared'] = {}
|
||||
for key, value in comps.get('shared', {}).items():
|
||||
self.components['shared'][key] = ComponentConfig(**value)
|
||||
self.components["shared"] = {}
|
||||
for key, value in comps.get("shared", {}).items():
|
||||
self.components["shared"][key] = ComponentConfig(**value)
|
||||
|
||||
# System-specific components
|
||||
for system_key in ['data_flow', 'documentation', 'execution']:
|
||||
for system_key in ["data_flow", "documentation", "execution"]:
|
||||
self.components[system_key] = {}
|
||||
for comp_key, comp_value in comps.get(system_key, {}).items():
|
||||
self.components[system_key][comp_key] = ComponentConfig(**comp_value)
|
||||
@@ -97,13 +99,15 @@ class ConfigLoader:
|
||||
return sys
|
||||
return None
|
||||
|
||||
def get_component(self, system_key: str, component_key: str) -> Optional[ComponentConfig]:
|
||||
def get_component(
|
||||
self, system_key: str, component_key: str
|
||||
) -> Optional[ComponentConfig]:
|
||||
"""Get component config"""
|
||||
return self.components.get(system_key, {}).get(component_key)
|
||||
|
||||
def get_shared_component(self, key: str) -> Optional[ComponentConfig]:
|
||||
"""Get shared component config"""
|
||||
return self.components.get('shared', {}).get(key)
|
||||
return self.components.get("shared", {}).get(key)
|
||||
|
||||
|
||||
def load_config(config_path: str | Path) -> ConfigLoader:
|
||||
@@ -115,6 +119,7 @@ def load_config(config_path: str | Path) -> ConfigLoader:
|
||||
if __name__ == "__main__":
|
||||
# Test with pawprint config
|
||||
import sys
|
||||
|
||||
config_path = Path(__file__).parent.parent / "pawprint.config.json"
|
||||
|
||||
loader = load_config(config_path)
|
||||
@@ -126,5 +131,5 @@ if __name__ == "__main__":
|
||||
print(f" {sys.icon} {sys.title} ({sys.name}) - {sys.tagline}")
|
||||
|
||||
print(f"\nShared Components:")
|
||||
for key, comp in loader.components['shared'].items():
|
||||
for key, comp in loader.components["shared"].items():
|
||||
print(f" {comp.name} - {comp.description}")
|
||||
|
||||
Reference in New Issue
Block a user