Skip to content

Repository files navigation

Disscount - Pronađi najbolje cijene u Hrvatskoj

Disscount

Find the cheapest groceries in Croatia. Disscount compares product prices across every major retail chain, shows real price history so you can tell whether a discount is genuine, and turns shopping lists into per-store basket totals.

Live Uptime License: BUSL 1.1 Next.js React Spring Boot PostgreSQL

Description

Since May 15th 2025, Croatian retail chains are legally required to publish their product prices publicly. Disscount turns that raw open data (via the Cijene API) into clear comparisons, real price history and better shopping decisions. It runs in any browser and installs as an offline-capable PWA, so your lists and recently viewed prices keep working even with no signal in the store.

Under the hood it is a full production stack: a Next.js frontend that also acts as the identity provider (better-auth), a Spring Boot API that owns user data, and a shared PostgreSQL database, self-hosted on a Hetzner VPS via Dokploy behind Cloudflare. The codebase is source-available under BUSL-1.1 and documented (see docs/), and contributions are welcome.

Features

Live:

  • Product search across 29 Croatian retail chains (with barcode scanning)
  • Price comparison per store and price history charts ("is the discount real?")
  • Smart shopping lists with per-store basket totals
  • Shared shopping lists via a private link, with view, shop or edit access and a revocable token
  • Product watchlist
  • Installable PWA that works offline (IndexedDB reads, background-sync writes)
  • Google + email/password auth with account linking

Coming soon (marked with an USKORO badge in the app):

  • Price-drop notifications
  • Digital loyalty cards
  • Store map with working hours
  • Spending analysis and market statistics

Tech stack

  • Frontend: Next.js, React, TypeScript, Tailwind CSS, shadcn-style UI on Radix + lucide-react, TanStack Query (+ Virtual), React Hook Form + Zod, Axios, Motion, Recharts, Sonner, cmdk, Vaul
  • Backend: Spring Boot on Java, PostgreSQL, JPA / Hibernate, Spring Security + OAuth2 resource server, Bean Validation, Lombok, springdoc-openapi (Swagger UI), Actuator
  • Auth: better-auth (in Next.js) issues ES256 JWTs, validated by Spring as an OAuth2 resource server via JWKS. Drizzle owns the auth tables
  • PWA & offline: Serwist service worker, IndexedDB-backed React Query cache (idb-keyval), offline reads and queued writes, in-browser barcode scanning
  • Email: Resend + React Email
  • Infra: Docker Compose, Dokploy on a Hetzner VPS, Traefik, Cloudflare, Sentry, UptimeRobot, Umami

Deeper references live in docs/: authentication, PWA & offline, mobile navigation, state persistence, the landing page, brand assets, and deployment.

Link

Deployed and available on: disscount.me

Live uptime and response times: status page

Visuals

Landing page (full-page screenshot):

Disscount - Landing page Disscount - Landing page on mobile

Features

Disscount - Product search across 29 chains Disscount - Product details with price history

Disscount - Product watchlist with price-drop targets Disscount - Shopping lists

Disscount - Shopping list items with quantities and per-item savings Disscount - Shopping list basket totals per store chain

Disscount - Digital loyalty cards Disscount - Preferences (nearby stores and locations)

On your phone

Disscount - Product search on mobile Disscount - Price history on mobile Disscount - Shopping list on mobile Disscount - Watchlist on mobile Disscount - Digital cards on mobile

Attribution

Created by: Jakov Jakovac

Big thanks to Cijene API for providing access to their API for data about products and store chains :)

Support

If Disscount saves you money, you can support its hosting and further development on Ko-fi. Disscount stays free whether or not you choose to contribute.

Podrži Disscount na Ko-fi

License BUSL 1.1

This work is licensed under the Business Source License 1.1 (BUSL-1.1).

License parameters used in this repository:

  • Licensor: Jakov Jakovac
  • Additional Use Grant: None
  • Change Date: Four (4) years from the date the Licensed Work is published
  • Change License: GPL-3.0-or-later

Under BUSL-1.1 terms, each version converts on the Change Date or on the fourth anniversary of first publicly available distribution of that version, whichever comes first.

Commercial use is restricted unless covered by an Additional Use Grant or separate commercial terms from the Licensor.

How to run

The quickest way to run the full stack (frontend + backend + PostgreSQL) locally is with Docker Compose:

cp example.env .env   # then fill in the values
docker compose up -d --build
# frontend: http://localhost:3000

