A cozy side project — a shared digital pantry collection of wines, liquors, recipes, cheeses and restaurants. What started as a simple note-keeping idea turned into a full-stack platform with AI-powered features and live integrations.

We kept discovering wines and restaurants and forgetting them by the time we got home. Same with cheeses, recipes and places we loved. A notes app felt too unstructured and limited — we wanted something curated and shared.
A content-driven platform where we catalog everything we love about food and drink. Wines and liquors connect to recipes, restaurants get their menus pulled in automatically, and everything is queryable by taste profile, type, origin or cuisine. Backed by Sanity.io, with live data from Systembolaget and AI-extracted menu data for restaurants.
The GCP functions (written in Go) query Sweden's national alcohol retailer for live product data — pricing, taste profiles, grapes, packaging and more. Results are cached in memory for 1 hour.
Point your phone at a wine bottle and the app identifies it. A two-stage OpenAI pipeline first extracts the wine name from the label image (GPT-4o vision), then searches Systembolaget using the web-search model. Falls back to Google Vision API and Tesseract.js OCR for reliability.
We can add wines on the spot while still sitting at a restaurant. The bot runs on a separate server and calls the /addwine/ and /addimage/ Go Cloud Function endpoints — same API the web app uses. It also lets us drop photos straight into the site gallery, uploading through the same Go functions into Sanity. By the time we get home, the wine — or the photo — is already on our website.
Custom Sanity Studio plugin that adds an OpenAI-powered image generation field. When adding a recipe or restaurant, you can generate an illustrative image directly inside the CMS without leaving the editor.
Every page gets a unique social-share image, rendered server-side at request time with Satori (JSX → SVG) and @resvg/resvg-wasm. No native binaries involved, so it runs cleanly in the Alpine Docker image we ship to production.
Publishing a restaurant in Sanity triggers a lookup service that crawls the restaurant's own website and extracts its menu — dishes, categories, prices — along with signals like cuisine and atmosphere, writing the structured result straight back into the CMS. Editors can still override or clear it manually.
// User browses the frontend
React + React Router rendered server-side by Express
// Content comes from Sanity
Node server queries Sanity CMS via GROQ
// Live product data fetched from Systembolaget
Server calls Go Cloud Functions → queries Systembolaget
// Adding a wine from a restaurant via Discord
Discord slash command → Go Cloud Function → writes to Sanity
// Dropping a photo into the gallery via Discord
Upload file in discord server → Go Cloud Function → uploads image to Sanity gallery
// Publishing a restaurant in Sanity
Sanity Studio action → lookup service crawls restaurant's website → extracts menu → writes back to Sanity
// Publishing a wine or liquor in Sanity
Sanity Studio action → Go Cloud Function → queries Systembolaget → matches Systembolaget product → writes back to Sanity
// Sharing a page on social
Express renders JSX with Satori → SVG → PNG via @resvg/resvg-wasm, no native binaries
Frontend
Backend
CMS
AI
Integrations
Infrastructure
Writing the Cloud Functions in Go was a deliberate choice — fast cold starts, tiny binaries, and one shared API that the web app, Sanity Studio actions, and the Discord bot all call to access the same underlying functions.
Already knew Sanity going in. What I picked up here was custom publish actions, webhooks, and Go functions patching data back into Sanity — not just reading content out, but writing into it too.
Restaurant menus rarely come as clean data. The Go functions scrape a restaurant’s website and let OpenAI turn that mess into a structured menu — dishes, categories, prices, cuisine, atmosphere — written straight back into Sanity.
Cupboard is live and we actually use it.