# Neighborhoods backend -- setup

Mirrors `Events/Weddings/server/BACKEND_MYSQL_MIGRATION.md`'s approach: this
app is fully usable with **zero setup** the moment it's deployed (FileDatabase,
a JSON file), and MySQL is an optional upgrade you switch on later, not a
prerequisite to go live.

I (Claude) cannot run any of the steps below myself -- no PHP, MySQL, or
cPanel access from where I work. Everything here is exact enough to run
as-is; nothing needs guessing.

## Locally verified vs. server-verified

I have no PHP runtime available, so nothing in this backend has actually
been executed. What I *could* verify without one:

- **Verified**: every file's braces/parens balance; every class name
  referenced in a `use` import or `new X(...)` call exists with a matching
  namespace; every controller method called from the router exists with
  that name; every service method called from a controller exists; every
  `mysqli_stmt::bind_param()` type-string length matches its argument count
  (checked programmatically across all 34 calls in `MysqliDatabase.php`,
  including the new lots/conversations/messages/profile columns added for
  the features below).
- **Not verified** (needs a real PHP+web server run): actual request/response
  behavior, SQL correctness beyond type/arity (e.g. whether a query's logic
  is right), GD image re-encoding, file upload handling, session cookie
  behavior. Please run the smoke test in "3. Verify" below after deploying,
  and send me anything that errors -- I'll fix it from the actual error
  message.

## Satellite map, lot tracer, profile email/video, messaging (new)

The backend now has full support for the features the prototype frontend
already demonstrates in `localStorage` mode: a satellite PNG as the member
map, hand-traced clickable lot polygons, a profile email field, a 30-second
intro video, and neighbor-to-neighbor messaging (DM / list / broadcast).
**The prototype frontend is not wired to call any of this yet** -- see
"What's next" below. This section documents the API surface for when that
wiring happens.

New endpoints (all under `/api/v1/neighborhoods/{id}/...`, auth via
`X-Member-Token` same as existing routes; map-image/lot-color are
Platform-Host-only, matching `hostCanEditStreets`/`superHostCanEditStreets`
for lot add/remove):

- `POST .../map-image` (multipart, field `mapImage`) -- upload/replace the
  satellite PNG. Stored unmodified (no GD resize) because lot polygon
  coordinates are pixel-exact against the uploaded image's dimensions.
  Max 25MB, max 8000px per side, max 40M total pixels.
- `DELETE .../map-image` -- clears the map image and wipes all lots (a lot's
  points are meaningless without the image they were traced against).
- `POST .../lots` -- add a hand-traced lot (`street`, `houseNumber`, `points`
  array of `{x,y}`, `color`). Requires >= 3 points.
- `DELETE .../lots/{lotId}`
- `POST .../lots/{lotId}/color` -- Platform-Host-only; recolors every lot on
  the same street at once.
- `POST .../profile/email`
- `POST .../profile/intro-video` (multipart: `street`, `houseNumber`,
  `durationSeconds`, file field `video`) -- 30-second cap enforced
  server-side as defense-in-depth (client already enforces it during
  capture/upload, same policy as Photo Slots' 30-second couple's welcome
  video). Replaces any existing video; old file is deleted only after the
  new one is safely stored. Max 60MB, not re-encoded (no server-side
  transcoder available).
- `POST .../profile/intro-video/remove`
- `GET .../directory` -- every household with a name and an id, for the
  message-recipient picker.
- `GET .../conversations` / `POST .../conversations` (`type`: `DM`, `LIST`,
  or `BROADCAST`; `participantIds`; optional `subject`/`body` to send a
  first message in the same call)
- `GET .../conversations/{conversationId}`
- `POST .../conversations/{conversationId}/messages`

Uploaded videos and map images are served through the same `/media/...`
passthrough as photos (path-traversal-guarded via `realpath()`).

If you're upgrading an existing MySQL install (not a fresh one), re-run
`server/sql/schema.sql` -- it now includes `map_image_*` columns on
`neighborhoods`, a new `lots` table, new `email`/`intro_video_*` columns on
`household_profiles`, and new `conversations`/`conversation_participants`/
`messages` tables. FileDatabase installs need no migration step; the JSON
blob just grows new keys on first write.

## 1. Deploy (works immediately, no database needed)

Once the files are uploaded to
`/home/airbridg/public_html/ceriapps/Airbridge/Maps/Neighborhoods/server/`,
copy the one required config file:

```bash
cd /home/airbridg/public_html/ceriapps/Airbridge/Maps/Neighborhoods/server
cp config/app.example.php config/app.php
```

