Scaffold backend (FastAPI/uv) and frontend (SvelteKit/bun)

Backend: FastAPI app with health check, AppError hierarchy, stderr
logging via LOG_LEVEL, ruff+pyright(strict) config, pytest passing.

Frontend: SvelteKit (TS) with prettier, eslint, vitest (unit +
component), adapter-node for self-hosted deployment on Zeus.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tim Otlik 2026-07-17 22:12:44 +02:00
parent 06df9df339
commit e5b4f54cd8
33 changed files with 517 additions and 4 deletions

0
backend/app/__init__.py Normal file
View file

41
backend/app/errors.py Normal file
View file

@ -0,0 +1,41 @@
"""Base exception hierarchy for the Sagaphone backend.
Errors propagate normally; only the API edge (see main.py's exception
handler) catches them and translates them into an HTTP response.
"""
from __future__ import annotations
class AppError(Exception):
"""Base class for all application errors."""
status_code: int = 500
def __init__(self, message: str) -> None:
super().__init__(message)
self.message = message
class NotFoundError(AppError):
"""Requested resource does not exist."""
status_code = 404
class ConfigError(AppError):
"""Missing or invalid configuration (e.g. secrets.toml)."""
status_code = 500
class NarrationError(AppError):
"""The narration (Haiku) call failed or returned something unusable."""
status_code = 502
class TTSError(AppError):
"""The TTS (Piper) synthesis step failed."""
status_code = 502

View file

@ -0,0 +1,25 @@
"""Logging setup for the backend.
Follows the project's output contract: all log output goes to stderr,
level controlled via the LOG_LEVEL env var (default INFO). This is server
logging, not the request/response payload that stays in FastAPI's
responses.
"""
from __future__ import annotations
import logging
import os
import sys
def configure_logging() -> None:
level_name = os.environ.get("LOG_LEVEL", "INFO").upper()
level = getattr(logging, level_name, logging.INFO)
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(name)s: %(message)s"))
root = logging.getLogger()
root.setLevel(level)
root.handlers = [handler]

33
backend/app/main.py Normal file
View file

@ -0,0 +1,33 @@
"""Sagaphone FastAPI entrypoint.
Routes are added as features land (see ../SPEC.md and ../spec/*.md).
This currently only wires up app creation, logging, and a health check
so the scaffold is runnable end to end.
"""
from __future__ import annotations
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from app.errors import AppError
from app.logging_config import configure_logging
configure_logging()
logger = logging.getLogger(__name__)
app = FastAPI(title="Sagaphone", version="0.1.0")
@app.exception_handler(AppError)
async def app_error_handler(_request: Request, exc: AppError) -> JSONResponse:
"""Edge-catch: translate AppError subclasses into clean HTTP responses."""
logger.error("request failed: %s", exc.message)
return JSONResponse(status_code=exc.status_code, content={"error": exc.message})
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}