Brings up the documented target architecture as a docker-compose stack — a modular monolith with the Ghidra step split into its own async worker. - worker/: RQ queue (lazy redis import) + run_acquisition task (Job status queued→started→finished/failed, drives ams.acquire with sink=db) - Job model + JobOut schema; Snapshot.data is JSONB on Postgres - POST/GET /jobs: stream an upload to a shared volume, enqueue, poll status - docker/api.Dockerfile (slim) + docker/worker.Dockerfile (JDK21 + Ghidra fetched at build, overridable via GHIDRA_URL) + docker-compose.yml - ghidra.py: AMS_GHIDRA_SCRIPTS override for in-container script path - pyproject: [worker] extra (rq/redis/psycopg), python-multipart in [api] - tests: 4 new (task success/failure + endpoint enqueue/503) -> 22/22 Verified: API image builds, container serves /health + /ui + /jobs; compose config validates. Worker image (downloads ~1 GB Ghidra) not built here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
1.3 KiB
Python
45 lines
1.3 KiB
Python
"""FastAPI application factory.
|
|
|
|
Run with uvicorn's factory mode (no import-time DB side effects):
|
|
|
|
uvicorn ams.api.app:create_app --factory --reload
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.responses import RedirectResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from .. import __version__
|
|
from .db import configure, init_db
|
|
from .routes import diff, games, jobs, snapshots
|
|
|
|
_STATIC = Path(__file__).parent / "static"
|
|
|
|
|
|
def create_app(database_url: str | None = None) -> FastAPI:
|
|
configure(database_url)
|
|
init_db()
|
|
|
|
app = FastAPI(title="ams — engine surface catalog", version=__version__)
|
|
app.include_router(games.router)
|
|
app.include_router(snapshots.router)
|
|
app.include_router(diff.router)
|
|
app.include_router(jobs.router)
|
|
|
|
@app.get("/health", tags=["meta"])
|
|
def health() -> dict[str, str]:
|
|
return {"status": "ok", "version": __version__}
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
def root() -> RedirectResponse:
|
|
return RedirectResponse("/ui/")
|
|
|
|
# The command-center UI (static HTML/CSS/JS, no build step) is served last so API
|
|
# routes take precedence.
|
|
app.mount("/ui", StaticFiles(directory=str(_STATIC), html=True), name="ui")
|
|
return app
|