Skip to content

About

Side project for practicing system design

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

CatQR Studio

CatQR Studio is a production-minded dynamic QR code generator and URL shortener. A generated QR code stores a stable short URL such as /r/{token}. The server owns the mutable destination, redirect rules, lifecycle state, and analytics.

Previews

Workspace:

CatQR Studio workspace

Admin dashboard:

CatQR Studio admin dashboard

What It Does

  • Creates QR codes that encode short URLs like /r/{token}.
  • Redirects active links with 302.
  • Updates destinations after QR creation without regenerating the QR image.
  • Soft deletes QR links.
  • Returns 410 for deleted or expired links.
  • Returns 404 for non-existent tokens.
  • Generates PNG QR images with a cat-themed frame.
  • Records scan analytics.
  • Provides a protected admin analytics dashboard at /admin.

System Design Goals

  • Dynamic QR behavior: the QR image remains stable while the destination changes in the database.
  • Correct redirect semantics: use 302, not 301, so browsers and intermediaries do not permanently cache an old target.
  • Operational safety: deleted and expired links stop redirecting immediately.
  • Update authorization: public QR tokens are not credentials; owner mutations require a private management_key in production.
  • Observability: redirect traffic is recorded as scan events for analytics.
  • Deployability: frontend, backend, database, cache, and reverse proxy can run through Docker Compose.
  • Local ergonomics: frontend and backend live in one repo and can also run directly for fast development.

Architecture

flowchart LR
  User["User Browser"] --> Web["React / Vite Web App"]
  Web --> API["FastAPI API"]
  Scanner["Scanner / Browser"] --> Redirect["GET /r/{token}"]
  Redirect --> API
  API --> DB[("Postgres or SQLite")]
  API --> Redis[("Redis")]
  API --> QR["QR PNG Generator"]
  Admin["Admin Dashboard"] --> API
  API --> Target["302 Location: destination URL"]
Loading

The web app is only a management surface. Scanning a QR code goes through the backend redirect endpoint so the server can check status, expiration, URL policy, and analytics before issuing a redirect.

Core Flows

Create

  1. The user submits a destination URL and style options.
  2. The API validates and normalizes the URL.
  3. The API creates a random token and a private management key.
  4. The database stores token -> normalized_url plus lifecycle metadata.
  5. The response includes short_url, qr_code_url, and the one-time management key.

Redirect

  1. A scanner opens /r/{token}.
  2. The API validates the token format.
  3. The API loads the QR link from the database.
  4. Missing links return 404.
  5. Deleted or expired links return 410.
  6. Blocked links return 403.
  7. Active links record a scan event and return 302 Location: normalized_url.

Redirect responses use:

Cache-Control: no-store, max-age=0
Pragma: no-cache

This keeps destination updates effective after a QR has already been scanned.

Update

  1. The owner submits a new destination.
  2. In production mode, the request must include X-QR-Management-Key.
  3. The API validates the key, then validates and normalizes the new URL.
  4. The existing token and QR image URL stay the same.
  5. Future scans redirect to the updated destination.

For local development, ALLOW_INSECURE_TOKEN_MUTATIONS=true allows update/delete without a key. Production compose sets it to false.

Data Model

Main tables:

  • qr_links: token, original URL, normalized URL, status, style, expiration, delete timestamp, management key hash.
  • qr_scan_events: QR link reference, scan timestamp, hashed IP, user agent, referer.
  • admin_users: seeded admin identity and password hash.
  • admin_sessions: signed server-side admin sessions.

The management key itself is not stored in plaintext. The server stores an HMAC hash and verifies future mutation requests against it.

URL Policy

The URL policy layer handles:

  • scheme validation: only http and https;
  • HTTPS-only production mode;
  • host normalization through IDNA;
  • path normalization;
  • userinfo rejection;
  • localhost and private/internal IP blocking.

This protects the redirect endpoint from obvious SSRF-style and local-network targets.

Analytics Design

Every successful redirect records a scan event. The admin dashboard aggregates:

  • total links;
  • active/deleted/expired links;
  • total scans;
  • scans today;
  • scans by day;
  • top QR links by scan count;
  • recent scan events.

The per-link analytics endpoint returns total scans and day buckets for the selected QR code.

Project Layout

apps/api/          FastAPI backend
apps/web/          React + Vite frontend
docker/            Nginx reverse proxy config
docs/              API, operations, plan, and screenshots
scripts/           API smoke verification
docker-compose.yml Production-like local compose
docker-compose.dev.yml Development compose
Makefile           Common local and Docker commands

Local Development

Start the Docker development stack:

make docker-dev-up

Or run the local direct dev servers:

make dev

If port 8000 is already occupied by another local service, use:

make dev-8001

Default URLs:

  • Web: http://localhost:5173
  • API: http://127.0.0.1:8000
  • Admin: http://localhost:5173/admin
  • Admin seed login: admin@example.com / change-me

Production-Like Compose

make docker-up

Open http://localhost:8080.

Production-like compose places Nginx in front of the web app and API. The API uses PostgreSQL and Redis services inside the compose network.

Verification

API smoke test:

make verify-api

Backend tests:

make test-api

Backend coverage gate:

make test-api-cov

Frontend tests:

make test-web

Frontend typecheck and build:

make typecheck-web
make build-web

Security Notes

The QR token is public because it appears in the QR code. It must never authorize updates or deletes by itself. Production update/delete flows require the private management_key returned at creation time through X-QR-Management-Key.

Admin analytics use a separate admin login with an HttpOnly cookie. Do not store admin session secrets in localStorage.

Change these before any real deployment:

  • SECRET_KEY
  • IP_HASH_SECRET
  • ADMIN_SEED_PASSWORD or ADMIN_SEED_PASSWORD_HASH
  • public base URL and TLS termination

About

Side project for practicing system design

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages