Monorepo: backend (Supabase Edge Functions) and one or more frontends. The backend is frontend-agnostic; the current app can be unplugged and a new one can be plugged in.
Wines that have been tasted are tracked: a photo of a bottle or label is captured, recognition results are returned (region, producer, type, vintage, etc.), and the result can optionally be saved as a wine log with geometry. Logs are stored in Supabase (Postgres/PostGIS) and can be published via GeoServer for the map. The map shows consumption (where wine was drunk) and production (where wines come from, with counts and top wine types per region).
-
Capture & recognize
A photo is taken or uploaded in the app. The recognize-wine Edge Function (Supabase) is called by the frontend; an external wine API (e.g. RapidAPI) is used to return wine metadata. -
Save log
When save is triggered, the payload (metadata + optional image) is sent by the frontend to the backend log server (Node, port 3001). The record is enriched by the log server with region geometry (e.g. from a WFS wine-regions service), then one row per log is persisted into the Supabasewine_logstable (PostGIS). Optionally a local GeoJSON copy is written underbackend/post-requests/for debugging. Label images can be uploaded to Supabase Storage and linked fromwine_logs. -
Map data
The Production tab of the map does not readwine_logsdirectly from Supabase. GeoJSON is requested from GeoServer WFS (layerwine_logs), which is backed by the same PostGIS data. The WFS response is used by the frontend to draw region polygons, aggregate frequency and top wine types per region, and show proportional symbols and outlines. If GeoServer is unavailable, a fallback to a local/regions GeoJSON source is used. Consumption tab data is derived from stored logs (e.g. via the log server or local sync). -
Optional GeoServer
For the Production map to show data from GeoServer, GeoServer is run (e.g. on port 8080), a layer from thewine_logstable is published, and either the Vite proxy (/geoserver→ GeoServer) is used in dev orVITE_GEOSERVER_BASE_URLis set for a remote GeoServer.
End-to-end: photo → recognize-wine (Supabase) → log server → PostGIS + Storage → (optional) GeoServer WFS → map.
backend/– Supabase config and Edge Functions (wine analysis, explorer, recognize). Same for all frontends.frontend/– Default web app (Vite, TypeScript, React, shadcn-ui, Tailwind). Can be replaced.
→ Unplugging or adding a frontend: see FRONTEND.md for step-by-step instructions and the backend API contract.
Node.js and npm are required. From repo root:
npm install
npm run devnpm run dev– Both the frontend (port 8000) and the backend log server (port 3001) are started. Every time recognize-wine is triggered in the app, the response is automatically sent to the log server and persisted to Supabase (and optionally a local GeoJSON copy is written inbackend/post-requests/ifLOG_POST_DEBUG_EXPORT_GEOJSONis set). If the log server is down, a console warning is shown and persistence may not occur.npm run dev:frontend– Only the frontend is run (no log server; no GeoJSON files).npm run build– The default frontend is built.npm run serve– Supabase functions are served locally (requires Supabase CLI).npm run log– Only the backend log server (port 3001) is run. Used when the frontend is run separately and persistence and optional local GeoJSON are desired.
Environment variables are read by the app from a .env file at the repo root. The file is not committed (it’s in .gitignore). It should be set up before the app is run.
A file named .env is created in the repo root (same folder as package.json).
Copy the contents of .env.example and replace the placeholders with your project’s values:
VITE_SUPABASE_PROJECT_ID="your-project-ref"
VITE_SUPABASE_URL="https://your-project-ref.supabase.co"
VITE_SUPABASE_PUBLISHABLE_KEY="your-anon-public-key"- The project is opened in the Supabase Dashboard.
- Project Settings (gear icon) → API is opened.
- The following are copied:
- Project URL → used as
VITE_SUPABASE_URL - anon public key → used as
VITE_SUPABASE_PUBLISHABLE_KEY - Project ref (in the dashboard URL or under “Reference ID”) → used as
VITE_SUPABASE_PROJECT_ID
- Project URL → used as
Example (replace with your real values):
VITE_SUPABASE_PROJECT_ID="abcdefghijklmnop"
VITE_SUPABASE_URL="https://abcdefghijklmnop.supabase.co"
VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6..."- Option A: The three values are sent securely by someone with access (e.g. password manager or secure channel).
.envis created at the repo root by the collaborator and the values are pasted in. - Option B: A separate Supabase project is created by the collaborator; the URL, anon key, and project ref are obtained from Dashboard → Settings → API, and are put in
.env. (Wine analysis will work only afterRAPIDAPI_KEYis set in that project’s Edge Function secrets.)
.env must not be committed or shared in chat or email.
A workflow in .github/workflows/build.yml runs on every push to main: dependencies are installed and npm run build (frontend) is run. To use it, these repository secrets are added in GitHub (Settings → Secrets and variables → Actions):
VITE_SUPABASE_URLVITE_SUPABASE_PUBLISHABLE_KEYVITE_SUPABASE_PROJECT_ID
They are injected as env vars during the build so import.meta.env.VITE_* can be read by Vite.
Backend commands use backend/supabase/; Supabase CLI is run from backend/:
cd backend
supabase functions serve
# or: supabase functions deploy- Frontend:
.envat the repo root is read by the app (see How to set up.env). TheVITE_prefix is used for variables read by the Vite app (e.g.VITE_SUPABASE_URL,VITE_SUPABASE_PUBLISHABLE_KEY,VITE_SUPABASE_PROJECT_ID). OnlyVITE_vars are exposed to the client; this prefix must not be used for backend-only secrets. - Backend / Node: No prefix is used (e.g.
LOG_POST_PORT).process.env.*is read by the log server and other Node scripts;VITE_is not used. - Supabase Edge Functions (recognize-wine, etc.): They run in Supabase’s cloud and read Supabase secrets, not the repo’s
.env. Secrets are set in the Supabase Dashboard under Project → Edge Functions → Secrets, or via CLI:The recognize-wine function requirescd backend supabase secrets set RAPIDAPI_KEY=your_key_here
RAPIDAPI_KEYfor the wine recognition API.
Summary: Frontend config is put in .env with VITE_; Edge Function secrets are put in Supabase (Dashboard or supabase secrets set). .env and real secrets must not be committed to the repo.
Wine analysis logs are now persisted to Supabase Postgres/PostGIS via UPSERT instead of writing .geojson files locally.
These are set for the backend log server process:
SUPABASE_URL="https://<project-ref>.supabase.co"
SUPABASE_SERVICE_ROLE_KEY="<service-role-key>"Optional debug export (OFF by default):
LOG_POST_DEBUG_EXPORT_GEOJSON="1"When enabled, a local debug GeoJSON copy is also written by the backend to backend/post-requests/.
Supabase migration is run with:
cd backend
supabase db pushMigration file:
backend/supabase/migrations/20260228160000_create_wine_logs.sql
After a scan is run, inserts are verified in SQL:
select id, user_id, observed_at, source_file_name
from public.wine_logs
order by created_at desc
limit 20;Map-ready flattened view:
select id, region, wine_type, year, score
from public.wine_logs_map
order by observed_at desc nulls last
limit 20;Wine log geometries and attributes are loaded by the Production tab of the map from GeoServer WFS (not directly from the Supabase table). An interoperable OGC workflow and optional bbox-based loading are allowed.
In root .env (or in CI):
VITE_GEOSERVER_BASE_URL="https://your-geoserver-host/geoserver"
VITE_GEOSERVER_WORKSPACE="wine0clock"- Omitted: In development the same origin path
/geoserveris used by the app, and the Vite dev server proxies it tohttp://localhost:8080/geoserver(seefrontend/vite.config.ts). GeoServer can be run on port 8080 and the frontend on port 8000 without CORS issues. - Set: A full URL is used when GeoServer is on another host or in production (e.g.
https://geo.example.com/geoserver).
- Workspace:
wine0clock(or the value ofVITE_GEOSERVER_WORKSPACE). - Layer: A WFS-capable layer named
wine_logs(e.g. type namewine0clock:wine_logs) whose data source is the PostGISwine_logstable (or a view). GeoJSON is requested by the frontend via WFS 2.0 GetFeature withoutputFormat=application/json.
- DevTools → Network is opened and filtered by XHR/fetch.
- The map is loaded and the Production tab is switched to (or the map is panned/zoomed to trigger a refetch).
- A request to
.../geoserver/.../wfs?service=WFS&request=GetFeature&typeNames=wine0clock:wine_logs...is confirmed to return 200 with a GeoJSON FeatureCollection. - If that request is missing or fails, a fallback to
storage.fetchRegionsGeoJSON()is used by the app and the message may be seen in the console. “Failed to fetch logs from GeoServer WFS” in the console.
-
Add the function folder and handler
backend/supabase/functions/<name>/index.tsis created andDeno.serve(async (req) => { ... })is used as the entry point. The file is started with:import "jsr:@supabase/functions-js/edge-runtime.d.ts";
Edge Functions run on Deno (
jsr:ornpm:imports are used). -
Configure in
config.toml
Inbackend/supabase/config.toml, the following is added:[functions.<name>] verify_jwt = false
verify_jwt = falseis used for public/anonymous access (e.g. from the frontend with anon key). -
Link the project (if needed)
Frombackend/:supabase link --project-ref <your-project-ref>
-
Run locally
cd backend supabase functions serve # or: supabase functions serve <name>
-
Deploy
cd backend supabase functions deploy <name> # or: supabase functions deploy
-
Secrets
If the function needs API keys, they are set in Supabase (Dashboard orsupabase secrets set KEY=value), then read in the function withDeno.env.get("KEY"). -
Call from the frontend
The Supabase client is used:supabase.functions.invoke("<name>", { body: ... }). The URL is derived fromVITE_SUPABASE_URLin.env.
To point the app and CLI at a different Supabase project:
- The new project is created or opened in the Supabase Dashboard.
- In the new project: Project Settings → API — Project URL, anon public key, and Project ref (from the dashboard URL) are copied.
- Root
.env: setVITE_SUPABASE_PROJECT_ID,VITE_SUPABASE_URL, andVITE_SUPABASE_PUBLISHABLE_KEYto the new project’s values. backend/supabase/config.toml:project_id = "<new-project-ref>"is set.- Re-link and redeploy: from
backend/runsupabase link --project-ref <new-project-ref>, then set any secrets (e.g.supabase secrets set RAPIDAPI_KEY=...) and runsupabase functions deploy.
Data (database, storage, auth) is per project; the new project starts empty unless migration is performed. Secrets must be set again for the new project.
- 403 when listing or deploying functions: Access to that project may not be granted to the Supabase account (e.g. it was created by another account or org). The account that owns the project is used, the right org role is obtained, or a project that is owned is used and the existing function code is deployed there.
- Sharing
.env:.envfiles must not be shared via chat or email. Instructions are preferred (e.g. “create a project and copy URL and anon key from Dashboard”) or a secure channel is used..envis treated as private so a file that later contains real secrets is never accidentally shared.
- Vite, TypeScript, React, shadcn-ui, Tailwind CSS
- Leaflet / React-Leaflet for the map; Production tab data from GeoServer WFS (with fallback to local/regions GeoJSON)