The download API for https://api.coeqwal.org. Lists available scenarios in S3 and presigns a short-lived GET so the website can serve direct downloads of the scenario ZIP and CSV outputs.
| AWS resource | Value |
|---|---|
| Function name | coeqwalPresignDownload |
| Runtime | Node.js 20.x or 22.x (uses built-in AWS SDK v3, no node_modules needed) |
| Trigger | API Gateway v2 (HTTP API, id x66ckhp067), routes GET /scenario and GET /download |
| Action | List scenario prefixes under coeqwal-model-run/scenario/, presign a GET for the requested file |
| Source | index.mjs (single file) |
| Test events | tests/ |
| Route | Behavior |
|---|---|
GET /scenario |
Walks s3://coeqwal-model-run/scenario/<id>/ prefixes. Returns one row per scenario with the keys for the run ZIP, the CalSim output CSV, and the SV input CSV (each null if not present) |
GET /download?scenario=s0020&type=zip|output|sv |
Validates the scenario id and type, finds the matching key in S3, presigns a GET (15 min expiry) with Content-Disposition: attachment, and returns a 302 redirect to the presigned URL |
Both routes set permissive CORS so the frontend can call them from any origin.
Single index.mjs, no external deps. Same flow as the ETL trigger.
Via AWS Console:
- AWS Console -> Lambda -> Functions ->
coeqwalPresignDownload - Click the Code tab
- Select all in the inline editor, paste the full contents of
index.mjs - Click Deploy
Via Cloud9 / CLI:
cd ~/environment/coeqwal-backend/api/lambda/coeqwalPresignDownload
zip lambda.zip index.mjs
aws lambda update-function-code --function-name coeqwalPresignDownload --zip-file fileb://lambda.zip
rm lambda.zip| File | Route exercised |
|---|---|
tests/test-event-scenario-list.json |
GET /scenario. Lists all available scenarios |
tests/test-event-download.json |
GET /download?scenario=s0020&type=zip. Returns a presigned URL |
In the AWS Console: Lambda -> coeqwalPresignDownload -> Test tab. Create a new test event and paste the JSON from one of the files in tests/.
Via AWS CLI:
# List scenarios
aws lambda invoke \
--function-name coeqwalPresignDownload \
--cli-binary-format raw-in-base64-out \
--payload file://api/lambda/coeqwalPresignDownload/tests/test-event-scenario-list.json \
--log-type Tail \
response.json \
--query 'LogResult' --output text | base64 -d
cat response.json | jq .
# Download presign
aws lambda invoke \
--function-name coeqwalPresignDownload \
--cli-binary-format raw-in-base64-out \
--payload file://api/lambda/coeqwalPresignDownload/tests/test-event-download.json \
--log-type Tail \
response.json \
--query 'LogResult' --output text | base64 -d- Runtime: Node.js 20.x or 22.x
- Handler:
index.handler - Timeout: 30 seconds
- Memory: 256 MB minimum
- Environment variable (optional):
COEQWAL_S3_BUCKET- defaults tocoeqwal-model-runif unset - IAM role has S3 read access to
coeqwal-model-runbucket
AccessDenied: Access Denied
Add S3 read to the Lambda's IAM role:
{
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetObject"],
"Resource": [
"arn:aws:s3:::coeqwal-model-run",
"arn:aws:s3:::coeqwal-model-run/*"
]
}{"error": "Not Found"}
Check the API Gateway route matches the Lambda's expected paths (/scenario, /download).
- The FastAPI service that serves the rest of the API:
../../coeqwal-api/ - Frontend caller (separate repo,
coeqwal-website):apps/main/app/lib/api/fileDownloadApi.ts - AWS-side resource details (API Gateway id, IAM, costs):
INFRASTRUCTURE.mdin the privatecoeqwal-private-docsrepo