Edit `config/app.php` and confirm `basePath` matches how this domain/vhost
is actually configured -- the shipped value assumes the document root is
`public_html` and this app is reachable at
`.../ceriapps/Airbridge/Maps/Neighborhoods/server/public`. If that's wrong,
fix it here (this is exactly the kind of thing I can't confirm myself).

Optionally add a real Google Maps key to the same file
(`googleMapsApiKey`) so visitors never have to paste their own -- restrict
it to your domain via HTTP referrer restrictions in Google Cloud Console
first.

That's it. `GET .../server/public/health` should now return
`{"status":"ok","storageDriver":"file",...}`.

## 2. Create the first neighborhood (Hidden Meadow)

Neighborhood creation is gated by a bootstrap admin token (same role
`ADMIN_SETUP_TOKEN` plays for Weddings), read from the `ADMIN_SETUP_TOKEN`
environment variable. Set one in cPanel's PHP environment variables for
this app (or, if env vars prove unreliable on this account the way
`DB_DRIVER` was for Weddings, hardcode a value directly in
`AdminAuthService`'s construction in `server/public/index.php` as a
fallback -- tell me if you hit that and I'll wire it the same way).

Then, from any HTTP client (including a browser's fetch console):

```bash
curl -X POST https://<your-domain>/ceriapps/Airbridge/Maps/Neighborhoods/server/public/api/v1/neighborhoods \
  -H "X-Admin-Token: <your ADMIN_SETUP_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Hidden Meadow",
    "city": "<your city>",
    "state": "<your state>",
    "zip": "<your zip>",
    "godHostName": "Kerry Davis",
    "universeMemberId": "kerry-davis"
  }'
```

The response includes `godHostAccessToken` -- **save it immediately, it is
never shown again.** That token is what proves you're Hidden Meadow's God
Host on every subsequent request (sent as the `X-Member-Token` header).
With 34 houses on two streets, once the neighborhood exists you'd add both
streets from the admin panel exactly like the prototype's `admin.html`
already does (that frontend isn't wired to this live API yet -- see "What's
next" below).

## 3. Verify

```bash
BASE=https://<your-domain>/ceriapps/Airbridge/Maps/Neighborhoods/server/public

curl $BASE/health
curl $BASE/api/v1/config
curl $BASE/api/v1/neighborhoods
curl "$BASE/api/v1/neighborhoods/<id-from-step-2>"
```

Each should return JSON, not a PHP error page or blank response. If you get
a 500, the response body's `error.message` is deliberately specific (see
`JsonResponse`/each controller's `sendServiceError`) -- send it to me
as-is.

## 4. Upgrade to MySQL (optional, whenever you're ready)

1. In cPanel, create a new database and user dedicated to this app (do
   **not** reuse Weddings' `airbridg_photo_slots` database -- this is a
   separate app with its own schema). Something like:
   `airbridg_neighborhoods` / `airbridg_neighborhoods` user, matching the
   naming convention `database.example.php` already assumes.
2. Import the schema:
   ```bash
   mysql -u airbridg_neighborhoods -p airbridg_neighborhoods < server/sql/schema.sql
   ```
   (or run it via phpMyAdmin's Import tab if `mysql` CLI access isn't
   available on this account).
3. `cp server/config/database.example.php server/config/database.php` and
   fill in the real host/database/username/password.
4. That's the whole switch -- `server/public/index.php` detects
   `database.php`'s presence automatically and starts using
   `MysqliDatabase` instead of the file, no code change needed. Existing
   FileDatabase data (`server/storage/dev-database.json`) does **not**
   auto-migrate; if Hidden Meadow already has real data in file mode by
   this point, tell me and I'll write a one-time migration script (mirrors
   Weddings' `server/scripts/migrate_json_to_mysql.php`) rather than you
   re-entering everything.

## 5. Ceri.us login (not needed to launch, future step)

Dormant by design -- see `server/config/ceri.example.php` and
`CeriIdentityService`'s class doc. Requires Ceri-side work I can't do
myself: a new `app_id = 'neighborhoods'` row in `ceri_app_registry` and a
generated app secret via `php bin/set-app-secret.php neighborhoods` run on
the Ceri server. Until then, the Platform Host / member access-token system in
step 2 is the real, working auth path -- exactly how Weddings itself
operates today (its own Ceri integration is built but also not live yet;
see `Events/Weddings/server/CERI_INTEGRATION_AUDIT.md`).

Per your message about "Nosy Neighbors" going through Ceri's App Creator
eventually: once that exists, `POST /api/v1/neighborhoods` (step 2) is
almost certainly the exact endpoint an App Creator "start building" flow
would call server-to-server -- no redesign needed, just a Ceri-side caller
instead of a curl command.

## Frontend wiring (done)

`Maps/Neighborhoods/prototype/*.html`/`js/store.js` now call this API
directly via `js/api.js` (a small fetch wrapper resolving relative to
`prototype/js/`, so it works under any domain without hardcoding one) --
every page listed under "Pages" in `prototype/README.md` is live against
this backend, not `localStorage`. See that README's new "Live backend
(wired)" section for what changed on the auth/UX side (access codes instead
of click-through shortcuts, real file uploads instead of data URLs).

This conversion is statically verified only (no PHP/browser runtime here --
see "Locally verified vs. server-verified" above, which applies to this
pass too). The first live click-through on the real deployed site is the
actual test; see `prototype/README.md`'s "Live backend" section and this
file's steps 1–3 for what needs to be true on the server first (`config/
app.php` copied and its `basePath` confirmed, `ADMIN_SETUP_TOKEN` set) --
those are the two most likely sources of a first-run error, so check them
before assuming the code itself is wrong.
