Back to Discover

Cellarion

connector

jagduvi1

Public wine registry and guides: search wines, grapes, regions, appellations. No account.

View on GitHub
0 starsSynced Aug 2, 2026

Install to Claude Code

/plugin marketplace add jagduvi1/Cellarion

README

Cellarion

Cellarion is a hosted wine cellar app — a ready-to-use online service at cellarion.app. Create a free account and start tracking your bottles, organizing them into cellars and racks, searching a shared wine registry, getting drink-window recommendations, and chatting with an AI sommelier about your collection. No installation, no server, no setup — just sign up and go. Every feature is free, forever.

Just want to use Cellarion? Go to cellarion.app and sign up. You do not need to clone this repository, run Docker, or host anything yourself.

Cellarion is also open-source (AGPL-3.0), so if you'd prefer to run your own private instance, you can self-host it. The rest of this README covers self-hosting — see Quick Start. Most people should just use the hosted service at cellarion.app.

Hosted Service (recommended)

Cellarion is live and publicly available at:

👉 https://cellarion.app

This is the primary way to use Cellarion. Create an account and start using the full service today — every feature is free, forever. No credit card, no trial clock, no paywalled features, nothing to install or maintain.

Features

  • Bottle tracking — Log every bottle with vintage, producer, region, price, rating, and tasting notes
  • Cellar & rack management — Organize bottles across multiple cellars with interactive 8×4 rack grids
  • Drink-window alerts — Get notified when bottles are approaching peak, in window, or past it
  • Rich statistics — Charts, maps (world choropleth), breakdowns by country, grape, value, and drink status
  • Smart search — Meilisearch-powered fuzzy search with deduplication
  • AI cellar chat — Ask questions about your collection — food pairings, occasion picks, cellar checks (powered by Claude + Voyage embeddings + Qdrant)
  • Label scanning — Snap a photo of a wine label and let AI fill in the details (Anthropic Vision API)
  • Import & export — Bring collections from Vivino, CellarTracker, or generic CSV; export as JSON (or ZIP with images)
  • Cellar sharing — Invite others to browse or co-manage a cellar with role-based access
  • Dark mode — Full light/dark theme with system preference detection
  • Notifications — In-app notification bell for wine requests, image approvals, shared cellars, and more
  • Everything free — Every feature is free for everyone. Optional Supporter and Patron tiers let you chip in to fund development — they unlock nothing extra, just our thanks
  • Support system — In-app support tickets and wine quality reports
  • Internationalization — i18n support via react-i18next
  • Sommelier tools — Dedicated maturity phase and pricing interfaces for sommeliers
  • Super admin dashboard — Platform-wide analytics, service health, rate limits, and embedding management

Stack

  • MongoDB 7 — Database (Mongoose 8)
  • Express 4 — Backend API
  • React 19 — Frontend (React Router 6)
  • Node.js 24 (LTS) — Runtime
  • Meilisearch — Fuzzy search engine
  • Qdrant — Vector database for AI cellar chat
  • Voyage AI — Wine embedding generation
  • Anthropic Claude — Label scanning + AI chat
  • nginx — Serves the React SPA and proxies /api/ to the backend (internal)
  • Traefik — External reverse proxy (bring your own; not included in this Compose file)
  • Docker Compose — Containerization
  • rembg — Python/Flask background removal microservice

Self-Hosting (Quick Start)

This section is only for people who want to run their own private instance. If you just want to use Cellarion, head to cellarion.app instead — no setup required.

Prerequisites

  • Docker + Docker Compose

Run the app

The app is routed through Traefik, so create the external web Docker network before the first up (compose declares it external — the first command fails otherwise):

git clone https://github.com/jagduvi1/Cellarion.git
cd Cellarion
cp .env.example .env
# Edit .env and set JWT_SECRET and MEILI_MASTER_KEY to strong random strings
docker network create web        # once; skip if it already exists
docker-compose up --build
URLDescription
http://localhostFrontend (React SPA) — served via Traefik
http://localhost/api/healthBackend health check

Seed demo data

After the containers are running:

docker exec cellarion-backend node src/seed-demo.js

This creates:

