who_need_help/README.md

264 lines
11 KiB
Markdown

# 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.
```bash
./scripts/compose-up.sh
```
Open:
- app: <http://localhost:4010>
- local email inbox: <http://localhost:8027>
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:
```bash
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:
```bash
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"
```
The commands, boundaries, and unclaimed production properties are documented
in [the operations runbook](docs/operations.md).
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:
```bash
./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:
```text
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.
## Tests
The reproducible test command builds a dedicated test target and uses the
project's PostGIS service:
```bash
./scripts/test.sh
```
It creates/updates only the project-scoped `who_need_help_test` database.
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:
```bash
./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.
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.
## First administrator
Register and confirm the first account, then explicitly bootstrap it:
```bash
./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`:
```bash
./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:
```json
{"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:
```bash
./scripts/kind-up.sh
```
Open:
- app: <http://localhost:4011>
- Mailpit: <http://localhost:8028>
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.
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 both GitHub OAuth variables 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:
```bash
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:
```bash
./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
- [Product specification](docs/product-spec.md)
- [Architecture](docs/architecture.md)
- [Trust and safety](docs/trust-safety.md)
- [Operations runbook](docs/operations.md)
- [Performance measurement](docs/performance.md)
- [Implementation verification and known limits](docs/verification.md)
- [Verified dependency baseline](docs/dependency-baseline.md)
- [PostgreSQL/PostGIS ADR](docs/decisions/0001-postgresql-postgis-over-spacetimedb.md)
Exact dependency versions are locked in `mix.lock`,
`assets/package-lock.json`, the Dockerfile, Compose file, and tool bootstrap
script.