lifetime is now optional in DailyConsumption — guard all .lifetime
accesses. The Ömür boyu card uses the derived total (sum of days).
Co-Authored-By: Claude <noreply@anthropic.com>
- backend daily PUT: no longer $set lifetime (app's local counter is unreliable after APK wipe)
- backend history GET: derives lifetime as sum of all days' totals, returns it at response level
- app: stops sending lifetime in chunks; reads it from history response
- app: refresh button on consumption pane + 'Şimdi yükle' on log pane (manual cache flush)
Co-Authored-By: Claude <noreply@anthropic.com>
MongoDB error: 'Updating the path day would create a conflict at day'.
On insert, $inc treats a missing field as 0, so no seed needed.
Co-Authored-By: Claude <noreply@anthropic.com>
and touching the same field (day.km) caused MongoDB
to throw a 500, holding all cached chunks back. Seed the whole day
subdocument instead.
Co-Authored-By: Claude <noreply@anthropic.com>
- HTML <input type=date> in a GET form, zero client JS (same server-component model)
- selected day detail card with tiles + rows above the period summary
- day-by-day list entries link to /consumption?date=YYYY-MM-DD and open+highlight the selected one
- 'Temizle' clears the filter
Co-Authored-By: Claude <noreply@anthropic.com>
- daily PUT: → for incremental 30-min delta chunks (day totals accumulate)
- new GET /v1/consumption/history/[installationId]: ingest-token-authenticated read-back
so the head-unit app can display cloud history without the dashboard password
Co-Authored-By: Claude <noreply@anthropic.com>
A lone password field is usually ignored: managers key a saved entry on
(origin, username), and with no username there is nothing to key on. There is
only ever one account here, so the hidden field carries a fixed value and the
server never reads it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign-out answered GET behind a <Link>, and Next prefetches links that are in the
viewport. The button sits in every page header, so the browser called it as soon
as a page rendered and cleared the session cookie before the reader had clicked
anything. The first page loaded; the next navigation landed back on the password
screen, which read as the session not persisting.
It is a POST form now, and the route answers POST only — a prefetch of it gets
405 and changes nothing. The rule this broke is the general one: an endpoint
with a side effect must be unreachable by a prefetch, a crawler, or a browser
preloading what it thinks you will click.
Both session cookies are now set on the returned response rather than through
the cookies() store, which a freshly constructed redirect does not reliably
carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>