Files
qtalker---/backend/app/main.py
Indiana 0756e677b9 Add EMF field mode, Armory shop/waitlist, and ambient haunting layer
Three self-contained features, verified complete and cross-wired
end-to-end (audited: backend 54/54 tests, frontend 110/110 tests,
tsc --noEmit clean, i18n coverage script clean):

- EMF mode: DeviceMotion/DeviceOrientation-based field-meter sensing,
  a fifth séance channel alongside Wire/EVP/Radio/Ouija, with its own
  fragment prompt persona and full frontend gauge UI.
- Armory (shop/waitlist): pre-order capture page for the future
  Ultimate Quantum Box hardware line, rate-limited public endpoint,
  explicitly no payment collection.
- Haunting layer: ambient possession effects (dread-bed audio, title
  glitching, idle-paced whispers/manifests), respects
  prefers-reduced-motion, mounted once at the app root.

Plus WebUSB robustness fixes in lib/sdr.ts (Terratec vendor ID,
explicit selectConfiguration, isSecureContext gate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PphXq1s43DNRj1uWKGXof
2026-07-21 02:28:14 +00:00

61 lines
1.7 KiB
Python

from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import app.models # noqa: F401 — registers models on Base.metadata before create_all
from app.config import settings
from app.db import Base, engine
from app.routes.auth import router as auth_router
from app.routes.codex import router as codex_router
from app.routes.shop import router as shop_router
from app.ws import AUDIO_DIR
from app.ws import router as ws_router
FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"
@asynccontextmanager
async def lifespan(app: FastAPI):
AUDIO_DIR.mkdir(parents=True, exist_ok=True)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
app = FastAPI(title="Quantumancy", lifespan=lifespan)
app.include_router(auth_router)
app.include_router(codex_router)
app.include_router(shop_router)
app.include_router(ws_router)
@app.get("/healthz")
async def healthz():
return {"status": "ok"}
app.mount(
"/assets",
StaticFiles(directory=FRONTEND_DIST / "assets", check_dir=False),
name="frontend-assets",
)
app.mount(
"/audio",
StaticFiles(directory=AUDIO_DIR, check_dir=False),
name="spirit-audio",
)
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
index_file = FRONTEND_DIST / "index.html"
if not index_file.exists():
raise HTTPException(
status_code=503,
detail="Frontend not built. Run `npm run build` in frontend/ and restart.",
)
return FileResponse(index_file)