feat(slice-4): backend serves the built SPA — the deployed nginx-proxies-all topology (launch-app §2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -17,6 +17,7 @@ from typing import Any
|
||||
import psycopg
|
||||
from fastapi import Depends, FastAPI, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.domains import accounts, storefronts
|
||||
@@ -89,7 +90,7 @@ def _set_session_cookie(response: Response, account: accounts.Account) -> None:
|
||||
)
|
||||
|
||||
|
||||
def create_app(database_url: str | None = None) -> FastAPI:
|
||||
def create_app(database_url: str | None = None, static_dir: str | Path | None = None) -> FastAPI:
|
||||
_ensure_app_logging()
|
||||
dsn = database_url or config.database_url()
|
||||
|
||||
@@ -202,6 +203,13 @@ def create_app(database_url: str | None = None) -> FastAPI:
|
||||
)
|
||||
return JSONResponse(status_code=201, content={"id": sf.id, "name": sf.name})
|
||||
|
||||
# Deployed topology (launch-app SPEC §2): nginx proxies everything here, so the
|
||||
# backend serves the built SPA. Mounted LAST so /healthz and /api/* win. In dev the
|
||||
# dist dir doesn't exist (Vite serves the frontend) and the mount is skipped.
|
||||
spa_dir = Path(static_dir) if static_dir is not None else _REPO_ROOT / "frontend" / "dist"
|
||||
if (spa_dir / "index.html").is_file():
|
||||
app.mount("/", StaticFiles(directory=spa_dir, html=True), name="spa")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Deployed topology (launch-app SPEC §2): nginx proxies EVERYTHING to the backend, so
|
||||
the backend must serve the built SPA (frontend/dist) itself. Dev is unaffected — Vite
|
||||
serves the frontend and the default dist dir simply doesn't exist."""
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import create_app
|
||||
|
||||
|
||||
def test_spa_served_when_dist_present(fresh_db_url, tmp_path):
|
||||
(tmp_path / "index.html").write_text("<!doctype html><title>ecomm spa</title>")
|
||||
with TestClient(create_app(database_url=fresh_db_url, static_dir=tmp_path)) as client:
|
||||
root = client.get("/")
|
||||
assert root.status_code == 200
|
||||
assert "ecomm spa" in root.text
|
||||
# API + health still win over the mount
|
||||
assert client.get("/healthz").json()["status"] == "ok"
|
||||
assert client.get("/api/auth/me").status_code == 401
|
||||
|
||||
|
||||
def test_no_mount_when_dist_absent(fresh_db_url, tmp_path):
|
||||
# An empty/missing dist (the dev case) must not 500 the app — / just 404s.
|
||||
with TestClient(create_app(database_url=fresh_db_url, static_dir=tmp_path / "nope")) as client:
|
||||
assert client.get("/").status_code == 404
|
||||
assert client.get("/healthz").json()["status"] == "ok"
|
||||
Reference in New Issue
Block a user