Skip to content

Commit c2caeb6

Browse files
authored
Merge branch 'master' into csv-upload
2 parents 47490a6 + 6520a0b commit c2caeb6

105 files changed

Lines changed: 4245 additions & 1214 deletions

File tree

Some content is hidden

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

alerting/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"jpeg-js": "^0.4.4",
6565
"lodash": "^4.18.0",
6666
"node-fetch": "^2.6.7",
67-
"nodemailer": "^8.0.9",
67+
"nodemailer": "^9.0.1",
6868
"pg": "^8.5.1",
6969
"prism-common": "file:../common",
7070
"puppeteer": "^24.33.0",

alerting/yarn.lock

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,10 +3132,10 @@ node-releases@^2.0.27:
31323132
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
31333133
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
31343134

3135-
nodemailer@^8.0.9:
3136-
version "8.0.11"
3137-
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-8.0.11.tgz#ce46b7c2c8bbf17b0408122fbfb4e47f4bffc688"
3138-
integrity sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==
3135+
nodemailer@^9.0.1:
3136+
version "9.0.1"
3137+
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-9.0.1.tgz#871c04d8423fc0c790289f4a8f6f71f1b3563e8c"
3138+
integrity sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==
31393139

31403140
normalize-path@^3.0.0:
31413141
version "3.0.0"
@@ -3786,7 +3786,16 @@ string-length@^4.0.2:
37863786
char-regex "^1.0.2"
37873787
strip-ansi "^6.0.0"
37883788

3789-
"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2:
3789+
"string-width-cjs@npm:string-width@^4.2.0":
3790+
version "4.2.3"
3791+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
3792+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
3793+
dependencies:
3794+
emoji-regex "^8.0.0"
3795+
is-fullwidth-code-point "^3.0.0"
3796+
strip-ansi "^6.0.1"
3797+
3798+
string-width@4.2.3, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2:
37903799
version "4.2.3"
37913800
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
37923801
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -3809,7 +3818,14 @@ string_decoder@~1.1.1:
38093818
dependencies:
38103819
safe-buffer "~5.1.0"
38113820

3812-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
3821+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
3822+
version "6.0.1"
3823+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
3824+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
3825+
dependencies:
3826+
ansi-regex "^5.0.1"
3827+
3828+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
38133829
version "6.0.1"
38143830
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
38153831
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4190,7 +4206,7 @@ wordwrap@^1.0.0:
41904206
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
41914207
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
41924208

4193-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
4209+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
41944210
version "7.0.0"
41954211
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
41964212
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -4208,6 +4224,15 @@ wrap-ansi@^6.2.0:
42084224
string-width "^4.1.0"
42094225
strip-ansi "^6.0.0"
42104226

4227+
wrap-ansi@^7.0.0:
4228+
version "7.0.0"
4229+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
4230+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
4231+
dependencies:
4232+
ansi-styles "^4.0.0"
4233+
string-width "^4.1.0"
4234+
strip-ansi "^6.0.0"
4235+
42114236
wrap-ansi@^8.1.0:
42124237
version "8.1.0"
42134238
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

api/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ PRISM_OIDC_CLIENT_SECRET=
1010
PRISM_SESSION_SECRET=
1111
# Default one week session time
1212
PRISM_SESSION_TTL_SECONDS=604800
13+
14+
# Local map export storage (optional — docker-compose.develop.yml sets RustFS defaults).
15+
# EXPORT_MAP_S3_BUCKET=prism-dev
16+
# AWS_ACCESS_KEY_ID=rustfsadmin
17+
# AWS_SECRET_ACCESS_KEY=rustfsadmin
18+
# AWS_ENDPOINT_URL=http://rustfs:9000 # server-side S3 (Docker network)
19+
# AWS_PRESIGN_ENDPOINT_URL=http://localhost:9000 # presigned download URLs (browser)

