Records pageviews (path, referrer, browser/OS/device, IP, GeoIP country) via a POST /api/track beacon into a new PageView table, and exposes aggregated daily/breakdown stats behind a token-gated GET /api/admin/stats endpoint with brute-force lockout. Frontend gets a /admin dashboard (charts + breakdown tables) built on recharts, with a switchable per-day pageviews chart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.5 KiB
YAML
54 lines
1.5 KiB
YAML
services:
|
|
db:
|
|
image: postgres:18-alpine
|
|
environment:
|
|
POSTGRES_USER: bridzik
|
|
POSTGRES_PASSWORD: bridzik
|
|
POSTGRES_DB: bridzik
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U bridzik -d bridzik"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
backend:
|
|
build: .
|
|
environment:
|
|
# Async SQLAlchemy URL -> the Postgres service above (asyncpg driver).
|
|
DATABASE_URL: postgresql+asyncpg://bridzik:bridzik@db:5432/bridzik
|
|
# Shared secret for the self-hosted /api/admin/* stats endpoints.
|
|
ADMIN_TOKEN: tajneheslo
|
|
# Optional: IP -> country for /api/track. Drop a .mmdb file (GeoLite2 or
|
|
# a DB-IP/IP2Location Lite equivalent) at ./geoip/ -- it's already inside
|
|
# the ./:/app bind mount below, no extra volume entry needed. Missing
|
|
# file -> country is just recorded as "" (see api/stats.py:_country_for_ip).
|
|
GEOIP_DB_PATH: /app/geoip/GeoLite2-Country.mmdb
|
|
ports:
|
|
- "5000:5000"
|
|
volumes:
|
|
- ./:/app
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
|
|
frontend:
|
|
image: node:22-alpine
|
|
working_dir: /app
|
|
environment:
|
|
# Inside the compose network the backend is reachable as `backend`.
|
|
VITE_BACKEND_URL: http://backend:5000
|
|
volumes:
|
|
- ./frontend:/app
|
|
ports:
|
|
- "5173:5173"
|
|
command: sh -c "npm install && npm run dev -- --host"
|
|
depends_on:
|
|
- backend
|
|
|
|
volumes:
|
|
pgdata:
|