Skip to content

Commit c9efcdd

Browse files
authored
Merge branch 'main' into codex/add-lru_cache-for-get_settings-helper
2 parents 2d7f27a + 2942907 commit c9efcdd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+10294
-3874
lines changed

.github/workflows/assets.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: assets
2+
on:
3+
pull_request: {}
4+
5+
jobs:
6+
validate-assets:
7+
runs-on: ubuntu-latest
8+
timeout-minutes: 20
9+
steps:
10+
- uses: actions/checkout@v4
11+
12+
- name: Setup Node 20
13+
uses: actions/setup-node@v4
14+
with:
15+
node-version: '20'
16+
cache: 'npm'
17+
cache-dependency-path: paform/frontend/package-lock.json
18+
19+
- name: Install frontend deps
20+
run: |
21+
cd paform/frontend
22+
npm ci
23+
24+
- name: Generate reference GLBs
25+
run: |
26+
cd paform
27+
python3 scripts/generate_reference_glbs.py
28+
29+
- name: Pack GLBs (LOD0/LOD1) with KTX2
30+
run: |
31+
cd paform
32+
bash scripts/pack_models.sh
33+
34+
- name: Validate GLBs (fail on warnings)
35+
run: |
36+
cd paform
37+
python3 scripts/glb_validate.py frontend/public/models/*.glb --fail-on-warning
38+
39+
- name: Generate manifest.json
40+
run: |
41+
cd paform
42+
python3 scripts/gen_glb_manifest.py > frontend/public/models/manifest.json
43+
44+
- name: Upload manifest
45+
if: always()
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: assets-artifacts
49+
path: paform/frontend/public/models/manifest.json

.github/workflows/ci.yml

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
name: CI
22

3+
permissions:
4+
contents: read
5+
actions: read
6+
checks: write
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
312
on:
413
push:
514
branches: [ main ]
@@ -10,14 +19,14 @@ jobs:
1019
pre-commit:
1120
runs-on: ubuntu-latest
1221
steps:
13-
- uses: actions/checkout@v5
22+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
1423
- name: Set up Python
15-
uses: actions/setup-python@v6
24+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c
1625
with:
1726
python-version: '3.12'
1827

1928
- name: Install uv
20-
uses: astral-sh/setup-uv@v7
29+
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d
2130
with:
2231
version: "0.5.21"
2332
enable-cache: true
@@ -37,14 +46,14 @@ jobs:
3746
working-directory: ./backend
3847

3948
steps:
40-
- uses: actions/checkout@v5
49+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
4150
- name: Set up Python
42-
uses: actions/setup-python@v6
51+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c
4352
with:
4453
python-version: '3.12'
4554

4655
- name: Install uv
47-
uses: astral-sh/setup-uv@v7
56+
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d
4857
with:
4958
version: "0.5.21"
5059
enable-cache: true
@@ -57,7 +66,15 @@ jobs:
5766
- name: Test with pytest
5867
run: |
5968
source .venv/bin/activate
60-
pytest
69+
mkdir -p test-results/pytest
70+
pytest -q -W error --maxfail=1 --durations=50 --junitxml=test-results/pytest/junit.xml
71+
72+
- name: Upload test results
73+
if: always()
74+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
75+
with:
76+
name: backend-test-results
77+
path: backend/test-results
6178

6279
frontend-tests:
6380
runs-on: ubuntu-latest
@@ -66,17 +83,20 @@ jobs:
6683
working-directory: ./frontend
6784

6885
steps:
69-
- uses: actions/checkout@v5
86+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
7087
- name: Set up Node.js
71-
uses: actions/setup-node@v5
88+
uses: actions/setup-node@0a44ba78451273a1ed8ac2fee4e347c72dfd377f
7289
with:
73-
node-version: '18'
90+
node-version: '20'
7491
cache: 'npm'
7592
cache-dependency-path: ./frontend/package-lock.json
7693

7794
- name: Install dependencies
7895
run: npm ci
7996

97+
- name: Test
98+
run: npm test
99+
80100
- name: Lint
81101
run: npm run lint
82102

.github/workflows/docs.yml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
name: docs
2+
23
permissions:
3-
contents: write
4-
pull-requests: write
4+
contents: read
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: true
59

610
on:
711
push:
@@ -44,21 +48,21 @@ jobs:
4448
runs-on: ubuntu-latest
4549
steps:
4650
- name: Checkout code
47-
uses: actions/checkout@v5
51+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
4852

4953
- name: Install uv
50-
uses: astral-sh/setup-uv@v7
54+
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d
5155
with:
5256
version: "0.5.21"
5357
enable-cache: true
5458

5559
- name: Set up Python
56-
uses: actions/setup-python@v6
60+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c
5761
with:
5862
python-version: "3.12"
5963

6064
- name: Setup Node.js
61-
uses: actions/setup-node@v5
65+
uses: actions/setup-node@0a44ba78451273a1ed8ac2fee4e347c72dfd377f
6266
with:
6367
node-version: '20'
6468
cache: 'npm'
@@ -83,27 +87,29 @@ jobs:
8387
./build-docs.sh
8488
8589
- name: Upload artifact
86-
uses: actions/upload-artifact@v4
90+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
8791
with:
8892
name: docs-site
8993
path: site/
9094
retention-days: 1
9195

9296
deploy:
9397
needs: build
98+
permissions:
99+
contents: write
94100
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
95101
runs-on: ubuntu-latest
96102
steps:
97103
- name: Checkout code
98-
uses: actions/checkout@v5
104+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
99105

100106
- name: Configure Git Credentials
101107
run: |
102108
git config user.name github-actions[bot]
103109
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
104110
105111
- name: Download artifact
106-
uses: actions/download-artifact@v5
112+
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
107113
with:
108114
name: docs-site
109115
path: site
@@ -112,7 +118,7 @@ jobs:
112118
run: touch site/.nojekyll
113119

114120
- name: Deploy to Github pages
115-
uses: JamesIves/github-pages-deploy-action@v4.7.3
121+
uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8
116122
with:
117123
branch: gh-pages
118124
folder: site

.github/workflows/perf-light.yml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
name: perf-light
22

3+
permissions:
4+
contents: read
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: true
9+
310
on:
411
pull_request: {}
512

613
jobs:
714
k6:
815
runs-on: ubuntu-latest
916
timeout-minutes: 15
10-
concurrency:
11-
group: perf-${{ github.ref }}
12-
cancel-in-progress: true
1317
steps:
14-
- uses: actions/checkout@v4
18+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
1519

1620
- name: Install k6
1721
run: |
@@ -20,12 +24,10 @@ jobs:
2024
2125
- name: Start stack
2226
run: |
23-
cd paform
2427
docker compose --env-file .env.development -f docker-compose.dev.yml up -d --build
2528
2629
- name: Wait for API
2730
run: |
28-
cd paform
2931
for i in {1..60}; do curl -sf http://localhost:8000/healthcheck && break || sleep 2; done
3032
3133
- name: Wait for Frontend
@@ -36,7 +38,6 @@ jobs:
3638
env:
3739
BASE_URL: http://localhost:8000
3840
run: |
39-
cd paform
4041
python - <<'PY'
4142
import json
4243
import os
@@ -84,18 +85,16 @@ PY
8485
BASE_URL: http://localhost:8000
8586
FRONTEND_BASE_URL: http://localhost:3000
8687
run: |
87-
cd paform
8888
k6 run --summary-export k6-summary.json tests/perf/k6-quote-cnc.js
8989
9090
- name: Upload k6 summary
9191
if: always()
92-
uses: actions/upload-artifact@v4
92+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
9393
with:
9494
name: k6-summary
95-
path: paform/k6-summary.json
95+
path: k6-summary.json
9696

9797
- name: Shutdown stack
9898
if: always()
9999
run: |
100-
cd paform
101100
docker compose --env-file .env.development -f docker-compose.dev.yml down
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from alembic import op
2+
import sqlalchemy as sa
3+
4+
# revision identifiers, used by Alembic.
5+
revision = "cfe1d8e4e001_add_sync_events"
6+
down_revision = "0001"
7+
branch_labels = None
8+
depends_on = None
9+
10+
11+
def upgrade() -> None:
12+
op.create_table(
13+
"sync_events",
14+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
15+
sa.Column("event_id", sa.String(length=255), nullable=True),
16+
sa.Column("source", sa.String(length=100), nullable=False),
17+
sa.Column("body_sha256", sa.String(length=64), nullable=False),
18+
sa.Column(
19+
"received_at",
20+
sa.TIMESTAMP(timezone=True),
21+
server_default=sa.text("CURRENT_TIMESTAMP"),
22+
nullable=False,
23+
),
24+
sa.UniqueConstraint("source", "event_id", name="uq_sync_events_src_event"),
25+
sa.UniqueConstraint("source", "body_sha256", name="uq_sync_events_src_body"),
26+
sa.UniqueConstraint("body_sha256", name="uq_sync_events_body"),
27+
)
28+
29+
30+
def downgrade() -> None:
31+
op.drop_table("sync_events")

backend/api/db.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Database configuration and session management using SQLAlchemy 2.x."""
2-
32
from __future__ import annotations
43

54
import os
@@ -21,7 +20,6 @@ def _normalize_postgres_url(url: str) -> str:
2120
Accepts legacy prefixes and ensures the SQLAlchemy URL includes the
2221
``+psycopg`` dialect when using PostgreSQL.
2322
"""
24-
2523
if url.startswith("postgres://"):
2624
return url.replace("postgres://", "postgresql+psycopg://", 1)
2725
if url.startswith("postgresql://") and "+psycopg" not in url:
@@ -31,17 +29,14 @@ def _normalize_postgres_url(url: str) -> str:
3129

3230
def get_engine_url() -> str:
3331
"""Resolve the database URL from env or settings with sane defaults."""
34-
35-
settings = get_settings()
32+
settings = Settings()
3633
url = os.getenv("DATABASE_URL", settings.database_url)
3734
if url.startswith("sqlite"):
38-
# SQLite works as-is
3935
return url
4036
return _normalize_postgres_url(url)
4137

4238

4339
ENGINE_URL = get_engine_url()
44-
4540
engine = create_engine(ENGINE_URL, pool_pre_ping=True, future=True)
4641

4742
SessionLocal = sessionmaker(
@@ -51,14 +46,14 @@ def get_engine_url() -> str:
5146
future=True,
5247
)
5348

49+
# Ensure models are registered on Base.metadata for create_all/drop_all
50+
import api.models # noqa: E402,F401
51+
5452

5553
def get_db() -> Generator:
5654
"""FastAPI dependency to provide a session per request."""
57-
5855
db = SessionLocal()
5956
try:
6057
yield db
6158
finally:
62-
db.close()
63-
64-
59+
db.close()

0 commit comments

Comments
 (0)