api/README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,33 @@ export KOBO_USERNAME=kobo_user KOBO_PASSWORD=test
4242
make api
4343
```
4444

45-
This starts two containers via `docker-compose.develop.yml`:
45+
This starts four containers via `docker-compose.develop.yml`:
4646
- **`db`** — PostGIS (Postgres) on host port **54321**
47+
- **`rustfs`** — local S3-compatible storage for map export artifacts (API port **9000**, console **9001**)
4748
- **`api`** — FastAPI (uvicorn with hot reload) on host port **80**
49+
- **`export_map_worker`** — polls `map_export_jobs`, runs Playwright export, writes artifacts to RustFS
50+
51+
### Local map export storage (RustFS)
52+
53+
Production stores batch and scheduled map exports in **AWS S3** (`EXPORT_MAP_S3_BUCKET`, e.g. `s3://prism-wfp/batch-maps`). Local dev uses **RustFS** so the same code paths run without real AWS credentials for artifacts.
54+
55+
`docker-compose.develop.yml` sets this automatically for **`api`** and **`export_map_worker`**:
56+
57+
| Variable | Local value | Role |
58+
|---|---|---|
59+
| `EXPORT_MAP_S3_BUCKET` | `prism-dev` | Bucket created by `rustfs-init` |
60+
| `AWS_ENDPOINT_URL` | `http://rustfs:9000` | Server-side S3 calls (worker, API inside Docker) |
61+
| `AWS_PRESIGN_ENDPOINT_URL` | `http://localhost:9000` | Presigned download URLs the browser can reach |
62+
63+
RustFS console: **http://localhost:9001** (login `rustfsadmin` / `rustfsadmin`).
64+
65+
These flows stay aligned with production when `EXPORT_MAP_S3_BUCKET` is set and `EXPORT_MAP_LOCAL_OUTPUT_DIR` is empty (the compose default):
66+
67+
1. **Write**`export_map_worker`
68+
2. **Dedupe**`POST /export-map/jobs` and `scheduled_public_maps` cron
69+
3. **Download**`GET /export-map/jobs/{id}` and admin schedule download
70+
71+
The alternative dev mode, `EXPORT_MAP_LOCAL_OUTPUT_DIR`, writes `file://…` URIs to disk instead of S3 and skips the RustFS/prod-aligned path.
4872

4973
### 3. Run migrations and seed data
5074