AccountEmailPasswordRole
Adminadmin@cellarion.appAdmin1234!demoadmin
Demo useruser@cellarion.appUser1234!demouser

…plus 2 countries, 2 regions, 5 grape varieties, 2 wine definitions, and a demo cellar with sample bottles.

These are local development credentials. Change them before deploying anywhere public.

Stop

docker-compose down          # keep data
docker-compose down -v       # also remove all volumes (wipes database)

Architecture

Cellarion/
├── backend/
│   ├── src/
│   │   ├── config/
│   │   │   ├── aiConfig.js         # AI chat feature flags, model config, daily limits
│   │   │   ├── db.js               # MongoDB connection
│   │   │   ├── plans.js            # Supporter tier config (all features free)
│   │   │   └── upload.js           # Multer config
│   │   ├── middleware/
│   │   │   ├── auth.js             # JWT + role middleware (requireAuth, requireAdmin, requireSomm)
│   │   │   └── bottleAccess.js     # requireBottleAccess(minRole) factory
│   │   ├── models/                 # 22 Mongoose schemas
│   │   │   ├── User.js
│   │   │   ├── WineDefinition.js   # Shared wine registry (vintage-neutral)
│   │   │   ├── Bottle.js           # User-owned bottle records
│   │   │   ├── Cellar.js
│   │   │   ├── Rack.js             # 8×4 grid rack layout
│   │   │   ├── AuditLog.js
│   │   │   ├── BottleImage.js      # Bottle photo metadata
│   │   │   ├── WineVintageProfile.js
│   │   │   ├── WineVintagePrice.js
│   │   │   ├── WineRequest.js
│   │   │   ├── WineReport.js       # User-submitted wine quality reports
│   │   │   ├── WineEmbedding.js    # Vector embedding tracking for RAG
│   │   │   ├── Country.js
│   │   │   ├── Region.js
│   │   │   ├── Appellation.js      # Wine appellations (e.g. Barolo, Châteauneuf-du-Pape)
│   │   │   ├── Grape.js
│   │   │   ├── ChatUsage.js        # Daily AI chat usage per user
│   │   │   ├── Notification.js     # In-app notifications
│   │   │   ├── SupportTicket.js    # User support tickets
│   │   │   ├── SiteConfig.js       # Global admin settings
│   │   │   ├── ImportSession.js    # Persisted bottle import state
│   │   │   └── ExchangeRateSnapshot.js # Cached currency exchange rates
│   │   ├── routes/
│   │   │   ├── auth.js             # /api/auth/*
│   │   │   ├── cellars.js          # /api/cellars/*
│   │   │   ├── bottles.js          # /api/bottles/*
│   │   │   ├── wines.js            # /api/wines/*
│   │   │   ├── racks.js            # /api/racks/*
│   │   │   ├── wineRequests.js     # /api/wine-requests/*
│   │   │   ├── wineReports.js      # /api/wine-reports/*
│   │   │   ├── import.js           # /api/bottles/import/*
│   │   │   ├── chat.js             # /api/chat (AI cellar chat)
│   │   │   ├── stats.js            # /api/stats/overview
│   │   │   ├── notifications.js    # /api/notifications
│   │   │   ├── support.js          # /api/support
│   │   │   ├── settings.js         # /api/settings
│   │   │   ├── images.js           # /api/images/*
│   │   │   ├── users.js            # /api/users/*
│   │   │   ├── health.js           # /api/health
│   │   │   ├── superadmin.js       # /api/superadmin/* (super admin only)
│   │   │   ├── admin/              # /api/admin/* (admin role)
│   │   │   └── somm/               # /api/somm/* (sommelier features)
│   │   ├── services/
│   │   │   ├── aiChat.js           # RAG pipeline: embed → Qdrant → Claude response
│   │   │   ├── audit.js            # Audit logging
│   │   │   ├── embedding.js        # Voyage AI embedding generation
│   │   │   ├── findOrCreateWine.js # Intelligent wine lookup/creation
│   │   │   ├── imageProcessor.js   # Background removal via rembg
│   │   │   ├── labelScan.js        # Anthropic Vision API for label scanning
│   │   │   ├── notifications.js    # Notification creation for key events
│   │   │   ├── search.js           # Meilisearch integration
│   │   │   ├── statsService.js     # Stats computation
│   │   │   └── vectorStore.js      # Qdrant REST client for vector search
│   │   ├── utils/
│   │   │   ├── cellarAccess.js     # Ownership verification
│   │   │   ├── drinkWindow.js      # classifyDrinkWindow() shared helper
│   │   │   ├── normalize.js        # Wine name dedup & fuzzy matching
│   │   │   └── ratingUtils.js      # Rating scale conversion + resolveRating()
│   │   ├── data/                   # Taxonomy reference JSON files
│   │   └── seed-demo.js            # Demo data seeder
│   └── Dockerfile
├── frontend/
│   ├── src/
│   │   ├── api/                    # API client wrappers
│   │   │   ├── admin.js            # Admin endpoints (wine reports, rate limits, etc.)
│   │   │   ├── bottles.js          # getBottle, updateBottle, consumeBottle, import
│   │   │   ├── cellars.js          # getCellar, updateCellar, deleteCellar, …
│   │   │   ├── importSessions.js   # Import session management
│   │   │   ├── racks.js            # getRacks, deleteRack, updateSlot, clearSlot
│   │   │   ├── support.js          # Support tickets & wine reports
│   │   │   └── wines.js            # searchWines, getWine, scanLabel
│   │   ├── components/
│   │   │   ├── BottleCard.js       # Bottle row/card (list + grid view)
│   │   │   ├── CellarionLogo.js    # Brand SVG logo component
│   │   │   ├── Layout.js           # Persistent navbar + bottom nav + mobile menu
│   │   │   ├── Modal.js            # Shared modal overlay
│   │   │   ├── NotificationBell.js # Notification dropdown with unread badge
│   │   │   ├── ReportWineModal.js  # Wine quality report modal
│   │   │   ├── SupportModal.js     # Support ticket submission modal
│   │   │   ├── ProtectedRoute.js
│   │   │   └── ErrorBoundary.js
│   │   ├── contexts/
│   │   │   ├── AuthContext.js      # Global auth state
│   │   │   ├── ThemeContext.js     # Dark/light mode with system preference detection
│   │   │   └── NotificationContext.js # Notification polling & unread count
│   │   ├── pages/                  # App screens
│   │   │   ├── LandingPage.js      # Public landing page
│   │   │   ├── Login.js            # Auth (login/register)
│   │   │   ├── VerifyEmail.js      # Email verification
│   │   │   ├── ResetPassword.js    # Password reset flow
│   │   │   ├── CellarChat.js       # AI cellar chat interface
│   │   │   ├── Statistics.js       # Analytics dashboard with charts & world map
│   │   │   ├── DrinkAlerts.js      # Drink-window alerts by urgency
│   │   │   ├── Plans.js            # Supporter tiers (all features free)
│   │   │   ├── Settings.js         # User preferences (currency, language, rating scale)
│   │   │   ├── SupportPage.js      # Support tickets & wine reports
│   │   │   ├── SommMaturity.js     # Sommelier maturity phase management
│   │   │   ├── SommPrices.js       # Sommelier pricing data management
│   │   │   ├── SuperAdmin.js       # Platform-wide admin dashboard
│   │   │   ├── AdminSupportTickets.js
│   │   │   ├── AdminWineReports.js
│   │   │   └── …                   # Cellar, bottle, rack, wine pages
│   │   ├── config/
│   │   │   ├── currencies.js
│   │   │   └── plans.js
│   │   ├── utils/                  # Frontend helpers
│   │   └── styles/common.css
│   ├── nginx.conf                  # nginx config (SPA + /api/ proxy)
│   └── Dockerfile                  # Multi-stage: Node build → nginx-unprivileged
├── rembg/                          # Python background-removal service
└── docker-compose.yml