To run the services manually instead, follow the steps below.

Prerequisites

  • Node.js 24 LTS+ and pnpm
  • Java 21 and Maven 3.9+
  • PostgreSQL 14+ (17 in production)
  • Docker + Docker Compose (only for the quick-run path above)

Startup flow

The backend and frontend share one PostgreSQL database: the backend owns the app tables (Hibernate ddl-auto), the frontend owns the better-auth tables (drizzle). Create the database, run the backend, then the frontend.

1. Database (PostgreSQL)

Create a database and user (example), and reuse these values in both env files below:

sudo -u postgres psql -c "CREATE USER disscount WITH PASSWORD 'secret';"
sudo -u postgres psql -c "CREATE DATABASE disscount OWNER disscount;"

2. Backend (Spring Boot)

cd backend
cp .env.example .env

Set in backend/.env:

  • SPRING_DATASOURCE_URL (e.g. jdbc:postgresql://localhost:5432/disscount)
  • SPRING_DATASOURCE_USERNAME and SPRING_DATASOURCE_PASSWORD
  • BETTER_AUTH_JWKS_URI (e.g. http://localhost:3000/api/auth/jwks)

Build and run with the local Spring profile (it supplies local better-auth defaults, the JWKS URL and issuer at http://localhost:3000, so the app starts without extra config):

mvn clean install
mvn spring-boot:run -Dspring-boot.run.profiles=local

The backend runs on port 8080. Swagger UI: http://localhost:8080/api-docs

3. Frontend (Next.js)

cd frontend
cp .env.local.example .env.local

Set in frontend/.env.local (see the file for the full list):

  • DATABASE_URL (same Postgres DB, e.g. postgresql://disscount:secret@localhost:5432/disscount)
  • BETTER_AUTH_SECRET (strong random string) and BETTER_AUTH_URL=http://localhost:3000
  • NEXT_PUBLIC_API_URL=http://localhost:8080 and NEXT_PUBLIC_APP_URL=http://localhost:3000
  • Google OAuth (NEXT_PUBLIC_GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET) and Facebook (FACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRET)
  • RESEND_API_KEY + EMAIL_FROM, and CIJENE_API_URL + CIJENE_API_TOKEN

Install dependencies, create the better-auth tables, then run:

pnpm install
pnpm drizzle-kit migrate   # creates the better-auth tables in the shared DB
pnpm dev

The frontend runs on http://localhost:3000

MCP servers (optional, for AI editors)

The repo ships pre-configured MCP servers so an AI editor picks them up on clone, one file per tool (same servers): .mcp.json (Claude Code), .cursor/mcp.json (Cursor), .vscode/mcp.json (VS Code).

Server Purpose
playwright Browser automation / E2E
context7 Up-to-date library docs
supabase Supabase project access
chrome-devtools Chrome DevTools debugging
sentry Error tracking (Disscount)

No secrets are committed: supabase and sentry authenticate via browser OAuth on first use, and context7 runs keyless (set CONTEXT7_API_KEY in your environment to raise rate limits). Never put keys in these files - use env vars or your editor's secret store.

playwright needs a browser at runtime: run npx playwright install chromium once per machine, or point it at an existing browser via PLAYWRIGHT_MCP_EXECUTABLE_PATH (e.g. /opt/helium/chrome). Both are machine-local, so keep the browser path out of the committed config.

Deployment

Disscount is self-hosted on a Hetzner VPS using Dokploy (Docker Compose), with Traefik for routing and automatic Let's Encrypt TLS, and Cloudflare in front for DNS, CDN, and proxying. Production deploys automatically from the main branch, and a staging environment deploys from the dev branch, on every push. Runtime errors go to Sentry, and UptimeRobot polls /health with a public status page.

See docs/DEPLOYMENT.md for the full infrastructure reference: architecture, environment variables, DNS and SSL, security, backups and restore, monitoring, and how to add more apps to the server.

How to contribute

Contributions are welcome, whether it's a bug report, feature idea, documentation improvement or code change. See the Contributing guide for how to report issues, set up your environment, and open a pull request.

Please also review our Code of Conduct. To report a security vulnerability, follow the Security Policy rather than opening a public issue.

Thank you for helping improve Disscount - every contribution helps!

About

Open platform for smart shopping and grocery price comparison for Croatia.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Sponsor this project

Used by

Contributors

Languages