OpenAI Build Week mutual-aid app for urgent local voluntary help. https://test.whoneedhelp.com
Go to file
2026-07-19 08:33:24 +03:00
.github/workflows feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
android chore: add local quality and security gates 2026-07-19 02:50:52 +03:00
assets test: harden browser accessibility and resilience 2026-07-19 03:32:25 +03:00
config feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
deploy chore: add local quality and security gates 2026-07-19 02:50:52 +03:00
docs feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
e2e feat: localize the complete product experience 2026-07-19 04:25:38 +03:00
lib feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
load/k6 test: exercise authenticated load and database writes 2026-07-19 05:59:26 +03:00
ops feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
priv feat: add measured cursor pagination 2026-07-19 05:12:07 +03:00
rel feat: implement Who Need Help MVP 2026-07-18 16:52:16 +03:00
scripts feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
test feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
.dialyzer_ignore.exs feat: localize the complete product experience 2026-07-19 04:25:38 +03:00
.dockerignore test: add isolated browser end-to-end flow 2026-07-19 01:28:28 +03:00
.env.e2e.example feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
.env.example feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
.env.load.example feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
.formatter.exs feat: implement Who Need Help MVP 2026-07-18 16:52:16 +03:00
.gitattributes feat: implement Who Need Help MVP 2026-07-18 16:52:16 +03:00
.gitignore test: add isolated browser end-to-end flow 2026-07-19 01:28:28 +03:00
compose.backup.yaml feat: add verified encrypted S3 backups 2026-07-19 08:00:12 +03:00
compose.e2e.yaml chore: add local quality and security gates 2026-07-19 02:50:52 +03:00
compose.external-boundaries.yaml feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
compose.load.yaml test: verify replica failure and rolling recovery 2026-07-19 06:41:06 +03:00
compose.observability.yaml feat: add verified local observability stack 2026-07-19 07:11:29 +03:00
compose.quality.yaml test: harden browser accessibility and resilience 2026-07-19 03:32:25 +03:00
compose.yaml feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00
Dockerfile test: exercise authenticated load and database writes 2026-07-19 05:59:26 +03:00
Dockerfile.backup feat: add verified encrypted S3 backups 2026-07-19 08:00:12 +03:00
Dockerfile.minio feat: add verified encrypted S3 backups 2026-07-19 08:00:12 +03:00
mix.exs chore: add local quality and security gates 2026-07-19 02:50:52 +03:00
mix.lock chore: add local quality and security gates 2026-07-19 02:50:52 +03:00
README.md feat: verify local external service boundaries 2026-07-19 08:33:24 +03:00

Who Need Help

Who Need Help is a working mutual-aid MVP for urgent, local, voluntary help. The first priority is pickup and delivery of a legal medicine that has already been purchased or reserved. The same urgent-help flow also supports safe, non-emergency fuel delivery, wheel help, bicycle and motorcycle problems, vehicle breakdowns, and practical support after a secured road incident.

It is not an emergency or medical service, does not prescribe or sell medicine, and does not process payments. A helper may publish an optional external thank-you link; money goes directly between users outside the platform.