Services

All external traffic enters through Traefik (runs on the shared web Docker network, external to this Compose file). All services inside this Compose file are internal only.

ServiceHost portDescription
Traefik80External reverse proxy (external)
nginxinternalServes React SPA + proxies /api/
BackendinternalExpress REST API (port 5000)
MongoDBinternalDatabase (port 27017)
MeilisearchinternalFuzzy search engine (port 7700)
QdrantinternalVector database (port 6333)
rembginternalBackground removal (port 5000)

Running behind Traefik

Cellarion is designed to sit behind a Traefik reverse proxy on a shared Docker network called web. Traefik handles incoming HTTP on port 80 (SSL termination is handled upstream by Cloudflare or similar).

Requirements:

  • A running Traefik instance connected to an external Docker network named web
  • The web network must exist before starting Cellarion: docker network create web

The frontend service declares the following Traefik labels in docker-compose.yml:

traefik.enable: "true"
traefik.docker.network: "web"
traefik.http.routers.cellarion.rule: "Host(`cellarion.app`)"
traefik.http.routers.cellarion.entrypoints: "web"
traefik.http.services.cellarion.loadbalancer.server.port: "8080"

The upstream port is 8080 (not 80): the frontend image is built on nginxinc/nginx-unprivileged, whose non-root nginx cannot bind ports below 1024.

