The standalone service in server/ was never deployed anywhere, and keeping it alongside a Vercel app would mean maintaining two implementations of the same validation rules. It is deleted; web/ is the only ingest path now. Its routes live at /v1/... rather than under /api, so the Android client's URL building is unchanged and CONSUMPTION_API_URL stays a bare origin. Credentials are split in two. INGEST_TOKEN is baked into the published APK and must be assumed extractable — anyone can unzip an APK and read its strings — so it opens the PUT endpoints and nothing else. Reading the history needs the dashboard password, which is never compiled into anything and reaches the browser as an HMAC-signed httpOnly cookie. The MongoDB URI stays a server-side environment variable; the car has never held one and still does not. The dashboard is a phone-shaped reader for the same data: totals for energy taken and paid, a daily energy chart, an efficiency trend, and every DC session with its power/SOC curve. It reuses the head unit's OLED palette so the two read as one app. Every page is a server component and the charts are hand-rolled SVG, so the whole thing renders without shipping client JavaScript. MongoClient is cached on globalThis: a fresh client per request means a fresh pool per request, and Atlas starts refusing connections long before the traffic would justify it. Index creation moved to the first write of each warm lambda, since a serverless deployment has no boot step to hang it on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.5 KiB
Markdown
69 lines
2.5 KiB
Markdown
# FatihsMG4 web
|
|
|
|
Next.js app on Vercel: the ingest API the car writes to, and a phone-shaped dashboard for
|
|
reading the history back. Replaces the standalone Express service that used to live in
|
|
`server/`.
|
|
|
|
**The car never connects to MongoDB and never holds a MongoDB URI.** The URI is a Vercel
|
|
environment variable and is only ever read by a server-side route.
|
|
|
|
## Two credentials, on purpose
|
|
|
|
| | Who holds it | What it opens |
|
|
|---|---|---|
|
|
| `INGEST_TOKEN` | the car, in the APK | the `PUT` endpoints — write only |
|
|
| `DASHBOARD_PASSWORD` | you, in a browser | the dashboard and the `GET` history |
|
|
|
|
The APK is published, so its token must be assumed extractable: anyone can `unzip` an APK
|
|
and read its strings. That is survivable here because the write token cannot read anything
|
|
back. The password is never compiled into anything.
|
|
|
|
## Environment variables
|
|
|
|
Set all five in Vercel → Settings → Environment Variables, for Production and Preview.
|
|
|
|
| Name | Notes |
|
|
|---|---|
|
|
| `MONGODB_URI` | Atlas connection string |
|
|
| `MONGODB_DATABASE` | defaults to `fatihsmg4` |
|
|
| `INGEST_TOKEN` | `openssl rand -hex 32` |
|
|
| `DASHBOARD_PASSWORD` | at least 8 characters, or login is refused outright |
|
|
| `SESSION_SECRET` | `openssl rand -hex 32`; rotating it signs every browser out |
|
|
|
|
Atlas network access must allow Vercel's egress, which is not a fixed address on the Hobby
|
|
plan — either allow `0.0.0.0/0` and rely on the database user's credentials, or use Atlas
|
|
private networking on a paid plan.
|
|
|
|
## Endpoints
|
|
|
|
Write, `Authorization: Bearer <INGEST_TOKEN>`:
|
|
|
|
- `PUT /v1/consumption/daily`
|
|
- `PUT /v1/charging-sessions/:sessionId`
|
|
|
|
Read, session cookie or `Authorization: Bearer <DASHBOARD_PASSWORD>`:
|
|
|
|
- `GET /v1/history/:installationId/consumption`
|
|
- `GET /v1/history/:installationId/charging`
|
|
|
|
`GET /v1/health` is unauthenticated and returns `{"status":"ok"}`.
|
|
|
|
The car builds its URLs as `CONSUMPTION_API_URL + /v1/...`, so the Android build variable
|
|
is the bare origin — `https://your-app.vercel.app`, no trailing path.
|
|
|
|
## Indexes
|
|
|
|
There is no boot step on Vercel, so `ensureIndexes()` runs on the first write of each warm
|
|
lambda and is skipped afterwards. The unique indexes on `(installationId, date)` and
|
|
`(installationId, sessionId)` are what make the upserts idempotent when the car retries a
|
|
record it could not confirm.
|
|
|
|
## Local development
|
|
|
|
```sh
|
|
cp .env.example .env.local # Next loads .env.local automatically
|
|
npm install
|
|
npm run dev
|
|
npm test # pure validation and session-signing tests, no database
|
|
```
|