What is implemented

  • Phoenix 1.8 LiveView application with email magic-link/password auth and 18+ self-attestation.
  • Data-driven, translated category tree with validated per-category fields, seven selectable urgent/roadside scenarios, community proposals/votes, and human approve/reject/merge tools.
  • Separate social Activity mode for coffee, cinema, walks, and hikes, with organizer-approved membership, capacity-safe joins, private group chat, and exact coordinates visible only to approved participants. Activities never affect urgent-helper reputation; Activity and Activity-message reports expose only the linked evidence to audited moderators.
  • Request lifecycle: open → matched → in_progress → completed, plus cancel and expiry paths.
  • PostgreSQL/PostGIS locations, MapLibre map, private matched chat, Phoenix PubSub/Presence, and optional consent-driven live location sharing.
  • Handover code plus both-party confirmation before verified completion.
  • Double-blind reviews, public trust summaries, and a helper leaderboard that prioritizes unique location-supported and handover-verified counterparts before raw totals.
  • Bidirectional discovery blocks, scoped reports, account/request/category moderation, abuse-signal review, and audited moderator access to only the conversation linked by a report.
  • Optional GPS evidence derived from browser accuracy envelopes. Raw current positions are deleted on stop, terminal match state, or participant block.
  • PostgreSQL-backed cross-replica action-limit policies configured by the operator. No unapproved numeric thresholds are enabled by default.
  • Public manual social links, always marked unverified, plus optional verified GitHub linking through a state- and PKCE-protected OAuth flow. Provider access tokens are not stored.
  • EN/UK/RU UI foundation and installable PWA metadata/service worker.
  • Native Android WebView client with the same authenticated LiveView, map, private chat, and a user-started location foreground service. Its persistent notification exposes Stop, it continues while the Activity is minimized, and it retains only the current point. Reproducible Docker targets export distinct local and public-staging debug APKs; production signing and store publication are not configured.
  • Local, advisory Codex category review through the user's ChatGPT-authenticated Codex CLI. It receives a PII-free export and never writes to the database.
  • One immutable release image with web, worker, and one-shot migrate roles.
  • Docker Compose and Helm/kind deployment paths with 2 web and 2 worker replicas by default.

Additional social providers, background PWA or unattended location tracking, platform payments, production Android signing/store publication, iOS, automatic punitive fraud decisions, and jurisdiction-specific public-launch policies are deliberately not claimed as complete.

Fast start with Docker Compose

Prerequisite: Docker with the Compose plugin.

./scripts/compose-up.sh

Open:

Compose starts Traefik, PostGIS, Mailpit, a migration runner, 2 web replicas, and 2 Oban worker replicas. It waits for readiness and verifies a PubSub message broadcast from a different BEAM node. Registration emails appear in Mailpit.

Inspect the exact state:

docker compose -p who_need_help ps -a
docker compose -p who_need_help logs -f web worker
./scripts/verify-realtime-cluster.sh compose

Create and restore-test a database backup without restoring over the source:

backup_output=$(./scripts/backup-compose.sh)
backup_path=$(printf '%s\n' "$backup_output" | sed -n 's/^Backup: //p')
./scripts/restore-drill-compose.sh "$backup_path"

For the reproducible encrypted S3-compatible drill, first start the isolated load project and then run:

./scripts/load-stack-up.sh
./scripts/backup-s3-drill.sh local-encrypted-backup

The command generates MinIO and Restic secrets only in ignored mode-0600 .env.load, streams pg_dump directly into an encrypted Restic repository, restores it into a new temporary database, checks corruption and interruption failure paths, removes those temporary buckets, and retains the successful encrypted bucket in local MinIO. It never writes a plaintext dump to the host.

Exercise the real OAuth/SMTP protocol clients and the future push adapter boundary entirely inside an isolated Docker network:

./scripts/external-boundaries-run.sh local-boundaries

The command publishes no host ports, generates independent one-run credentials in an ignored mode-0600 file, verifies success, rejection, retry, replay, and timeout paths, retains only non-secret evidence, and removes its exact Compose project and images. The HTTP push adapter is deliberately not connected to a product workflow and is not presented as an FCM or APNs implementation.

The commands, boundaries, and unclaimed production properties are documented in the operations runbook.

Local defaults are intentionally limited to local development. Copy .env.example to .env and replace every secret before any public deployment. Generate independent values with mix phx.gen.secret; do not reuse the session secret as the BEAM cookie or handover secret. External Helm deployments must set app.host, app.scheme, and app.urlPort to the public URL used in email links, and must set app.mapTileUrl to a tile service whose policy and capacity fit the deployment.

Rotate all local application secrets and the existing local PostgreSQL role without printing the generated values:

./scripts/rotate-local-secrets.sh
docker compose up -d --wait

This preserves the named PostgreSQL volume, but invalidates existing browser sessions and changes handover codes for active local requests.

Optional verified GitHub linking

Create a GitHub OAuth App with this exact callback URL for the active public origin:

https://YOUR_PHX_HOST/auth/social/github/callback

Set both GITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET in the ignored .env or deployment Secret, then recreate the application containers. If both values are empty, the feature stays disabled and the profile explains that state. A partial pair is rejected at startup. The flow requests only the public GitHub identity, protects callbacks with state and PKCE, binds the flow to the initiating signed-in user, and never persists the provider access token. The optional GITHUB_OAUTH_*_URL and HTTP timeout variables exist only so the isolated boundary drill can use its internal mock. Leave them empty for the official GitHub endpoints and Req defaults.

Tests

The reproducible test command builds a dedicated test target and uses the project's PostGIS service:

./scripts/test.sh

It creates/updates only the project-scoped who_need_help_test database.

The complete local CI-equivalent command uses pinned containerized tools and an isolated PostgreSQL volume:

./scripts/quality.sh

It checks shell scripts, Dockerfiles, the GitHub Actions workflow, every Compose profile, the rendered Helm chart, tracked-source secrets and infrastructure misconfigurations, Elixir formatting/compilation/xref/Credo/Sobelow/Dialyzer, retired Hex packages, locked npm dependencies, all Phoenix tests, the pinned backup-tool, MinIO server/client, external-boundary mock, and production release images. Its generated database credentials are random and exist only for that run. The exact database volume, networks, temporary source snapshot, and one-run images are removed automatically.

The same external-service protocol drill used by CI can be run independently:

./scripts/external-boundaries-run.sh local-boundaries

The cursor-pagination database benchmark also creates a one-run Compose project, random database credentials, and a separate PostgreSQL volume:

./scripts/db-scale-benchmark.sh

It seeds 50,000 rows in each large benchmark table by default, captures PostgreSQL 18 JSON EXPLAIN (ANALYZE, BUFFERS) plans before and after the cursor-index migration, verifies two consecutive keyset pages for gaps and duplicates, and removes its database project and volume. DB_SCALE_ROWS changes the sample size; it is an experiment input, not a resource minimum. Ignored evidence is written below output/db-scale/.

The browser E2E command creates a uniquely named, isolated Compose project with its own PostGIS volume, Mailpit instance, Traefik proxy, two web replicas, and two worker replicas:

./scripts/e2e-run.sh

On its first run it generates .env.e2e with independent random local secrets and mode 0600. The suite registers users through real Mailpit messages and uses the audited one-time bootstrap command inside only the isolated E2E database to create its moderator. It covers public/authentication boundaries; the urgent medicine flow through matching, realtime chat, handover, and double-blind reviews; and the Activity flow through join approval, private group chat, message-scoped reporting, blocking, privacy defaults, an unverified social link, category moderation, report resolution, and account restriction. The same gate checks keyboard skip navigation, WCAG violations and contrast on four public pages in both themes, horizontal overflow at three viewport widths, an actual locally served raster map tile, LiveView offline/reconnect UI, public RU/UK locale persistence, and the selected Ukrainian locale in an authenticated LiveView. An optional list of Playwright spec paths can be passed after the command for a focused diagnostic run. It retains traces, screenshots, video, and Compose logs under the ignored output/e2e/ directory on failure. The exact E2E project, database volume, and networks are removed automatically; the normal who_need_help Compose project is not recreated.

The Android device suite builds a dedicated debug and instrumentation APK, boots an API 37 emulator in an isolated container without external networking, and serves its HTTP fixture only on device loopback:

./scripts/android-instrumentation-test.sh

