diff --git a/alembic/versions/010_bbox_gist.py b/alembic/versions/010_bbox_gist.py new file mode 100644 index 0000000..a7feab3 --- /dev/null +++ b/alembic/versions/010_bbox_gist.py @@ -0,0 +1,29 @@ +"""GIST bbox indexes for events/fires map-pan queries. + +Revision ID: 010_bbox_gist +Revises: 009_vessels +Create Date: 2026-09-01 +""" + +from alembic import op + +revision = "010_bbox_gist" +down_revision = "009_vessels" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + "CREATE INDEX IF NOT EXISTS ix_events_geom_gist ON events " + "USING gist (ST_SetSRID(ST_MakePoint(location_lon, location_lat), 4326))" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_fires_geom_gist ON fires " + "USING gist (ST_SetSRID(ST_MakePoint(longitude, latitude), 4326))" + ) + + +def downgrade() -> None: + op.execute("DROP INDEX IF EXISTS ix_fires_geom_gist") + op.execute("DROP INDEX IF EXISTS ix_events_geom_gist") diff --git a/app/main.py b/app/main.py index ecb2a64..b0052fc 100644 --- a/app/main.py +++ b/app/main.py @@ -1992,4 +1992,4 @@ app.mount("/static", CachedStaticFiles(directory=str(STATIC_DIR)), name="static" if __name__ == "__main__": import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) + uvicorn.run(app, host="0.0.0.0", port=8000, workers=1) # single worker: in-memory WS/pubsub + layer caches diff --git a/docker-compose.yml b/docker-compose.yml index 01abd37..98e1cc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -170,7 +170,7 @@ services: # Listens on 8000 INSIDE the container (the app already owns host 8000); # published on host loopback 127.0.0.1:8001 only. titiler: - image: ghcr.io/developmentseed/titiler:latest + image: ghcr.io/developmentseed/titiler:latest@sha256:1809958d063543e3ec858259536002b2de78e9f8f09a22a8d9591bdc2b550b14 pull_policy: missing container_name: osint-titiler platform: linux/arm64 diff --git a/tests/test_phase1_guardrails.py b/tests/test_phase1_guardrails.py index de2a00c..bf3115d 100644 --- a/tests/test_phase1_guardrails.py +++ b/tests/test_phase1_guardrails.py @@ -31,3 +31,35 @@ def test_no_redis_kafka_celery(): assert "kafka" not in blob assert "celery" not in blob assert "cachetools" in req + + +def test_titiler_image_pinned_by_digest(): + text = (ROOT / "docker-compose.yml").read_text() + assert ( + "ghcr.io/developmentseed/titiler:latest@sha256:" + "1809958d063543e3ec858259536002b2de78e9f8f09a22a8d9591bdc2b550b14" + in text + ) + # Unpinned :latest would drift on every pull. + for line in text.splitlines(): + if "titiler" in line.lower() and "image:" in line: + assert "@sha256:" in line + + +def test_uvicorn_single_worker_guard(): + text = (ROOT / "app" / "main.py").read_text() + main_block = text.split('if __name__ == "__main__":', 1)[1] + assert "workers=1" in main_block + + +def test_bbox_gist_migration_keeps_btree_and_adds_gist(): + text = (ROOT / "alembic" / "versions" / "010_bbox_gist.py").read_text() + assert "down_revision" in text and "009_vessels" in text + assert "ix_events_geom_gist" in text + assert "ix_fires_geom_gist" in text + assert "ST_MakePoint(location_lon, location_lat)" in text + assert "ST_MakePoint(longitude, latitude)" in text + assert "USING gist" in text + models = (ROOT / "app" / "models.py").read_text() + assert 'Index("ix_events_location"' in models + assert 'Index("ix_fires_bbox"' in models