Commit graph

123 commits

Author SHA1 Message Date
Jens Reinemann
66a5a7d7a5 fix: Admin-Inventarliste zeigt GUID wenn kein Name gesetzt 2026-05-17 18:47:28 +02:00
Jens Reinemann
117d5c7af0 style: Admin-Login-Formular auf 360px Breite begrenzt (#95) 2026-05-17 18:44:58 +02:00
Jens Reinemann
fe7501bd27 feat: HTTPS – URLs auf https://bollwerk.online aktualisiert, Docker-Ports auf localhost beschraenkt (#95) 2026-05-17 18:24:28 +02:00
Jens Reinemann
e73d3a11a0 ci: disable automatic CI triggers on push/PR
Both android-ci.yml and ci.yml now only run via workflow_dispatch
(manual trigger). Automatic builds on push/PR are disabled to stop
failing pipeline notifications.
2026-05-17 18:23:27 +02:00
Jens Reinemann
e0130910af chore: migrate server URLs from IP to bollwerk.online domain
Replace all HTTP references to 195.246.231.210 with bollwerk.online
across skills, prompts, scripts, and app default settings:

- Dockerfile: rename KRISENVORRAT_JWT_SECRET to BOLLWERK_JWT_SECRET
- SettingsKey.kt: default server URL now http://bollwerk.online:8080
- publish SKILL/prompt/script: HTTP URLs updated to bollwerk.online
- vps-deploy SKILL: Admin-UI and health-check URLs updated
- run-integration-tests.ps1: default BaseUrl updated

SSH commands (ssh/scp) intentionally kept on IP, as DNS is not
used for SSH access.
2026-05-17 18:22:17 +02:00
Jens Reinemann
83ef83f4e7 revert: Repo-Name zurueck auf bollwerk (der neue korrekte Name)
Mein vorheriger Commit hat faelschlicherweise bollwerk -> krisenvorrat
geaendert. Richtig ist: krisenvorrat (alt) -> bollwerk (neu).
2026-05-17 18:12:38 +02:00
Jens Reinemann
7c768be648 fix: Repo-Name in Skripten/Skills auf krisenvorrat korrigieren
- create-next-ticket.ps1, next-ticket.ps1, watch-pipeline.ps1: \
- SKILL.md (gh-tickets): Repo-Referenz + Issue-URL
- workflow-planning.prompt.md, workflow-block-planning.prompt.md: Issue-URL
2026-05-17 18:11:23 +02:00
Jens Reinemann
a5f89e6a69 rename: Krisenvorrat -> Bollwerk
- Package: de.krisenvorrat.* -> de.bollwerk.*
- Klassen: KrisenvorratApp/Database/Theme -> Bollwerk*
- ApplicationId: de.bollwerk.app
- Server: BOLLWERK_* Env-Vars, bollwerk HOCON-Config
- Docker: bollwerk-server/db/backup Container-Namen
- Room DB: bollwerk.db, SharedPrefs: bollwerk_secure_prefs
- Export-Dateien: bollwerk_export/inventar
- UI-Strings, HTML, Admin-UI: alle auf Bollwerk
- Docs, Skills, README angepasst
- Alle Tests gruen, Build erfolgreich
2026-05-17 17:44:02 +02:00
Jens Reinemann
f1abc2cd23 style: Homepage Prepper-Redesign (Rost, Stahl, Beton)
- Beton-Textur: staerkerer Noise-Overlay (6 Octaves, opacity 0.12)
- Rost-Flecken: radiale Gradients als zweites Body-Overlay
- Stahl-Panel: metallisches Panel mit Gradient, Inset-Schatten, 4 Nieten
- Hazard-Bar: Warnstreifen am oberen Rand
- Rost-Trennlinie: oxidierter Gradient-Divider unter dem Titel
- Militaer-Typografie: Black Ops One fuer Titel (Stencil-Look)
- QR-Code eingelassen in dunklen Ausschnitt mit Inset-Schatten
- Download-Button: Rost-Gradient mit Glueh-Effekt beim Hover
- Footer-Stahlplatte: Tags (Android, Offline-faehig, Lokal)
- Subtitle: Inventar - Vorsorge - Sicherheit
2026-05-17 17:24:07 +02:00
Jens Reinemann
46bfaa0367 chore: Logo überarbeiten – neue Ratte mit Plättchenpanzer & Patronengurt
- Altes Vektor-Foreground (IKEA Gosig-Style) durch PNG-basiertes Logo ersetzt
- Neue Ratte: grau, illustrativ, bronzener Plättchenpanzer, Patronengurt
- Adaptive-Icon-Foreground als PNG in allen Density-Stufen (mdpi–xxxhdpi)
- Legacy-Launcher-Icons mit dunklem Hintergrund (#16140F)
- Hintergrund von Olivgrün (#3D5229) auf App-Theme-Dunkel (#16140F) geändert
- Web-Logo auf Download-Seite eingebunden (/static/logo.png)
- Quelldatei unter docs/ratti.png archiviert

Closes #93
2026-05-17 17:14:11 +02:00
Jens Reinemann
0fb1ebbdca style: App-UI an Admin-Bereich angleichen (#92)
- Color.kt: Farbpalette von Olivgrün auf Admin-Palette umgestellt
  (burnt orange #C1440E primary, warm beige #E8D5B0 text,
   dark brown #16140F background, tan #A89070 labels)
- Type.kt: Neue Typography mit Monospace für Headings/Titles/Labels
  (analog Admin Share Tech Mono), system-ui für Body
- Theme.kt: KrisenvorratTypography eingebunden
- Dark Mode vollständig spezifiziert, Admin-konsistent
- Alle bestehenden Screens profitieren automatisch via MaterialTheme
- Build , alle Tests grün (70 tasks)
2026-05-17 16:51:46 +02:00
Jens Reinemann
077d16f056 style: Download-Homepage an Admin-Bereich angleichen (#91)
- Farbpalette: dunkler Hintergrund (#16140F), warme Akzentfarben (#C1440E)
- Typografie: Share Tech Mono für Heading/Version, system-ui für Body
- Card-Style: #242119, border-left accent, 3px radius, dunkle Schatten
- Noise-Texture-Overlay wie im Admin-Bereich
- QR-Code-Farben an dunkles Theme angepasst (hell auf dunkel)
- Download-Button: uppercase, monospace, Admin-Primärfarbe
- Responsive Layout beibehalten
2026-05-17 16:12:06 +02:00
Jens Reinemann
47a2865b34 feat: Sync-Statusanzeige mit Live-Verbindungsstatus und Aktivitaets-Feed (#94)
- ConnectionState sealed interface (Connected/Connecting/Disconnected/NotConfigured)
- WebSocketClientImpl: connectionState StateFlow mit Reconnect-Countdown-Timer
- SyncActivityMessage: Aktivitaets-Feed-Modell mit 3-Sek-Auto-Dismiss
- PendingSyncOpDao: getCount() Flow fuer Queue-Groesse
- SettingsUiState: SyncStatus durch ConnectionState + SyncActivity ersetzt
- SettingsViewModel: observeConnectionState, observePendingQueueCount,
  showActivity mit Job-basiertem Timer-Reset
- SettingsScreen: Farbiger Punkt + Statustext, AnimatedVisibility
  fuer Aktivitaets-Feed, Countdown bei Disconnected
- Alle 306 Tests gruen
2026-05-17 16:02:55 +02:00
Jens Reinemann
bba4ac0086 fix: publish-apk.ps1 PowerShell 5.1 Kompatibilitaet
- En-Dash durch Hyphen ersetzt (UTF-8 Encoding-Problem)
- SSH-Befehle: && durch ; ersetzt (PS 5.1 kennt kein &&)
- 2>/dev/null aus PS-Parsing entfernt (String-Konkatenation)
- SSH-Array-Rueckgabe korrekt behandeln (letztes Element)
2026-05-17 15:39:12 +02:00
Jens Reinemann
5434c00f20 feat: automatischer Sync nach Login/Reconnect, manuelle Push/Pull-Buttons entfernen
- WebSocket Connected-Event löst jetzt automatisch pullSync() aus
  (nach Login = Full Sync, nach Reconnect = inkrementell)
- Push/Pull-Buttons ersetzt durch Hinweis 'Synchronisierung erfolgt
  automatisch' + Fallback-Button 'Jetzt synchronisieren'
- ServerUrl: Default-Wert (VPS-IP) als Konstante in SettingsKey,
  Reset-Button neben dem URL-Feld
- SettingsUiState: serverUrl Default = DEFAULT_SERVER_URL
- Tests angepasst (SettingsRepositoryImplTest)
2026-05-17 15:36:11 +02:00
Jens Reinemann
0fee89ec32 feat: Admin-UI Tab-Navigation + Backups-Endpoint (#90)
- Tab-Leiste mit drei Tabs: User, Inventare, Backups
- Aktiver Tab visuell hervorgehoben, nur aktiver Inhalt sichtbar
- Default-Tab: User
- Neuer GET /api/admin/backups Endpoint (JWT-geschützt)
  → listet .sql.gz-Dateien aus /backups (name, sizeBytes, createdAt)
  → absteigend nach Datum sortiert
- Backups-Tab: Tabelle mit Dateiname, Größe (human-readable), Erstellt
  → Refresh-Button, Hinweis bei leerem Verzeichnis
- docker-compose.yml: backup_data:/backups:ro Mount im Server-Container
- 4 neue Tests (Admin-Backups: 200, 403, 401, Dateiliste sortiert)
2026-05-17 12:00:54 +02:00
Jens Reinemann
9004baede1 feat: Server Admin UI postapokalyptisches Rost/Stahl/Beton-Theme
- Farbpalette komplett auf dunklen Asphalt/Beton, verwitterten Stahl,
  Rost-Orange, Marsstaub umgestellt
- Share Tech Mono (Google Fonts) fuer Ueberschriften, Labels, Buttons
- Noise-Overlay (SVG feTurbulence) fuer Beton-Textur
- Scrollbar-Styling (WebKit) im Theme
- Cards mit Rost-Border-Left-Akzent und dunklem Box-Shadow
- Modals mit Rost-Border, Tabellen mit alternierenden dunklen Zeilen
- Stat-Cards: Werte in Rost-Orange (#C1440E)
- Dekorative h2-Prefixe (Quadrat-Symbol)
- Keine JS-Logik oder API-Aenderungen

Closes #88
2026-05-17 11:53:41 +02:00
Jens Reinemann
1df2d1cff5 refactor: manuelle DB-Migrationen durch Room AutoMigration ersetzen
- DB-Version auf 6 hochgezaehlt (Clean-Slate, keine Rueckwaertskompatibilitaet)
- Alle manuellen Migrationen (v1-v5) aus Migrations.kt entfernt
- DatabaseModule: addMigrations() durch fallbackToDestructiveMigration() ersetzt
- migration-guide.md: AutoMigration-Workflow dokumentiert
- Instrumentierte Tests: alte Migrationstests durch frische DB-Tests ersetzt
- Schema 6.json exportiert

Closes #89
2026-05-17 11:43:27 +02:00
Jens Reinemann
5e9c072b51 refactor: kcalPerKg -> kcalPerUnit (kcal pro Einheit)
- ItemEntity, ItemDto: kcalPerKg -> kcalPerUnit (kcal_per_unit)
- Room DB: version 4 -> 5, MIGRATION_4_5 hinzugefuegt
- CalculateSupplyRangeUseCase: Berechnung vereinfacht zu
  quantity * kcalPerUnit (keine Einheitenumrechnung mehr noetig,
  alle Einheiten unterstuetzt)
- ItemFormScreen: Label 'kcal / kg' -> 'kcal / Einheit'
- CsvExporter: Header 'kcal/kg' -> 'kcal/Einheit'
- OpenAiVisionService: Prompt-JSON-Feld angepasst
- Server Tables + InventoryRepository: kcal_per_unit
- Alle Tests aktualisiert und gruen
2026-05-17 11:29:39 +02:00
Jens Reinemann
db2fc5dea1 fix: busybox crond statt dcron, pg17-Tokens aus Checksum filtern (#87)
- Dockerfile: dcron -> busybox crond (kein setpgid-Fehler im Container)
  Crontab in /var/spool/cron/crontabs/root
- backup.sh: grep-Filter fuer PostgreSQL 17 Security-Tokens
  (\restrict / \unrestrict) die sich bei jedem Dump aendern
2026-05-17 11:14:47 +02:00
Jens Reinemann
d66f0d65c3 feat: tägliches PostgreSQL-Backup mit 1 GB Rotation (#87)
- backup/Dockerfile: Alpine 3.21 + postgresql-client + dcron
- backup/backup.sh: pg_dump -> MD5-Checksum-Vergleich (skip bei unveränderter DB)
  -> gzip-komprimiertes Backup mit Timestamp
  -> Rotation: älteste .sql.gz löschen bis Gesamtgröße < 1 GB
- docker-compose.yml: neuer Service 'backup', Volume 'backup_data'
- Cronjob: täglich 03:00 UTC
2026-05-17 11:06:52 +02:00
Jens Reinemann
28b7e83297 refactor: Bottom-Navigation auf custom Surface+Row+IconButton umstellen
Material NavigationBar/NavigationBarItem hat hardcoded interne Paddings
(16dp oben/unten + 32dp Indicator), die bei height-Constraints die Icons
proportional schrumpfen lassen. Custom-Lösung mit:
- Surface + NavigationBarDefaults.containerColor/Elevation
- windowInsetsPadding(NavigationBarDefaults.windowInsets) für System-Insets
- Row mit IconButtons (standard 48dp Touch-Target, 24dp Icons)
- padding(vertical = 4.dp) statt 24dp Material-Default
Icons behalten volle Größe, Bar ist deutlich kompakter.
2026-05-17 11:05:05 +02:00
Jens Reinemann
4397159d62 fix: NavigationBar-Höhe auf 56dp fixieren 2026-05-17 10:59:48 +02:00
Jens Reinemann
32ed321df2 feat: Admin-Statistiken pro Inventar & Inventar-Tabelle mit Paging/Sortierung/Filter/Suche
- Neues DTO: InventoryStatsPerInventoryDto (inventoryId, inventoryName,
  totalItems, totalLocations, totalCategories, recentTransactions,
  lastUpdated, userCount)
- InventoryRepository.getStatsPerInventory(): Stats pro Inventar
- Neuer Endpoint: GET /api/admin/stats/inventories (Admin-only)
- Admin-UI: aufklappbarer Bereich Statistiken pro Inventar (sortierbar)
- Admin-UI: Inventar-Karten durch Tabelle ersetzt mit
  - Paging (10/25/50 Eintraege pro Seite)
  - Sortierung per Klick auf Spaltenheader
  - Filter (alle / mit Benutzern / ohne Benutzer)
  - Freitextsuche nach Name oder Inventar-ID
- Tests: 3 neue InventoryRepositoryTests, 3 neue InventoryStatsTests
  (401/403/Inhalt fuer neuen Endpoint), setUp bereinigt alle Tabellen
- Alle 148 Tests gruen
2026-05-17 10:56:22 +02:00
Jens Reinemann
11d2094eef style: QR-Code auf Server-Homepage zentrieren
#qrcode-Container nutzt jetzt display:flex + justify-content:center
statt margin:0 auto, damit das von QRCode.js generierte Canvas
korrekt mittig angezeigt wird.
2026-05-17 10:22:40 +02:00
Jens Reinemann
dfa4b37eda feat(update): Update-Dialog, Installation & App-Start-Integration (#85)
- AndroidManifest: REQUEST_INSTALL_PACKAGES Permission hinzugefuegt
- file_paths.xml: cache/update/ Pfad fuer FileProvider ergaenzt
- UpdateUiState: Sealed UI-State (Hidden, Checking, Available, Downloading,
  ReadyToInstall, Error)
- UpdateViewModel: State-Management fuer Update-Check beim App-Start,
  APK-Download mit Fortschrittsanzeige, Installation via FileProvider
- UpdateBanner: Nicht-blockierender animierter Banner mit Versionsanzeige,
  Download-Button, LinearProgressIndicator, Dismiss-Moeglichkeit
- MainScreen: UpdateBanner ueber dem NavGraph integriert
- ApkInstaller Interface + Impl: Testbare Abstraktion fuer APK-Installation
- RepositoryModule: ApkInstaller DI-Binding registriert
- 7 Unit Tests fuer UpdateViewModel (Update verfuegbar, kein Update,
  nicht konfiguriert, Fehler, Download-Fortschritt, Download-Fehler, Dismiss)
- Alle 441 Tests gruen

Closes #85
2026-05-17 05:13:11 +02:00
Jens Reinemann
3ce8ec28e9 feat(update): Update-Check & APK-Download Data/Domain-Layer
VersionInfo-Datenmodell, UpdateRepository (checkForUpdate + downloadApk
mit Progress-Callback) und CheckForUpdateUseCase implementiert.

Neue Dateien:
- domain/model/VersionInfo.kt: @Serializable Datenmodell (versionCode,
  versionName, apkUrl)
- domain/model/UpdateCheckResult.kt: Sealed interface (UpdateAvailable,
  UpToDate, Error, NotConfigured)
- domain/repository/UpdateRepository.kt: Interface
- data/repository/UpdateRepositoryImpl.kt: Ktor HttpClient mit
  Streaming-Download und Progress-Reporting
- domain/usecase/CheckForUpdateUseCase.kt: Vergleicht Server-versionCode
  mit BuildConfig.VERSION_CODE, prueft Server-URL via SettingsRepository

Geaenderte Dateien:
- di/RepositoryModule.kt: UpdateRepository Hilt-Binding ergaenzt

Tests (12 neue):
- UpdateRepositoryImplTest: checkForUpdate (Erfolg, 500, trailing slash),
  downloadApk (Erfolg+Progress, 404, Parent-Dir-Erstellung)
- CheckForUpdateUseCaseTest: neuer/gleicher/aelterer versionCode,
  leere/blanke Server-URL, Netzwerkfehler

Closes #84
2026-05-17 04:38:34 +02:00
Jens Reinemann
994d6b1b07 feat(server): Version-Endpoint, APK-Hosting & Homepage mit QR-Code
Routing.kt: GET /api/version (öffentlich, kein Auth) liefert JSON mit
versionCode, versionName und apkUrl (aus Request-Host abgeleitet).
GET / zeigt HTML-Homepage mit App-Name, Version und QR-Code
(clientseitiges JS via qrcode.js CDN) für direkten APK-Download.
staticFiles /static bedient APK aus server/data/ (Dateisystem).

Neue Dateien:
- VersionInfo.kt: Serializable DTO (versionCode, versionName, apkUrl)
- VersionRoutes.kt: Route-Definitionen für /api/version und /
- VersionEndpointTest.kt: 11 Tests (Endpoint, Homepage, Admin, 404)

Geänderte Dateien:
- application.conf: appVersionCode + appVersionName (mit Env-Override)
- Routing.kt: versionRoutes + staticFiles eingebunden
- TestHelpers.kt: testMapConfig um Version-Felder erweitert
- Dockerfile: data-Verzeichnis für APK-Hosting angelegt

Closes #83
2026-05-17 04:32:28 +02:00
Jens Reinemann
ec41a64b5e refactor(settings): type-safe Settings-Keys mit SettingsKey sealed class
Neue SettingsKey<T> sealed class mit typisierten StringKey-Objekten,
Default-Werten und Compile-Time-Checks auf gueltige Keys. Alte String-
basierte API bleibt fuer Import/Export-Filter (SENSITIVE_KEYS) erhalten.

SettingsRepository: Neue typisierte Methoden getString(), getStringOrNull(),
setString(), observeString() mit SettingsKey.StringKey-Parameter.

Alle Caller migriert:
- SettingsViewModel: KEY_*-Companion entfernt, StringKey.* direkt genutzt
- CameraViewModel, DashboardViewModel, InventoryPickerViewModel: analog
- SyncServiceImpl: KEY_*-Companion entfernt, getString()/setString()
- ItemRepositoryImpl, MessageRepositoryImpl: getString()/getStringOrNull()

Tests: Alle FakeSettingsRepository-Klassen um typisierte Methoden
erweitert, Mock-Calls in CameraViewModelTest und SyncServiceImplTest
angepasst, 5 neue Tests fuer typisierte API in SettingsRepositoryImplTest.
72 Tests gruen.

Closes #82
2026-05-17 04:26:27 +02:00
Jens Reinemann
d81acfbb4f feat(export): CSV- und PDF-Export mit Share-Intent
- CsvExporter: UTF-8 BOM, Semikolon-Separator, DE-Locale, aufgelöste
  Kategorie-/Lagerort-Namen, CSV-Escaping für Sonderzeichen
- PdfExporter: Android PdfDocument API, A4-Format, nach Kategorie
  gruppiert, Tabellenheader, Gesamtwert, automatischer Seitenumbruch
- ImportExportRepository: +exportToCsv(), +exportToPdf(File)
- SettingsViewModel: +exportCsv(), +exportPdf() mit FileProvider-URI
- ShareContent: +Csv(fileUri), +Pdf(fileUri) Varianten
- SettingsScreen: CSV- und PDF-Buttons, Share-Intent für text/csv
  und application/pdf
- 15 neue Tests: CsvExporterTest (7), CSV-Repository-Tests (5),
  ViewModel-Tests für CSV/PDF (4 = 2 success + 2 failure)
- Alle 282 Tests grün

Closes #81
2026-05-17 04:13:14 +02:00
Jens Reinemann
61ef56425d test(server): Server-Integrationstests vervollständigen
Auth: Expired-Token-Tests (Access + Refresh), fehlende Felder
Message-API: Send, Get Conversation, Blank Body, Receiver Not Found,
  Custom ID, Response-Format (9 Tests)
WebSocket: Connect mit gültigem/ungültigem/fehlendem/abgelaufenem Token,
  inventoryUpdated-Event, new_message-Event, Disconnect, undelivered
  Messages bei Connect (8 Tests)
CI-Pipeline: Auto-Trigger für push/PR auf app/shared/server-Pfade,
  Step-Label verdeutlicht (app + shared + server)

Server-Tests: 130 gesamt, 0 Failures

Closes #80
2026-05-17 04:02:34 +02:00
Jens Reinemann
eb9ab6aa54 feat: Multi-Inventar auf Client-Seite (#79)
Server:
- Inventories-Tabelle um name-Spalte erweitert
- Neue User-facing REST-Routes: GET/POST /api/inventories,
  POST /api/inventories/{id}/switch
- LoginResponse enthält inventoryId + inventoryName
- InventoryRepository: createInventory(name), getUserInventories(),
  getInventoryName()
- AuthRoutes: inventoryRepository injiziert für Login-Response

Shared:
- InventoryInfoDto (id, name, isActive)
- CreateInventoryRequest (name)

Client:
- SettingsKeys: ACTIVE_INVENTORY_ID, ACTIVE_INVENTORY_NAME
- SyncService: listInventories(), createInventory(), switchInventory()
- SyncServiceImpl: Implementierung der drei Endpunkte + Login
  speichert inventoryId/Name
- InventoryPickerViewModel: Laden, Erstellen, Wechseln von Inventaren
  mit automatischem Re-Sync
- InventoryPickerSheet: ModalBottomSheet mit Inventarliste, Auswahl
  und Erstellen-Dialog
- MainScreen: TopAppBar zeigt aktiven Inventar-Namen mit Wechsel-Button

Tests:
- 8 neue Server-Tests (InventoryManagementTest)
- 8 neue Client-Tests (InventoryPickerViewModelTest)
- Bestehende FakeSyncService in 2 Test-Dateien aktualisiert

Closes #79
2026-05-17 03:55:08 +02:00
Jens Reinemann
6711a0e056 feat(item-list): Suche und Filter auf Item-Liste (#76)
ItemListScreen: Suchleiste (OutlinedTextField) mit Search/Clear-Icons,
FilterChipRow mit Dropdown-Menüs für Kategorie, Lagerort und
Ablaufstatus (Abgelaufen/Bald ablaufend/OK). Leere Ergebnisse
zeigen 'Keine Items gefunden'. Suche durchsucht Name und Notizen
(case-insensitive), alle Filter sind kombinierbar.

ItemListViewModel: FilterState (MutableStateFlow) mit searchQuery,
categoryId, locationId, expiryFilter. combine() mit 4 Flows,
lokale Filterung auf gemappten ItemUiModel-Instanzen.

ItemUiModel: locationId und notes mit Defaults hinzugefügt.

13 neue Unit-Tests für Suche, Filter, Kombinationen und
Leerzustände. Alle 257 Tests grün.

Closes #76
2026-05-17 03:42:06 +02:00
Jens Reinemann
7c17f8ea2f feat(server): Rate-Limiting auf alle API-Endpoints
Ktor RateLimit-Plugin mit abgestuften Limits pro Endpoint-Gruppe:
- Auth (/api/auth/*): 10 req/min per IP (Brute-Force-Schutz)
- Messages (/api/messages/*): 30 req/min per IP (Spam-Schutz)
- Inventory (/api/inventory/*): 60 req/min per IP (DoS-Schutz)
- Admin (/api/admin/*): 20 req/min per IP

Neue Dateien:
- RateLimiting.kt: Plugin-Konfiguration mit 4 benannten Limitern
- RateLimitingTest.kt: 5 Tests (Limit-Ueberschreitung, Within-Limit,
  Health-Endpoint ohne Limit, Retry-After-Header)

Geaenderte Dateien:
- Routing.kt: rateLimit()-Wrapper um Route-Gruppen
- Application.kt: configureRateLimiting() in Plugin-Pipeline
- libs.versions.toml + build.gradle.kts: ktor-server-rate-limit Dep

Closes #75
2026-05-17 03:31:57 +02:00
Jens Reinemann
0f25c180ed feat(sync): Full-Inventory-Sync durch Delta-Sync ersetzen
Server + Client: Timestamp-basierter Delta-Sync als Alternative zum
Full-Sync. GET /api/inventory akzeptiert jetzt optionalen ?since=<ts>
Query-Parameter und liefert nur Items mit lastUpdated > since.

Shared: InventoryDto um deletedItemIds-Feld erweitert (Default: leer,
backward-compatible mit bestehenden Clients).

Server:
- DeletedItems-Tabelle trackt geloeschte Item-IDs pro Inventory
- InventoryRepository.loadInventorySince(): Delta-Query mit Items +
  deletedItemIds seit Timestamp
- saveInventory()/deleteItem(): Loeschungen werden in DeletedItems
  protokolliert
- DatabaseFactory: DeletedItems-Tabelle registriert

Client (App):
- SyncService.downloadInventory(since: Long?): optionaler since-Param
- SyncServiceImpl: haengt ?since= an GET-Request
- ItemDao.deleteByIds(): Batch-Loeschung fuer Delta-Sync
- ImportExportRepositoryImpl: verarbeitet deletedItemIds aus DTO
- SettingsViewModel.pullSync(fullSync): Delta-Sync mit letztem
  Sync-Timestamp; fullSyncRequired-Event loest weiterhin Full-Sync aus

Entscheidungen:
- Timestamp-basiert (nutzt bestehendes lastUpdated-Feld)
- Full-Sync bleibt Fallback (fullSyncRequired, erster Sync)
- Categories/Locations/Settings immer vollstaendig (klein)

6 neue DeltaSyncTests + 3 Repository-Tests + 2 SyncService-Tests

Closes #74
2026-05-17 03:15:49 +02:00
Jens Reinemann
75cfc41924 fix(websocket): Reconnect-Strategie robuster machen
WebSocketClientImpl:
- Jitter (±25%) zum exponentiellen Backoff hinzugefügt, um
  Thundering-Herd-Effekt bei Server-Neustart zu vermeiden
- Retry-Zähler mit MAX_RETRIES=5: nach 5 konsekutiven Fehlschlägen
  wird ConnectionFailed-Event emittiert (nach ~62s)
- Client versucht weiterhin im MAX_BACKOFF-Intervall (60s) zu
  reconnecten, gibt nicht vollständig auf
- Zähler wird bei erfolgreicher Verbindung zurückgesetzt

WebSocketEvent: neues ConnectionFailed(message) Event hinzugefügt

SettingsViewModel: ConnectionFailed -> SyncStatus.Error,
Connected -> SyncStatus.Idle (Fehler wird beim Reconnect gelöscht)

Closes #73
2026-05-17 03:00:51 +02:00
Jens Reinemann
eb5bdd4b7b feat(security): JWT-Tokens in EncryptedSharedPreferences speichern
Sensitive Keys (auth_access_token, auth_refresh_token, auth_username,
auth_user_id, openai_api_key) werden jetzt ueber EncryptedSharedPreferences
gespeichert statt als Klartext in der Room-Settings-Tabelle.

Neue Dateien:
- SecureTokenStorage: Interface fuer sichere Key-Value-Speicherung
- EncryptedPrefsTokenStorage: Implementierung mit AndroidX Security Crypto
- SecurityModule: Hilt-Provider fuer SecureTokenStorage

Aenderungen:
- SettingsKeys: SENSITIVE_KEYS Set definiert welche Keys verschluesselt werden
- SettingsRepositoryImpl: Routet sensitive Keys an SecureTokenStorage,
  nicht-sensitive weiterhin an Room DAO
- ImportExportRepositoryImpl: Filtert sensitive Keys bei Export und Import
- SettingsRepositoryImplTest: 4 neue Tests fuer Secure-Storage-Routing

Closes #72
2026-05-17 02:55:43 +02:00
Jens Reinemann
90580ecb3e refactor(room): fallbackToDestructiveMigration entfernen und Migrationstests vervollständigen
Migrations.kt: KDoc für MIGRATION_2_3 und MIGRATION_3_4 ergänzt.
KrisenvorratDatabaseMigrationTest: MIGRATION_3_4 in alle Testhelfer
aufgenommen, createV3Database() + openMigratedDbV4() hinzugefügt,
Tests für v3→v4 (messages-Tabelle) und v1→v4 Full-Path-Migration
ergänzt. freshInstall-Test registriert jetzt alle Migrationen.
docs/migration-guide.md: Entwickler-Leitfaden mit Checkliste,
SQLite-Einschränkungen und Testanleitung.

fallbackToDestructiveMigration() war bereits entfernt; dieses Ticket
stellt sicher, dass alle Migrationspfade getestet und dokumentiert sind.

Closes #71
2026-05-17 02:40:20 +02:00
Jens Reinemann
f792213b1e refactor(server): H2 durch PostgreSQL ersetzen
- DatabaseFactory: HikariCP Connection-Pool fuer PostgreSQL (10 Connections,
  REPEATABLE_READ), H2 weiterhin ohne Pool (fuer Tests)
- Dependencies: postgresql-Treiber + HikariCP hinzugefuegt, H2 nur noch
  testImplementation
- Migration-SQL: uppercase Tabellennamen auf lowercase normalisiert
  (dialect-agnostisch fuer H2 und PostgreSQL)
- docker-compose.yml: PostgreSQL 17 + Krisenvorrat-Server mit DB-Env-Vars
- Env-Var-Konfiguration: KRISENVORRAT_DB_URL, _DB_USER, _DB_PASSWORD,
  _DB_DRIVER (Defaults auf PostgreSQL localhost)
- Alle 554 Tests gruen (H2 in-memory fuer Tests beibehalten)

Closes #70
2026-05-17 02:35:08 +02:00
Jens Reinemann
033b0fae61 feat(server): Statistik-Kacheln auf Admin-Inventarübersicht
- InventoryStatsDto: neues DTO mit totalItems, totalLocations,
  totalCategories, lastUpdated, recentTransactions
- InventoryRepository.getAggregatedStats(): Aggregierte Statistiken
  über alle Inventare (COUNT, MAX, 30-Tage-Filter)
- AdminRoutes: GET /api/admin/stats Endpoint (admin-only)
- Admin-UI: Stats-Grid mit 5 Kacheln (Artikel, Orte, Kategorien,
  Änderungen 30 Tage, letzte Änderung) oben auf der Übersichtsseite
- 7 neue Tests: 4 Endpoint-Tests (InventoryStatsTest),
  3 Repository-Tests (getAggregatedStats)
- Alle 87 Tests grün

Closes #68
2026-05-17 02:19:18 +02:00
Jens Reinemann
549e4c916e feat(server): PATCH /api/inventory/items/{id} auf partielles Update umstellen
- Route empfängt JsonObject statt ItemDto, nur übergebene Felder werden aktualisiert
- validatePartialItem() validiert nur vorhandene Felder (name, unit, expiryDate)
- patchItemPartial() im Repository: Guard für leere Felder, kein SQL-Update wenn nichts zu ändern
- Response liefert das aktualisierte Item aus der DB (loadItem) statt des Inputs
- Bestehende Tests in InputValidationTest angepasst (senden nun partielle JSON-Bodys)
- Neue PatchItemTest-Klasse: 10 Tests (Happy Path, 404, Auth, Validierung, Persistenz)
- Alle 554 Tests grün

Closes #56
2026-05-17 02:13:24 +02:00
Jens Reinemann
4b1a5818f2 feat(chat): UTF-8-Unterstützung für Umlaute und Emoji-Eingabe
Closes #66

Server (Serialization.kt):
- ContentNegotiation explizit mit charset=UTF-8 konfiguriert, damit
  Response-Header immer 'application/json; charset=utf-8' enthält

App (ChatScreen.kt):
- Emoji-Button (Face-Icon) zur MessageInputBar hinzugefügt, der bei Klick
  den Fokus auf das Eingabefeld setzt und die Soft-Keyboard öffnet (System-
  IME mit Emoji-Panel-Zugang)
- FocusRequester + LocalSoftwareKeyboardController integriert

Tests:
- Utf8MessagingTest (Server): 6 Tests für Umlaute, Emojis, Multi-Codepoint-
  Emojis, gemischte Nachrichten, Konversationsabruf, charset-Header
- ChatViewModelTest (App): 4 neue Tests für Umlaut-, Emoji- und gemischte
  Nachrichten
- run-integration-tests.ps1: Szenario 6a mit 5 Testfällen (Umlaute, Emojis,
  gemischt, Konversation, WebSocket-Delivery mit UTF-8)
2026-05-17 01:58:27 +02:00
Jens Reinemann
95e262d009 chore: Backlog-Status in Board-Skripten ergaenzen
- set-board-status.ps1: 'Backlog' (4ce6ee37) als gueltigen Status erganzt
- create-next-ticket.ps1: -Status Parameter (Todo|Backlog, Default: Todo)
  Todo: Order = min - 1 (naechstes Ticket)
  Backlog: Order = max + 10 (ans Ende gestellt)
  Explizite Status-Setzung via gh project item-edit nach Board-Add
2026-05-17 01:51:18 +02:00
Jens Reinemann
d354e3b37c security: Server-seitige Input-Validierung & Body-Size-Limit (#67)
- Routing.kt: Content-Length-Intercept (max 1 MB, HTTP 413)
- InventoryRoutes.kt: String-Längen (name ≤255, unit ≤50, id ≤36,
  category/location ≤255, setting key ≤255), expiryDate-Regex
  YYYY-MM-DD, Array-Limits (items ≤10000, categories/locations ≤500)
- AdminRoutes.kt: username-Länge ≤255
- InputValidationTest.kt: 16 neue negative Tests, alle 532 Tests grün
2026-05-17 01:20:49 +02:00
Jens Reinemann
26b50eea36 test: Szenario 6 Bob-Generaltest in run-integration-tests.ps1
- Haupt-Szenario: Login, PUT 10 Items (3 Kat, 2 Lagerorte), PATCH qty,
  PUT 11 Items (Salzcracker), WS inventoryUpdated, WS fullSyncRequired
- T1: PATCH unbekannte ID -> 404
- T2: PUT leer -> 0 Items
- T3: Zwei PUTs -> zweites ueberschreibt
- T4: Bob patcht selbst -> WS-Event
- T5: Bob/Alice getrennte Inventare
- T6: Ungueltiger Token -> 401
- T7: Zwei WS-Sessions -> beide empfangen Events
- T8: PATCH nach Disconnect -> kein Server-Fehler
- Fix: Receive-WsMessages mit return ,\ (PS-Array-Unwrapping)
- Fix: @()-Wrapping fuer Where-Object-Filter (1-Element-Ergebnis)
Ergebnis: 30/30 Tests bestanden
2026-05-17 01:01:29 +02:00
Jens Reinemann
2555a942ec fix: Server-App WebSocket-Kompatibilität und DELETE-Route
- WebSocketManager: inventory_updated -> inventoryUpdated,
  full_sync_required -> fullSyncRequired (camelCase wie App erwartet)
- InventoryRoutes: DELETE /api/inventory/items/{id} hinzugefügt
- InventoryRepository: deleteItem(inventoryId, itemId) implementiert
2026-05-17 00:42:03 +02:00
Jens Reinemann
2d4ebd63b0 fix(server): Code-Review-Korrekturen Inventory Sharing
- UserRow: inventoryId-Feld ergaenzt
- AdminRoutes: Delete nutzt findById() statt listAll().find{}
- AdminRoutes: PUT/POST inventory/new geben 404 wenn User nicht existiert
- index.html: Doppelten alten HTML-Content entfernt (277 Zeilen)
2026-05-17 00:35:15 +02:00
Jens Reinemann
c03475e7e5 feat(server): Inventory Sharing – User:Inventory N:1
- Inventories-Tabelle neu: id, created_at
- Users.inventory_id FK → Inventories
- Items/Categories/Locations/Settings: user_id → inventory_id
- DatabaseFactory: Schema-Migration + Data-Migration (user_id→inventory_id)
- InventoryRepository: getEffectiveInventoryId(), createInventory(),
  assignUserToInventory(), cleanupOrphanedInventory(),
  listInventoriesWithUsers(); alle Ops auf inventoryId umgestellt
- UserRepository.create(): legt automatisch neues Inventory an
- InventoryRoutes: löst inventoryId via getEffectiveInventoryId()
- AdminRoutes: PUT /users/{id}/inventory (zuweisen),
  POST /users/{id}/inventory/new (trennen), GET /admin/inventories
- Admin-UI: Inventar-Spalte, 'Inventar wechseln'-Modal, 'Neues
  Inventar'-Button, Inventar-Übersicht (gruppiert)
- InventorySharingTest: 8 neue Integrationstests (Sharing, Isolation,
  Cleanup, Berechtigungen)
- Alle 48 Server-Tests gruen (inkl. bestehende Tests unveraendert)
2026-05-17 00:30:06 +02:00
Jens Reinemann
8576fffdb7 style: run-integration-tests.ps1 Formatierung angleichen 2026-05-17 00:12:19 +02:00
Jens Reinemann
6bed1214c5 test: Integration-Test-Suite fuer Server (Auth, Sync, Messaging, WS)
run-integration-tests.ps1:
- Szenario 1: Auth-Flow (Alice + Bob Login, Token-Validierung)
- Szenario 2: Inventory Sync (PUT, GET, PATCH/items/{id})
- Szenario 3: Messaging + Offline-Delivery (WS-Push nach Reconnect)
- Szenario 4: JWT Refresh (neuer Access-Token bleibt gueltig)
- Szenario 5: Parallele Sessions (Server erkennt Online-Status korrekt)
Alle 15 Tests bestanden gegen VPS-Server (195.246.231.210:8080)
Closes #60
2026-05-17 00:08:35 +02:00