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

228 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```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<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)
```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).