Update the Host(...) rule to match your own domain.


Core Concepts

EntityDescription
WineDefinitionVintage-neutral wine entry in the shared registry. Admins create and manage these.
BottleA user's bottle: references a WineDefinition and adds vintage, price, rating, notes, rack location.
CellarNamed container of Bottles, owned by a user. Can be shared with other users via role-based access.
Rack8×4 grid within a Cellar for physical bottle placement.
WineRequestUser-submitted wine suggestion. Admins review and fulfil by creating a WineDefinition.
TaxonomyAdmin-managed Countries, Regions, Appellations, and Grapes to prevent free-text proliferation.
NotificationIn-app notification for events like wine requests resolved, images approved, cellars shared.
SupportTicketUser support tickets with admin response tracking.
WineReportUser-submitted wine quality reports (wrong info, duplicates, inappropriate content).

User Roles

RoleDescription
userStandard user — manages own cellars, bottles, and requests
sommelierCan manage maturity profiles and pricing data for wines
adminFull access — wine library, taxonomy, user management, image review, audit log
super adminPlatform-level access — system monitor, service health, rate limits, embedding management

API Summary

Auth — /api/auth

MethodPathDescription
POST/registerCreate account (sends verification email if Mailgun is configured)
POST/loginLogin, returns JWT (blocked until email is verified when Mailgun is configured)
GET/verify-email?token=Verify email address, returns JWT on success
POST/resend-verificationResend verification email
POST/forgot-passwordRequest password reset email
POST/reset-passwordReset password with token

Cellars — /api/cellars (auth required)

MethodPathDescription
GET/List user's cellars
POST/Create cellar
GET/:idGet cellar + bottles
PUT/:idUpdate cellar
DELETE/:idDelete cellar
GET/:id/statisticsAggregated stats

Bottles — /api/bottles (auth required)

MethodPathDescription
POST/Add bottle to cellar
PUT/:idUpdate bottle
DELETE/:idRemove bottle
POST/import/validateValidate import data and match wines
POST/import/confirmCreate bottles from validated import

Wine Registry — /api/wines (auth required)

All wine registry endpoints require a valid JWT. Behaviour differs by role:

  • Regular userssearch param is mandatory; results capped at 10.
  • Admin / Sommelier — full browse without a search term; no result cap.
MethodPathDescription
GET/Search/filter wines. Params: search, type, country, region, grapes, sort, limit, offset
GET/:idGet a single wine definition by ID

Chat — /api/chat (auth required)

MethodPathDescription
POST/Send a question to the AI cellar chat (RAG pipeline)

Notifications — /api/notifications (auth required)

MethodPathDescription
GET/Get user's notifications
PUT/:id/readMark notification as read
PUT/read-allMark all notifications as read

Stats — /api/stats (auth required)

MethodPathDescription
GET/overviewCollection analytics (all cellars)

Support — /api/support (auth required)

MethodPathDescription
GET/ticketsGet user's support tickets
POST/ticketsSubmit a support ticket

Wine Reports — /api/wine-reports (auth required)

MethodPathDescription
GET/Get user's wine reports
POST/Report a wine issue (wrong info, duplicate, etc.)

