Files
Aidem-Media-DLL-Analysis/ams/api/app.py
Patryk Gensch f4aa7caaa9 Containerise: Postgres + Redis/RQ + API + Ghidra worker
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>
2026-05-31 12:24:47 +02:00

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