add claude.md
This commit is contained in:
227
CLAUDE.md
Normal file
227
CLAUDE.md
Normal file
@@ -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<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).
|
||||
Reference in New Issue
Block a user