spr migrated books, and tester

This commit is contained in:
buenosairesam
2025-12-31 09:07:27 -03:00
parent 21b8eab3cb
commit cccc6b5a93
136 changed files with 15763 additions and 472 deletions

369
atlas/main.py Normal file
View File

@@ -0,0 +1,369 @@
"""
Album - Documentation system.
"""
import os
import httpx
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI(title="Album", version="0.1.0")
BASE_DIR = Path(__file__).parent.resolve()
BOOK_DIR = BASE_DIR / "book"
STATIC_DIR = BASE_DIR / "static"
# Create static directory if it doesn't exist
STATIC_DIR.mkdir(exist_ok=True)
templates = Jinja2Templates(directory=str(BASE_DIR))
# Serve static files
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
# Pawprint URL for data fetching
PAWPRINT_URL = os.getenv("PAWPRINT_URL", "http://localhost:12000")
def get_data():
"""Fetch data from pawprint hub."""
try:
resp = httpx.get(f"{PAWPRINT_URL}/api/data/album", timeout=5.0)
if resp.status_code == 200:
return resp.json()
except Exception as e:
print(f"Failed to fetch data from pawprint: {e}")
return {"templates": [], "larders": [], "books": []}
@app.get("/health")
def health():
return {"status": "ok", "service": "album"}
@app.get("/")
def index(request: Request):
data = get_data()
return templates.TemplateResponse("index.html", {
"request": request,
"pawprint_url": os.getenv("PAWPRINT_EXTERNAL_URL", PAWPRINT_URL),
**data,
})
@app.get("/api/data")
def api_data():
"""API endpoint for frontend data (proxied from pawprint)."""
return get_data()
# --- Book: Feature Flow (HTML presentations) ---
@app.get("/book/feature-flow/", response_class=HTMLResponse)
@app.get("/book/feature-flow", response_class=HTMLResponse)
def feature_flow_index():
"""Redirect to English presentation by default"""
return """<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Feature Flow</title>
<style>
body { font-family: system-ui; background: #0f172a; color: #e2e8f0; min-height: 100vh;
display: flex; flex-direction: column; align-items: center; justify-content: center; }
h1 { font-size: 2rem; margin-bottom: 2rem; }
.links { display: flex; gap: 1rem; }
a { background: #334155; color: #e2e8f0; padding: 1rem 2rem; border-radius: 8px;
text-decoration: none; font-size: 1.1rem; }
a:hover { background: #475569; }
</style></head>
<body>
<h1>Feature Flow - Standardization Pipeline</h1>
<div class="links">
<a href="/book/feature-flow/en">English</a>
<a href="/book/feature-flow/es">Español</a>
</div>
</body></html>"""
@app.get("/book/feature-flow/en", response_class=HTMLResponse)
def feature_flow_en():
html_file = BOOK_DIR / "feature-flow" / "index-en.html"
return HTMLResponse(html_file.read_text())
@app.get("/book/feature-flow/es", response_class=HTMLResponse)
def feature_flow_es():
html_file = BOOK_DIR / "feature-flow" / "index-es.html"
return HTMLResponse(html_file.read_text())
# --- Book: Feature Form Samples (templated book) ---
@app.get("/book/feature-form-samples/", response_class=HTMLResponse)
@app.get("/book/feature-form-samples", response_class=HTMLResponse)
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)
if not book:
return HTMLResponse("<h1>Book not found</h1>", status_code=404)
return templates.TemplateResponse("book-template.html", {
"request": request,
"book": book,
})
@app.get("/book/feature-form-samples/template/", response_class=HTMLResponse)
@app.get("/book/feature-form-samples/template", response_class=HTMLResponse)
def feature_form_samples_template():
"""View the template - styled like actual feature forms"""
html = """<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feature Form Template · Album</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f8fafc;
color: #1e293b;
line-height: 1.6;
}
.container { max-width: 800px; margin: 0 auto; padding: 2rem 1rem; }
header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
.breadcrumb a { color: #15803d; text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
h1 { font-size: 1.5rem; color: #15803d; }
.meta { display: flex; gap: 0.5rem; margin-top: 0.5rem; font-size: 0.8rem; }
.meta span { background: #f1f5f9; padding: 0.2rem 0.5rem; border-radius: 4px; color: #64748b; }
.meta .template { background: #dbeafe; color: #1d4ed8; }
.form-card {
background: white;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow: hidden;
}
.form-header {
background: linear-gradient(135deg, #15803d, #22c55e);
color: white;
padding: 1rem 1.5rem;
}
.form-header h2 { font-size: 1.1rem; font-weight: 600; }
.form-body { padding: 1.5rem; }
.field { margin-bottom: 1.25rem; }
.field:last-child { margin-bottom: 0; }
.field-label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #64748b;
margin-bottom: 0.35rem;
}
.field-value {
background: #f8fafc;
border: 2px dashed #cbd5e1;
border-radius: 6px;
padding: 0.75rem;
font-size: 0.95rem;
color: #94a3b8;
font-style: italic;
min-height: 2.5rem;
}
.field-value.multiline { min-height: 4rem; }
.field-steps .field-value { padding-left: 0.5rem; }
.field-steps ol { list-style: none; padding-left: 0; margin: 0; }
.field-steps li {
position: relative;
padding-left: 2rem;
margin-bottom: 0.5rem;
}
.field-steps li::before {
content: attr(data-step);
position: absolute;
left: 0;
width: 1.5rem;
height: 1.5rem;
background: #cbd5e1;
color: white;
border-radius: 50%;
font-size: 0.75rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.field-problems .field-value { background: #fef2f2; border-color: #fecaca; }
.field-special .field-value { background: #fffbeb; border-color: #fde68a; }
.field-technical .field-value { background: #f0fdf4; border-color: #bbf7d0; font-family: monospace; font-size: 0.85rem; }
footer {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #e2e8f0;
font-size: 0.85rem;
}
footer a { color: #15803d; text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<header>
<div class="breadcrumb">
<a href="/">Album</a> / <a href="/book/feature-form-samples/">Feature Form Samples</a> / Template
</div>
<h1>Feature Form Template</h1>
<div class="meta">
<span class="template">Template</span>
</div>
</header>
<div class="form-card">
<div class="form-header">
<h2>[Nombre del Flujo]</h2>
</div>
<div class="form-body">
<div class="field">
<label class="field-label">Tipo de Usuario</label>
<div class="field-value">[Dueno de mascota / Veterinario / Admin]</div>
</div>
<div class="field">
<label class="field-label">Punto de Entrada</label>
<div class="field-value">[Que pagina/boton/link]</div>
</div>
<div class="field">
<label class="field-label">Objetivo del Usuario</label>
<div class="field-value">[Objetivo en una oracion]</div>
</div>
<div class="field field-steps">
<label class="field-label">Pasos</label>
<div class="field-value multiline">
<ol>
<li data-step="1">[Primera cosa que hace el usuario]</li>
<li data-step="2">[Segunda cosa que hace el usuario]</li>
<li data-step="3">[etc.]</li>
</ol>
</div>
</div>
<div class="field">
<label class="field-label">Resultado Esperado</label>
<div class="field-value">[Resultado esperado cuando todo funciona]</div>
</div>
<div class="field field-problems">
<label class="field-label">Problemas Comunes</label>
<div class="field-value multiline">[Problema 1]<br>[Problema 2]</div>
</div>
<div class="field field-special">
<label class="field-label">Casos Especiales</label>
<div class="field-value multiline">[Caso especial 1]<br>[Caso especial 2]</div>
</div>
<div class="field">
<label class="field-label">Flujos Relacionados</label>
<div class="field-value">[Otros flujos que se conectan con este]</div>
</div>
<div class="field field-technical">
<label class="field-label">Notas Tecnicas</label>
<div class="field-value">[Notas para el equipo de desarrollo]</div>
</div>
</div>
</div>
<footer>
<a href="/book/feature-form-samples/">&larr; Feature Form Samples</a>
</footer>
</div>
</body>
</html>"""
return HTMLResponse(html)
@app.get("/book/feature-form-samples/larder/", response_class=HTMLResponse)
@app.get("/book/feature-form-samples/larder", response_class=HTMLResponse)
def feature_form_samples_larder():
"""Browse the larder (actual data)"""
html_file = BOOK_DIR / "feature-form-samples" / "index.html"
if html_file.exists():
return HTMLResponse(html_file.read_text())
return HTMLResponse("<h1>Larder index not found</h1>", status_code=404)
@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)
larder_dir = BOOK_DIR / "feature-form-samples" / "feature-form"
file_path = larder_dir / user_type / filename
if not file_path.exists():
return HTMLResponse("<h1>Not found</h1>", status_code=404)
content = file_path.read_text()
detail_file = BOOK_DIR / "feature-form-samples" / "detail.html"
return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), {
"request": request,
"user_type": user_type,
"filename": filename,
"content": content,
})
# --- Book: Gherkin Samples ---
@app.get("/book/gherkin-samples/", response_class=HTMLResponse)
@app.get("/book/gherkin-samples", response_class=HTMLResponse)
@app.get("/book/gherkin/", response_class=HTMLResponse) # Alias
@app.get("/book/gherkin", response_class=HTMLResponse) # Alias
def gherkin_samples_index():
"""Browse gherkin samples"""
html_file = BOOK_DIR / "gherkin-samples" / "index.html"
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
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
if not file_path.exists() or not file_path.suffix == ".feature":
return HTMLResponse("<h1>Not found</h1>", status_code=404)
content = file_path.read_text()
detail_file = BOOK_DIR / "gherkin-samples" / "detail.html"
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")
# --- Book: Drive Index ---
@app.get("/book/drive-index/", response_class=HTMLResponse)
@app.get("/book/drive-index", response_class=HTMLResponse)
def drive_index():
"""Browse drive index"""
html_file = BOOK_DIR / "drive-index" / "index.html"
return HTMLResponse(html_file.read_text())
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=int(os.getenv("PORT", "12002")),
reload=os.getenv("DEV", "").lower() in ("1", "true"),
)