# 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).