feat(P01): SLICE-01+02 — Dockerfile, .dockerignore, docker-compose.yml, FastAPI StaticFiles, PRAXIS_DB_PATH env (G-102 fix)

SLICE-01 (lead-developer): multi-stage Dockerfile (node:22-slim→python:3.12-slim),
  .dockerignore (excludes secrets/node_modules/.git), docker-compose.yml
  (port 8789, SQLite volume, env injection for all voice-service vars)
SLICE-02 (backend-engineer+data-engineer): FastAPI mounts client/dist as
  StaticFiles at / after API routes (D-023, REQ-DEPLOY-13).
  G-102 MUST fix: db/store.py + db/migrate.py now read PRAXIS_DB_PATH
  from env so the Docker volume mount persists SQLite data.
G-105 FIX: Dockerfile copies pyproject.toml before source (pip install
  layer cached, source changes don't invalidate).

REQ-DEPLOY-01, 02, 13, 16 covered.

---ci---
project: praxis
phase: 1
milestone: v0.2
status: execute
slice: 01-02
wave: 1
---/ci---
This commit is contained in:
Praxis CI
2026-08-01 14:16:39 +00:00
parent 98779b5a72
commit f04b9b3588
6 changed files with 181 additions and 2 deletions
+14
View File
@@ -29,6 +29,7 @@ except ImportError: # pragma: no cover
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pipecat.transports.smallwebrtc.connection import SmallWebRTCConnection
from server.pipeline import build_pipeline
@@ -116,6 +117,19 @@ async def webrtc_offer(offer: WebRTCOffer) -> dict[str, str]:
raise HTTPException(status_code=500, detail=str(exc))
# ── Static client serving (D-023, REQ-DEPLOY-13) ────────────────────
# Mount client/dist as StaticFiles at "/" AFTER all API routes so they
# take precedence. html=True serves index.html for "/" (SPA root).
# The client has no React Router (single-view state machine: start→live
# →debrief), so no SPA fallback fallback route is needed per RESEARCH.md Q3.
_CLIENT_DIST = _env("PRAXIS_CLIENT_DIST", "client/dist")
if os.path.isdir(_CLIENT_DIST):
app.mount("/", StaticFiles(directory=_CLIENT_DIST, html=True), name="client")
logger.info(f"Serving client from {_CLIENT_DIST}")
else:
logger.warning(f"Client dist not found at {_CLIENT_DIST} — API-only mode")
def main() -> int:
"""Run the server with uvicorn."""
import uvicorn