@@ -55,7 +79,7 @@ make db-migrate
5579
make db-seed
5680
```
5781

58-
`db-migrate` applies Alembic migrations (`upgrade head`). `db-seed` runs migrations first, then inserts sample dev data (Mozambique AA metadata, a `local_dev_user`, and example alert rows).
82+
`db-migrate` applies Alembic migrations (`upgrade head`). `db-seed` runs migrations first, then inserts sample dev data (Mozambique AA metadata, a `local_dev_user`, example alert rows, and five `[Seed]` map export schedules for cron/download QA).
5983

6084
### 4. Verify
6185

api/docker-compose.develop.yml

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,56 @@ services:
1616
retries: 12
1717
start_period: 30s
1818

19+
# Local S3-compatible storage for map export artifacts (dev only).
20+
rustfs:
21+
image: rustfs/rustfs:latest
22+
ports:
23+
- "9000:9000"
24+
- "9001:9001"
25+
environment:
26+
RUSTFS_ACCESS_KEY: rustfsadmin
27+
RUSTFS_SECRET_KEY: rustfsadmin
28+
RUSTFS_CONSOLE_ENABLE: "true"
29+
RUSTFS_ADDRESS: 0.0.0.0:9000
30+
RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001
31+
volumes:
32+
- rustfs_data:/data
33+
command: ["/data"]
34+
healthcheck:
35+
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:9000/health || exit 1"]
36+
interval: 5s
37+
timeout: 5s
38+
retries: 12
39+
start_period: 40s
40+
41+
rustfs-init:
42+
image: amazon/aws-cli:2.27.50
43+
depends_on:
44+
rustfs:
45+
condition: service_healthy
46+
environment:
47+
AWS_ACCESS_KEY_ID: rustfsadmin
48+
AWS_SECRET_ACCESS_KEY: rustfsadmin
49+
AWS_DEFAULT_REGION: us-east-1
50+
entrypoint: /bin/sh
51+
command:
52+
- -c
53+
- |
54+
until aws --endpoint-url http://rustfs:9000 s3 ls >/dev/null 2>&1; do
55+
echo "waiting for rustfs..."
56+
sleep 2
57+
done
58+
aws --endpoint-url http://rustfs:9000 s3 mb s3://prism-dev 2>/dev/null || true
59+
1960
api:
2061
env_file:
2162
- path: .env
2263
required: false
2364
depends_on:
2465
db:
2566
condition: service_healthy
67+
rustfs-init:
68+
condition: service_completed_successfully
2669
volumes:
2770
- ./prism_app:/prism_app
2871
- ./pyproject.toml:/pyproject.toml
@@ -52,18 +95,27 @@ services:
5295
- VECTOR_STORE_ACCESS_KEY_ID=${VECTOR_STORE_ACCESS_KEY_ID}
5396
- VECTOR_STORE_SECRET_ACCESS_KEY=${VECTOR_STORE_SECRET_ACCESS_KEY}
5497
- VECTOR_STORE_DEFAULT_REGION=${VECTOR_STORE_DEFAULT_REGION}
98+
# Map export artifacts → local RustFS
99+
- EXPORT_MAP_S3_BUCKET=prism-dev
100+
- EXPORT_MAP_LOCAL_OUTPUT_DIR=
101+
- AWS_ACCESS_KEY_ID=rustfsadmin
102+
- AWS_SECRET_ACCESS_KEY=rustfsadmin
103+
- AWS_DEFAULT_REGION=us-east-1
104+
- AWS_ENDPOINT_URL=http://rustfs:9000
105+
- AWS_PRESIGN_ENDPOINT_URL=http://localhost:9000
55106
command: uvicorn prism_app.main:app --host 0.0.0.0 --port 80 --reload
56107
# Infinite loop, to keep it alive, for debugging
57108
# command: bash -c "while true; do echo 'sleeping...' && sleep 10; done"
58-
109+
59110
export_map_worker:
60111
shm_size: "2gb"
61112
depends_on:
62113
db:
63114
condition: service_healthy
115+
rustfs-init:
116+
condition: service_completed_successfully
64117
volumes:
65118
- ./prism_app:/prism_app
66-
- ./cache:/cache:rw
67119
build:
68120
context: ./
69121
dockerfile: ./Dockerfile.export-map-worker
@@ -74,14 +126,16 @@ services:
74126
- POSTGRES_DB=postgres
75127
- POSTGRES_PORT=5432
76128
- POSTGRES_PASSWORD=!ChangeMe!
77-
- EXPORT_MAP_S3_BUCKET=${EXPORT_MAP_S3_BUCKET:-}
78-
- EXPORT_MAP_LOCAL_OUTPUT_DIR=${EXPORT_MAP_LOCAL_OUTPUT_DIR:-/cache/map_exports}
79-
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-}
80-
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
81-
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
129+
- EXPORT_MAP_S3_BUCKET=prism-dev
130+
- EXPORT_MAP_LOCAL_OUTPUT_DIR=
131+
- AWS_ACCESS_KEY_ID=rustfsadmin
132+
- AWS_SECRET_ACCESS_KEY=rustfsadmin
133+
- AWS_DEFAULT_REGION=us-east-1
134+
- AWS_ENDPOINT_URL=http://rustfs:9000
82135
- BROWSER_POOL_SIZE=1
83136
working_dir: /prism_app
84137
command: [ "python", "-m", "prism_app.workers.export_map_worker" ]
85138

86139
volumes:
87140
db_data:
141+
rustfs_data:

0 commit comments

Comments
 (0)