The gallformers.org website - a comprehensive database and reference guide for galls.
# Install Elixir dependencies
mix setup
# Start the dev server
mix phx.serverVisit localhost:4000 in your browser.
- Elixir 1.19+ and OTP 28+
- Node.js 20+ (for asset compilation)
- PostgreSQL 16+
- libvips (for image processing - resizing uploaded images)
- Playwright (for E2E tests only —
make e2e-setupinstalls browsers)
The easiest way on macOS:
brew install elixirOr use asdf for version management:
asdf plugin add elixir
asdf plugin add erlang
asdf install erlang 28.0
asdf install elixir 1.19.0-otp-28# macOS
brew install postgresql@16
brew services start postgresql@16
# Ubuntu/Debian
sudo apt-get install postgresql postgresql-contrib# macOS
brew install libvips
# Ubuntu/Debian
sudo apt-get install libvips libvips-devmake e2e-setupVerify with make e2e-setup.
Ensure PostgreSQL is running locally, then:
# Create the database and run migrations
mix ecto.setup
# Or download a snapshot from production and restore locally
make download-dbmix phx.server # Start dev server
make test # Rebuild test DB + run tests (excludes E2E)
mix test # Run tests without rebuilding DB
mix format # Format code
mix credo --strict # Code quality
mix precommit # Run all checks (do this before committing)
make ci # Full CI check (same as GitHub Actions)Tests use a separate PostgreSQL database (gallformers_test) built from:
- Ecto migrations - Schema only (no production data)
priv/repo/test_seeds.sql- Minimal seed data for tests
make test rebuilds this automatically. Use make test-db to rebuild manually.
Browser-based E2E tests use phoenix_test_playwright with Firefox. All tests run against a production data copy and are excluded from regular test runs and CI.
Requires Playwright browsers — see Prerequisites.
make e2e # Run all E2E tests
make e2e-changed # Run only tests affected by changed files (smart)
make e2e-public # Public pages only
make e2e-search # Search functionality only
make e2e-browse # Species/hosts/galls browsing only
make e2e-admin # Admin pages only
make e2e-auth # Authentication flows onlymake e2e-headed # Run with visible browser
E2E_HEADED=1 make e2e-public # Specific area with visible browserE2E tests are organized by functional area in test/e2e/:
| Directory | Coverage |
|---|---|
public/ |
Home, about, glossary, resources, explore |
search/ |
Global search, ID tool |
browse/ |
Species, hosts, galls detail pages |
admin/ |
Admin dashboard, taxonomy admin, reclassify modal |
auth/ |
Login, logout, protected routes |
See test/support/e2e_case.ex for documentation. All E2E tests must be tagged:
defmodule GallformersWeb.E2E.MyTest do
use GallformersWeb.E2ECase
@moduletag :e2e
@moduletag :e2e_public # Area tag
test "page loads", %{conn: conn} do
conn
|> visit("/")
|> assert_has("h1", text: "Welcome")
end
endgallformers/
├── lib/ # Elixir application code
├── assets/ # Frontend (JS, CSS, Tailwind)
├── priv/ # Static files, migrations, database
├── test/ # Tests
├── config/ # Phoenix configuration
└── services/ # Auxiliary services (boundaries, usda_plants)
Range maps use PMTiles vector tiles generated from Natural Earth shapefiles. In production, tiles are served via CloudFront from S3. In dev, tiles are served locally from priv/static/data/boundaries.pmtiles.
# Build tiles locally (~2 minutes)
cd services/boundaries
./build_boundaries.sh ../../priv/static/data/boundaries.pmtiles
# Or skip the local build and use production tiles:
TILES_URL=https://gallformers.org/tiles/boundaries.pmtiles mix phx.serverRequires: gdal, tippecanoe, jq. See services/boundaries/README.md and runbooks/map-tiles.md for full details.
Production runs on Fly.io:
fly deploy # Deploy to production
fly logs # View logs
fly status # Check statusSee runbooks/ for operational procedures.
The full release workflow:
- Commit and push to main —
git push origin main - Wait for CI — The "CI V2" workflow runs format, compile, credo, and tests
- Wait for deploy — On CI success, "Deploy V2" automatically deploys to Fly.io and runs smoke tests
- Verify deploy — Check that the site is working:
fly statusor visit gallformers.org - Create the release — Run
/releasein Claude Code, review the generated notes, and approve
The /release skill handles tag naming, commit collection, and release note generation. Tags use CalVer format: v2026.2.6, with .2, .3 suffixes for multiple same-day releases. Release notes are published at github.com/jeffdc/gallformers/releases.
The PostgreSQL database backup strategy is TBD as part of the Postgres migration. Daily snapshots continue to be stored in S3:
- Public (
s3://gallformers-backups/public/) - Sanitized, PII removed - Private (
s3://gallformers-full-backups/) - Full backup with PII
For restore procedures, see runbooks/restore-database.md. For AWS bucket details, see docs/ops/aws-private-backup-bucket.md.
The users table contains personally identifiable information:
| Field | Description |
|---|---|
auth0_id |
Unique identifier from Auth0 |
display_name |
User's chosen display name |
nickname |
Fallback name from Auth0 |
inaturalist_url |
Link to iNaturalist profile |
social_url |
Link to social media |
personal_url |
Link to personal website |
Public database downloads are sanitized - all PII fields are set to NULL and auth0_id is replaced with a placeholder.
- Public site requires no authentication
- Admin/curation features require Auth0 login
- User management is handled via Auth0 console
- Production: gallformers.org
- Images: AWS S3
- Auth: Auth0
- Domains: Namecheap (gallformers.org, gallformers.com)
- Status page: jeffdc.github.io/gallformers-status - Uptime monitoring via Upptime
- Metrics dashboard: fly-metrics.net - CPU, memory, HTTP metrics (view-only, no alerting)
Fly.io also sends automatic email alerts on OOM (out-of-memory) events.
All application logs (requests, errors, crashes) are structured JSON via LoggerJSON, written to a persistent file in production.
- Production:
/data/logs/app.log(size-rotated, 1 GB max)
Retrieve logs from production:
fly ssh sftp get /data/logs/app.logAnalyze with jq:
# Find request errors
cat app.log | jq -c 'select(.conn.status >= 500)'
# Find application errors
cat app.log | jq -c 'select(.severity == "error")'See CODING_STANDARDS.md for detailed format and analysis examples.
See CLAUDE.md for detailed development guidelines.