← All projects

Gratitude

In progress

A gratitude journal you share as narrowly as you like, pinned to a map only you can see.

Location is owner-only by construction, not by remembering to hide it. A post's coordinates live in a separate table the feed never joins, behind a row-level policy with no "friends" branch at all, and get stripped from photos on upload — so a friend's view of a geotagged post doesn't carry the location as null, it carries it not at all. Even the map is self-hosted down to the last tile: one request to someone else's tile server, on a map centred on your own pins, would already have said "this person lives here."

Gratitude is a deliberate rewrite of an earlier, busier version, stripped back to one idea: write down something you’re grateful for, share it with close friends, all your friends, or nobody, and let it remember where you were. The streaks, badges, stories, partner spaces, reactions, and comments of the first version are gone. What’s left is a short post — up to 500 characters, one optional photo, one optional location — and a private map of your own.

The interesting part is what “only you can see the location” actually takes. Coordinates aren’t a column on the post; they live in a separate table the feed query never joins, so a read that doesn’t deliberately reach for them cannot return them — it fails safe instead of failing open. A row-level policy makes locations readable only by their author, with no “friends” branch to get subtly wrong. And a friend’s view of a geotagged post doesn’t carry the location as an empty value, which would still admit that a location exists — it carries nothing at all. The test that guards this checks the data a friend’s browser actually receives, not the page it renders: the question is whether the coordinates were ever sent, not whether they were shown.

Photos are the other place a location can leak, and the first version leaked it — it wrote the raw upload to disk and served it from a public URL, GPS tags and all, so a friend could download the file and read the coordinates straight out of the JPEG, past every database rule. Here every upload is re-encoded on the server, which drops the EXIF metadata, and the file is served through an endpoint that answers “may I see this photo” as the very same question as “may I see this post.” No public bucket, no signed link that works for whoever ends up holding it.

The map is self-hosted down to the tiles — a 17GB archive of the whole globe on its own disk — and that’s the point of the feature, not an optimisation. A private map opens centred on your own pins, so the first request to someone else’s tile server would amount to “this person lives here” before you’d touched anything; serving the tiles ourselves means nobody learns where you’re looking. Hosting 17GB doesn’t mean sending it: the format reads a handful of tiles per view over range requests, so a first load is about half a megabyte. Auth, the database, and photo storage do sit on Supabase — a trade made in the open — but the map is the one thing that never moved, because where you’re looking was never worth handing to anyone.

Built with

  • Next.js 16
  • TypeScript
  • Supabase (Postgres + RLS)
  • MapLibre + pmtiles
  • sharp