Files
qaffee/CLAUDE.md
2026-03-31 17:03:43 +02:00

9.5 KiB
Raw Permalink Blame History

Qaffee by Aquantico Projektübersicht

Wichtig: Diese Datei bei jeder relevanten Änderung aktuell halten — neue Endpoints, Datenbankmigrationen, Routen, Komponenten oder Architekturentscheidungen gehören hier dokumentiert.

Zweck

Digitale Strichliste für eine gemeinsame Kaffeeküche. Mehrere Firmen teilen sich eine Küche; ein Anbieter (Aquantico) verwaltet Produkte und Firmen. Mitarbeiter strichen ihren Konsum auf einem iPad, Firmen und der Anbieter werten den Verbrauch monatlich aus.


Stack

Schicht Technologie
Backend Quarkus 3.17.7, Java 21, RESTEasy Reactive, Hibernate ORM Panache, Flyway
Frontend SvelteKit 2, Svelte 5, TypeScript, adapter-node
Datenbank MariaDB 11.4
Deployment Docker Compose
iPad PWA (manifest.json, apple-mobile-web-app-capable)

Projektstruktur

Strichliste/
├── backend/                         Quarkus-Backend
│   ├── Dockerfile                   Multi-Stage Build (maven → jre)
│   ├── pom.xml                      Quarkus 3.17.7, flyway-mysql Dependency nötig!
│   └── src/main/
│       ├── java/de/strichliste/
│       │   ├── dto/                 Record-DTOs für alle API-Requests/Responses
│       │   ├── entity/              JPA-Entities (PanacheEntityBase + IDENTITY-Strategy!)
│       │   ├── filter/              AuthFilter (JAX-RS NameBinding, Token via ?token=)
│       │   └── resource/
│       │       ├── PublicResource          GET/POST für iPad (kein Auth)
│       │       ├── CompanyAdminResource    COMPANY_ADMIN Rolle
│       │       └── ProviderAdminResource   PROVIDER_ADMIN Rolle
│       └── resources/
│           ├── application.properties
│           └── db/migration/        Flyway SQL-Migrationen (V1V4)
│
├── frontend/                        SvelteKit-Frontend
│   ├── Dockerfile                   Multi-Stage Build (node build → node run)
│   ├── src/
│   │   ├── hooks.server.ts          Proxy: /api/* → http://backend:8080 (kein CORS nötig)
│   │   ├── app.html / app.css       Globales Layout & CSS-Variablen (Aquantico-Farben)
│   │   ├── lib/
│   │   │   ├── api/client.ts        Typisierter API-Client (alle Endpoints)
│   │   │   └── components/
│   │   │       ├── Header.svelte    Globaler Header mit Aquantico-Logo & Zurück-Button
│   │   │       ├── MonthPicker.svelte   Monat Jahr  Picker (DE), ersetzt input[type=month]
│   │   │       └── EmojiPicker.svelte  emoji-picker-element Web Component Wrapper
│   │   └── routes/
│   │       ├── +page.svelte               iPad: Firmen-Auswahl
│   │       ├── company/[id]/              iPad: Mitarbeiter-Auswahl
│   │       ├── company/[id]/tally/        iPad: Produkt wählen → Strich → zurück zu /
│   │       ├── admin/company/             Firmen-Admin (Mitarbeiter, Monatsauswertung, Logo)
│   │       └── admin/provider/            Anbieter-Admin (Firmen, Produkte, Report, Links)
│   └── static/
│       ├── aquantico-logo.png       Originaldatei: AquanticoLogo_Horizontal.png
│       └── manifest.json            PWA-Manifest (Name: "Qaffee by Aquantico")
│
├── db/
│   ├── backup.sh                    mysqldump-Cronjob, 7 Tage Rotation
│   └── seed.sql                     Platzhalter (Schema via Flyway, nicht hier)
├── docker-compose.yml
└── backups/                         Automatische DB-Dumps (gitignore)

Datenmodell

company         id, name, active, logo (MEDIUMBLOB), logo_content_type, created_at
employee        id, company_id, first_name, last_name, active, created_at
product         id, name, price_cents (INT), icon_placeholder (Emoji-Zeichen), active, created_at
tally_entry     id, employee_id, product_id, price_cents_at_booking, month_key (YYYY-MM), created_at
access_link     id, token (64 chars), role (COMPANY_ADMIN|PROVIDER_ADMIN), company_id, description, active

Wichtige Konventionen:

  • Alle Entities erben von PanacheEntityBase<Long> mit @GeneratedValue(strategy = IDENTITY)nicht PanacheEntity (würde SEQUENCE verwenden, MariaDB-inkompatibel)
  • Preise immer in Cent (Integer), nie als Decimal
  • price_cents_at_booking in tally_entry sichert historische Preise — Buchungen sind unveränderlich
  • active-Flag statt DELETE (Soft Delete) bei Company, Employee, Product, AccessLink
  • month_key (z.B. 2026-03) als denormalisiertes Feld für schnelle Monatsabfragen

Flyway-Migrationen

Version Inhalt
V1 Initiales Schema (alle Tabellen)
V2 Seed-Daten (Beispielfirmen, Produkte, Tokens)
V3 logo, logo_content_type in company
V4 price_cents_at_booking in tally_entry

Neue Migrationen immer als V5__beschreibung.sql anlegen — niemals bestehende Migrationen ändern.


Auth-Konzept

  • iPad-Oberfläche (/, /company/*): komplett öffentlich, kein Token
  • Admin-Bereiche: Token als Query-Parameter ?token=xyz oder Header X-Auth-Token
  • Token wird gegen access_link-Tabelle geprüft (AuthFilter.java, JAX-RS @NameBinding)
  • Rollen: COMPANY_ADMIN (sieht nur eigene Firma), PROVIDER_ADMIN (sieht alles)
  • Tokens werden vom Anbieter-Admin generiert (POST /api/admin/provider/access-links)

Seed-Tokens (Entwicklung):

  • provider-admin-token → Anbieter-Admin
  • company1-admin-token → Firmen-Admin für Firma 1
  • company2-admin-token → Firmen-Admin für Firma 2

API-Übersicht

Öffentlich (kein Auth)

GET  /api/companies                        Aktive Firmen
GET  /api/companies/{id}/employees         Aktive Mitarbeiter einer Firma
GET  /api/companies/{id}/logo              Firmenlogo (Binär)
GET  /api/products                         Aktive Produkte
POST /api/tally                            Strich erfassen {employeeId, productId}
GET  /api/tally/monthly/{employeeId}       Monatsübersicht (?month=YYYY-MM)

Firmen-Admin (?token=…, Rolle: COMPANY_ADMIN)

GET/POST      /api/admin/company/employees
PUT           /api/admin/company/employees/{id}
PUT           /api/admin/company/employees/{id}/toggle
POST          /api/admin/company/logo              (multipart/form-data)
DELETE        /api/admin/company/logo
GET           /api/admin/company/report            (?month=YYYY-MM)
GET           /api/admin/company/report/employee/{id}

Anbieter-Admin (?token=…, Rolle: PROVIDER_ADMIN)

GET/POST      /api/admin/provider/companies
PUT           /api/admin/provider/companies/{id}
PUT           /api/admin/provider/companies/{id}/toggle
GET/POST      /api/admin/provider/products
PUT           /api/admin/provider/products/{id}
PUT           /api/admin/provider/products/{id}/toggle
GET           /api/admin/provider/report            (?month=YYYY-MM)
GET/POST      /api/admin/provider/access-links

Frontend-Routen

URL Seite Zugang
/ Firmen-Auswahl öffentlich (iPad)
/company/[id] Mitarbeiter-Auswahl öffentlich (iPad)
/company/[id]/tally?employee=[id] Produkt wählen öffentlich (iPad)
/admin/company?token=… Firmen-Admin COMPANY_ADMIN Token
/admin/provider?token=… Anbieter-Admin PROVIDER_ADMIN Token

CSS-Variablen (Aquantico-Branding)

--color-navy:          #0d2440   /* Header-Hintergrund */
--color-bg:            #081829   /* Seitenhintergrund */
--color-bg-secondary:  #0d2440
--color-bg-card:       #122e52   /* Kacheln */
--color-primary:       #00b4c8   /* Teal (Aquantico) */
--color-teal:          #00b4c8
--color-warning:       #ffa502   /* "Qaffee"-Schriftzug im Header */
--color-text-muted:    #8aaabf
--color-border:        #1a3a5c

