9.5 KiB
9.5 KiB
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)— nichtPanacheEntity(würde SEQUENCE verwenden, MariaDB-inkompatibel) - Preise immer in Cent (Integer), nie als Decimal
price_cents_at_bookingintally_entrysichert historische Preise — Buchungen sind unveränderlichactive-Flag statt DELETE (Soft Delete) bei Company, Employee, Product, AccessLinkmonth_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=xyzoder HeaderX-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-Admincompany1-admin-token→ Firmen-Admin für Firma 1company2-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.PanacheEntitynutzt SEQUENCE, das auf MariaDB nicht existiert. - flyway-mysql: Muss explizit in
pom.xmlstehen (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), derOrigin-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_MAPin der Tally-Seite behandelt das.- MariaDB-Version: Muss
11.4sein (LTS).11.8wird 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).