Wine Requests — /api/wine-requests (auth required)

MethodPathDescription
GET/Get user's wine requests
POST/Submit a new wine request

Sommelier — /api/somm/* (somm or admin role)

MethodPathDescription
GET/POST/maturityManage maturity phases for wine vintages
GET/POST/pricesManage pricing data for wine vintages

Admin — /api/admin/* (admin role required)

MethodPathDescription
POST/PUT/DELETE/winesManage wine definitions
GET/PUT/wine-requestsReview user wine requests
CRUD/taxonomy/*Manage countries, regions, appellations, grapes
GET/DELETE/imagesManage bottle images
GET/auditView audit log
GET/usersManage users
GET/PUT/support-ticketsManage support tickets
GET/PUT/wine-reportsManage wine reports

Super Admin — /api/superadmin/* (super admin only)

MethodPathDescription
GET/dashboardPlatform analytics (user counts, supporter-tier distribution)
GET/PUT/settingsRate limits, contact email, AI config
POST/embeddingsManage embedding jobs

Environment Variables

Copy .env.example to .env in the project root and set the required values:

VariableRequiredDefaultDescription
JWT_SECRETYesLong random string for signing JWTs
MEILI_MASTER_KEYYesLong random string for Meilisearch auth
MONGO_URINomongodb://mongo:27017/winecellarMongoDB connection
ACCESS_TOKEN_EXPIRES_INNo15mAccess-token TTL (sessions refresh via a rotating 30-day cookie)
PORTNo5000Backend port
FRONTEND_URLNohttp://localhostCORS origin — set to your domain in production
MEILI_URLNohttp://meilisearch:7700Meilisearch URL
REMBG_URLNohttp://rembg:5000Background removal service
ANTHROPIC_API_KEYNoEnables label scanning and AI cellar chat (get a key)
VOYAGE_API_KEYNoRequired for AI cellar chat embeddings (get a key)
AI_PROVIDERNoanthropicSet to openai to serve all LLM features from an OpenAI-compatible endpoint (see Self-hosted AI)
OPENAI_BASE_URLNoOpenAI-compatible /v1 root, e.g. http://host.docker.internal:11434/v1 (required when AI_PROVIDER=openai)
OPENAI_API_KEYNoBearer token for the endpoint (Ollama/LM Studio ignore it; vLLM may require one)
AI_MODELNoModel name to use, e.g. llama3.1:70b (required when AI_PROVIDER=openai)
AI_VISION_MODELNoAI_MODELVision-capable model for label scanning, e.g. qwen2.5-vl:32b
OPENAI_TIMEOUT_MSNo120000Per-request timeout for the OpenAI-compatible endpoint
EMBEDDING_PROVIDERNovoyageSet to openai to serve wine embeddings from an OpenAI-compatible endpoint instead of Voyage AI
EMBEDDING_BASE_URLNoOPENAI_BASE_URL/v1 root of the embedding endpoint
EMBEDDING_API_KEYNoBearer token for the embedding endpoint (falls back to OPENAI_API_KEY only when EMBEDDING_BASE_URL is also unset — a dedicated embedding host is never sent the chat key)
EMBEDDING_MODELNoEmbedding model name, e.g. nomic-embed-text (required when EMBEDDING_PROVIDER=openai)
EMBEDDING_DIMENSIONNoThe model's vector size, e.g. 768 (required when EMBEDDING_PROVIDER=openai)
EMBEDDING_TIMEOUT_MSNo30000Per-request timeout for the embedding endpoint
QDRANT_URLNohttp://qdrant:6333Vector database URL (auto-set in Docker Compose)
SUPER_ADMIN_EMAILNoEmail of the super admin account
SUPER_ADMIN_IPSNoComma-separated IP allowlist for super admin access
MAILGUN_API_KEYNoMailgun API key — enables email verification when set
MAILGUN_DOMAINNoMailgun sending domain (e.g. mg.yourdomain.com)
MAILGUN_FROMNoCellarion <no-reply@{DOMAIN}>Sender address shown in verification emails
MAILGUN_API_URLNohttps://api.mailgun.netUse https://api.eu.mailgun.net for EU region

AI Cellar Chat

The AI chat feature requires three services working together:

  1. Anthropic Claude (ANTHROPIC_API_KEY) — generates conversational responses grounded in your cellar
  2. Voyage AI (VOYAGE_API_KEY) — creates wine embeddings for semantic search
  3. Qdrant (QDRANT_URL) — vector database for fast similarity search

When all three are configured, users can ask natural-language questions about their collection (food pairings, occasion picks, cellar insights). The system only surfaces wines the user actually owns — no hallucinated recommendations.

A single daily usage quota — the same for every user, regardless of supporter tier — is configurable by SuperAdmins (default 50 questions/day).

Self-hosted AI (OpenAI-compatible endpoints)

Self-hosters who prefer not to use an Anthropic API key can point every LLM feature (cellar chat, label scan, import lookup, drink-window / price / profile suggestions) at any endpoint that speaks the OpenAI chat-completions API — Ollama, LM Studio, vLLM, LiteLLM, or OpenAI itself:

AI_PROVIDER=openai
OPENAI_BASE_URL=http://host.docker.internal:11434/v1   # your endpoint's /v1 root
AI_MODEL=llama3.1:70b                                   # any model your server hosts
AI_VISION_MODEL=qwen2.5-vl:32b                          # optional — used for label scanning

Wine embeddings (the semantic-search half of cellar chat) can independently be moved off Voyage AI the same way:

EMBEDDING_PROVIDER=openai
EMBEDDING_MODEL=nomic-embed-text   # any embedding model your server hosts
EMBEDDING_DIMENSION=768            # MUST be that model's real vector size
# EMBEDDING_BASE_URL / EMBEDDING_API_KEY default to OPENAI_BASE_URL / OPENAI_API_KEY

Notes:

  • Both switches are opt-in: without them, nothing changes — Anthropic + Voyage remain the defaults, and they can be switched independently (e.g. local LLM + Voyage embeddings).
  • host.docker.internal resolves out of the box only on Docker Desktop (Windows/macOS). On a Linux server, either add extra_hosts: ["host.docker.internal:host-gateway"] to the backend service in your compose file, or point OPENAI_BASE_URL at the host's LAN IP or a service on the compose network.
  • The AI usage budgets (per-user/global daily caps, import per-request cap, chat daily limit) were tuned to bound paid Anthropic spend. Against your own hardware they still apply — raise or disable them in SuperAdmin → Rate limits (0/-1 = unlimited) if you don't want your local endpoint metered.
  • The admin panel's Claude model settings are ignored in openai mode (the model comes from AI_MODEL); the configurable AI prompts still apply.
  • Label scanning needs a vision-capable model. Extraction quality depends heavily on the model you host — smaller local models will misread more labels than Claude does.
  • The Qdrant collection is sized to the embedding dimension. After changing EMBEDDING_PROVIDER, EMBEDDING_MODEL, or EMBEDDING_DIMENSION, run a full embedding job (SuperAdmin → AI) — it drops and rebuilds the collection at the new size. Every returned vector is validated against EMBEDDING_DIMENSION, so a wrong value fails loudly instead of corrupting search.
  • Qdrant itself is always required for cellar chat (it ships in docker-compose).
  • Privacy note for multi-user instances: the bundled Privacy Policy names Anthropic and Voyage AI as the AI sub-processors. Pointing these vars at your own local endpoint (Ollama/vLLM on your hardware) removes third-party AI processing entirely — but if you point them at a remote third-party service (e.g. OpenAI or a hosted proxy), you are responsible for updating your instance's privacy policy and user consent accordingly.

Email Verification

When both MAILGUN_API_KEY and MAILGUN_DOMAIN are set, email verification is enabled:

  • New users receive a verification link after registering and cannot log in until they click it.
  • The link expires after 24 hours. A resend option is available on the login page and the /verify-email page.
  • If Mailgun is not configured, registration issues a token immediately — the same behaviour as before.

Existing users: After enabling verification on a running instance, existing accounts will have emailVerified: false and will be locked out. Run this once in the MongoDB shell to restore access:

db.users.updateMany({ emailVerified: { $exists: false } }, { $set: { emailVerified: true } })

Bottle Import

Users can import bottles from other wine cellar apps (Vivino, CellarTracker, or any generic CSV). The import flow:

  1. Upload — Drop a CSV file; the system auto-detects the source format and maps it to a standard schema
  2. Validate — Each item is matched against the wine library using fuzzy search (Meilisearch + MongoDB text search + normalized key lookup) and scored with combined similarity
  3. Review — Users see match results: exact matches (auto-selected), fuzzy matches (pick from candidates), and unmatched items (search manually or skip)
  4. Import — Confirmed items are created as bottles in the target cellar

Import sessions are persisted so users can resume later if interrupted. Access the import from any cellar's overflow menu (⋯ → Import Bottles). Requires editor or owner access.

Master Import JSON Format

Bottles can also be imported as JSON. Each item supports:

{
  "wineName": "Albe",
  "producer": "G.D. Vajra",
  "vintage": "2019",
  "country": "Italy",
  "region": "Piedmont",
  "appellation": "Barolo",
  "type": "red",
  "price": 299,
  "currency": "SEK",
  "bottleSize": "750ml",
  "quantity": 2,
  "purchaseDate": "2024-03-15",
  "purchaseLocation": "Systembolaget",
  "notes": "Beautiful nebbiolo",
  "rating": 4.2,
  "ratingScale": "5",
  "rackName": "Rack A",
  "rackPosition": 5,
  "addToHistory": false
}

To import directly into history (already consumed bottles), add:

{
  "addToHistory": true,
  "consumedReason": "drank",
  "consumedAt": "2025-12-24",
  "consumedNote": "Opened for Christmas",
  "consumedRating": 4.5,
  "consumedRatingScale": "5",
  "dateAdded": "2024-06-01"
}

Cellar Export

Cellar owners can export their data via a cellar's overflow menu (⋯ → Export) or Settings. Available as JSON, or as a ZIP that also includes your uploaded bottle images. The export covers bottles with rack placement (rackName, rackPosition), rack geometry, the 3D room layout, and your own reviews. The JSON format is directly re-importable.


Wine Deduplication

When an admin creates a wine, the system checks for near-duplicates using:

  1. Levenshtein distance (40%) — character-level similarity
  2. Trigram Jaccard (30%) — overlapping 3-char sequences
  3. Token Jaccard (30%) — word-level similarity after removing wine domain stop words

Score: name × 0.45 + producer × 0.45 + appellation × 0.10

Candidates above the threshold (default 0.75) appear as warnings with a "Use This" option.


Testing

Frontend

cd frontend && npm test -- --watchAll=false

Uses Jest + React Testing Library (bundled with Create React App). Covers drink-window logic, currency conversion, and the shared Modal component.

Backend

cd backend && npm test

Uses Jest. Covers auth middleware, cellar access control, wine normalisation/similarity, rating scale conversion, and drink-window classification.

Run both test suites before opening a pull request. PRs with failing tests will not be merged.


Contributing

  1. Fork the repo and create a feature branch off main
  2. Make your changes
  3. Run the tests (cd frontend && npm test and cd backend && npm test)
  4. Smoke-test in Docker: docker-compose up --build
  5. Submit a pull request with a clear description of your changes

Translations

Want Cellarion in your language? Translations are contributed through Weblate — no coding required. See TRANSLATING.md to get started. (Please don't PR edits to translation.json files directly; they conflict with Weblate's sync.)


Reporting a Vulnerability

Please report security issues privately to: github@cellarion.app


License

GNU Affero General Public License v3.0 (AGPL-3.0)

You are free to use, modify, and self-host this software. If you run a modified version as a network service, you must make your source code available to users of that service. Commercial hosting of this software as a managed service requires a separate agreement.


Acknowledgements

This codebase was developed together with Claude Code by Anthropic.

Rendered live from jagduvi1/Cellarion's GitHub README — not stored, always reflects the source repo.

1 Install Method

NameDescriptionCategorySource
streamable-http remoteHosted streamable-http endpointmcp-serverhttps://cellarion.app/api/mcp/public

0 Comments

Login required
Log in to post a comment or update on this repo.

No comments yet — be the first to share an update.