Bekannte Stolperfallen

  • PanacheEntity vs PanacheEntityBase: Immer PanacheEntityBase<Long> + @GeneratedValue(strategy = IDENTITY) verwenden. PanacheEntity nutzt SEQUENCE, das auf MariaDB nicht existiert.
  • flyway-mysql: Muss explizit in pom.xml stehen (org.flywaydb:flyway-mysql), sonst erkennt Flyway MariaDB nicht.
  • CORS: Im Backend deaktiviert (quarkus.http.cors=false). Alle API-Anfragen laufen über den SvelteKit-Server-Proxy (hooks.server.ts), der Origin-Header würde sonst 403 auslösen.
  • icon_placeholder: Enthält seit Einführung des EmojiPickers direkte Emoji-Zeichen (z.B. ). Legacy-Produkte aus Seed-Daten haben noch Schlüsselwörter (coffee, tea, chocolate) — LEGACY_ICON_MAP in der Tally-Seite behandelt das.
  • MariaDB-Version: Muss 11.4 sein (LTS). 11.8 wird von Flyway nicht unterstützt.
  • Docker-Proxy: API_URL=http://backend:8080 (privat, nicht PUBLIC_) in docker-compose.yml für den SvelteKit-Server.

Lokale Entwicklung (ohne Docker)

# MariaDB lokal starten (z.B. via Docker)
docker run -d -p 3306:3306 -e MARIADB_DATABASE=strichliste \
  -e MARIADB_USER=strichliste -e MARIADB_PASSWORD=strichliste \
  -e MARIADB_ROOT_PASSWORD=root mariadb:11.4

# Backend
cd backend
./mvnw quarkus:dev

# Frontend (Vite-Proxy leitet /api → localhost:8080)
cd frontend
npm run dev

Deployment

docker compose up --build          # Alles neu bauen und starten
docker compose up --build frontend # Nur Frontend neu bauen
docker compose down -v             # Alles stoppen + Volumes löschen (DB-Reset)

Backups liegen automatisch in backups/ (täglicher mysqldump, 7 Tage Rotation).