From e79826d37c4741bc1e535c7915543407328e6e68 Mon Sep 17 00:00:00 2001 From: Markus Date: Tue, 31 Mar 2026 17:03:43 +0200 Subject: [PATCH] add claude.md --- CLAUDE.md | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7108067 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,227 @@ +# 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 (V1–V4) +│ +├── 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` 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) + +```css +--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` + `@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) + +```bash +# 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 + +```bash +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).