On first run it generates the ignored .env.android-test with a randomized device-loopback origin and mode 0600. The suite covers denied and granted location permission, same-origin deep links, Activity recreation, foreground location upload, the persistent notification Stop action, and a disconnected Stop request with visible retry state. Results and failure diagnostics are retained under ignored output/android-instrumentation/; the exact emulator container and one-run image are removed automatically.

First administrator

Register and confirm the first account, then explicitly bootstrap it:

./scripts/bootstrap-admin.sh you@example.com --confirm

This succeeds only while no administrator exists and writes an audit event. After bootstrap, an administrator can manage roles in /moderation; the last administrator cannot demote themselves. For kind, append kind:

./scripts/bootstrap-admin.sh you@example.com --confirm kind

Optional shared action limits

RATE_LIMIT_POLICIES_JSON configures atomic PostgreSQL counters shared by every web replica. Its shape is:

{"action_name":{"limit":"POSITIVE_INTEGER","window_seconds":"POSITIVE_INTEGER"}}

The strings above describe the required types and are not a runnable policy. Keep {} until numeric limits have been approved from policy and observed traffic. Supported actions are listed in docs/trust-safety.md.

Local Kubernetes verification

The kind script downloads checksum-verified kubectl, Helm, and kind binaries into .tools/, creates a project-owned cluster, loads the local image, and installs the Helm chart, waits for both Deployments, and verifies cross-node PubSub:

./scripts/kind-up.sh

Open:

The script refuses to modify a pre-existing cluster named who-need-help unless the project ownership marker exists. On first install it creates independent random application and PostgreSQL credentials in the who-need-help-local Kubernetes Secret without writing them to Git. The local PostGIS data directory persists inside the kind node. When migrating an older project-owned cluster from the former emptyDir deployment, the script creates and validates a local dump before replacing the database workload, then restores that dump. After a successful rollout it also removes the obsolete chart Secret and only the local Helm history revisions that stored the former inline credential fields.

Exercise the verified local rolling-update path without recreating PostGIS or the Secret:

./scripts/kind-rolling-verify.sh local-kind-rollout

Compose crash/replacement and Oban retry checks use the separate load project:

./scripts/load-stack-up.sh
./scripts/load-resilience-run.sh local-resilience

Both scripts retain ignored evidence under output/resilience/; their exact mutation and cleanup boundaries are documented in the operations runbook.

Start and verify the local monitoring profile against every isolated load web replica:

./scripts/observability-run.sh local-observability

The command validates Prometheus and Alertmanager configuration, provisions the Grafana datasource and dashboard, discovers each current web container as a separate target, and exercises a firing/resolved alert by stopping and recovering exactly one verified load replica. It prints the loopback-only random ports and retains non-secret evidence below output/observability/. The generated Grafana password remains only in mode-0600 .env.load.

Stop only the monitoring services while leaving their local metric volumes and the load application running:

./scripts/observability-stop.sh

For an external cluster, provide a real PostgreSQL/PostGIS service and a pre-created Secret through required existingSecret; the chart never renders credentials from tracked values. The Secret must contain DATABASE_URL, SECRET_KEY_BASE, HANDOVER_SECRET, RELEASE_COOKIE, and METRICS_TOKEN, and may additionally contain the GitHub OAuth client ID and client secret described above. The chart intentionally has no invented CPU/RAM limits or HPA thresholds; measure this application in the target environment before setting them.

Local Codex category review

Export the redacted proposals from a running release:

docker compose -p who_need_help exec -T web \
  /app/bin/who_need_help eval \
  'WhoNeedHelp.CatalogModeration.export_open_proposals() |> IO.puts()' \
  > proposals.json

Then run the advisory review:

./scripts/codex-review-categories.sh proposals.json recommendations.json

The script exits unless codex login status is exactly Logged in using ChatGPT. It uses no OpenAI API key, no usage-based API billing, and no fallback provider. Recommendations require a human moderator action.

Design documentation

Exact dependency versions are locked in mix.lock, assets/package-lock.json, the Dockerfile, Compose file, and tool bootstrap script.