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
Jens Reinemann
56ac9b1425
feat: Messaging-System mit Offline-First und WebSocket-Push ( #58 )
...
## Server
- Messages-Tabelle (id, sender_id, receiver_id, body, sent_at, delivered_at)
- MessageRepository: save/getUndelivered/getConversation/markDelivered (JOIN statt N+1)
- POST /api/messages, GET /api/messages/{userId}: Nachrichten senden/abrufen
- GET /api/users: User-Liste fuer authentifizierte User (ohne eigenen Account)
- WebSocketManager: notifyNewMessage() + isOnline()
- WebSocketRoutes: unzugestellte Nachrichten bei Reconnect pushen
- LoginResponse: userId + username ergaenzt
- Server-Dependency: kotlinx.serialization fuer shared
## App
- MessageEntity + MessageDao (Room, Migration 3->4)
- KrisenvorratDatabase v4, Migrations.MIGRATION_3_4
- MessageRepositoryImpl: Offline-First (isPending), drain bei WebSocket-Connect
- WebSocketEvent.NewMessage -> MessageDto aus shared
- WebSocketClientImpl: new_message-Event parsen
- AUTH_USER_ID in SettingsKeys, SyncServiceImpl speichert userId bei Login
- UserListScreen + UserListViewModel: User-Liste anzeigen
- ChatScreen + ChatViewModel: WhatsApp-Style Chat (links/rechts, Zeitstempel)
- Navigation: Screen.UserList, Screen.Chat, MESSAGES in Bottom-Nav
- RepositoryModule: MessageRepository gebunden
## Tests
- 234 Tests, 0 Fehler
2026-05-16 23:35:25 +02:00
Jens Reinemann
1d7a62448a
feat: Offline-Queue, Sofort-Sync & Last-Write-Wins ( #61 )
...
- PendingSyncOpEntity + PendingSyncOpDao: Room-Queue fuer ausstehende PATCH/DELETE-Ops
- MIGRATION_2_3: neue Tabelle pending_sync_ops (Version 2 -> 3)
- SyncService.patchItem() + deleteItem(): PATCH/DELETE /api/inventory/items/{id}
- ItemRepositoryImpl: nach insert/update/delete sofortiger PATCH-Versuch (fire-and-forget),
bei Netzwerkfehler (Timeout/Connection/Unknown) -> Queue, AuthError/NotConfigured -> silent
- drainQueue() bei WebSocketEvent.Connected: Queue abarbeiten, korrupte Ops loeschen
- ImportExportRepositoryImpl.applyInventoryDto(): Last-Write-Wins per lastUpdated-Timestamp
- KrisenvorratDatabaseMigrationTest: V2->V3-Test ergaenzt
- 223 Unit Tests gruen
2026-05-16 21:40:10 +02:00
Jens Reinemann
4c2f5f08a4
feat(app): User-Konzept App-Phase - JWT-Auth, Login, WebSocket-Client ( #57 )
...
- SettingsKeys: API_KEY entfernt, AUTH_ACCESS_TOKEN/REFRESH_TOKEN/USERNAME hinzugefügt
- SyncService: login() und logout() Interface-Methoden
- SyncServiceImpl: Bearer-Token statt X-API-Key, Auto-Refresh bei 401
- AuthModels: LoginRequest, LoginResponse, RefreshRequest
- WebSocketClient: Interface + Impl mit exponentiellem Backoff
- SettingsViewModel: Login/Logout, WebSocket-Connect, FullSyncRequired auto-pullSync
- SettingsScreen: Login-Formular (Username + Passwort) statt API-Key-Feld
- NetworkModule: WebSocketClient als Singleton gebunden
- Alle Tests gruen (70 Tasks up-to-date)
2026-05-16 19:45:11 +02:00
Jens Reinemann
14631c7327
feat(server): User-Konzept Auth, JWT, Admin-CRUD, WebSocket-Push, Admin-UI ( #57 )
...
- Users-Tabelle mit bcrypt-Passwort-Hash
- JWT-Auth (Access + Refresh Token) ersetzt API-Key
- POST /api/auth/login + /api/auth/refresh
- Admin-Endpoints: GET/POST/DELETE/PUT /api/admin/users
- Seed-Admin beim Start (KRISENVORRAT_ADMIN_PASSWORD)
- Inventar-Endpoints user-spezifisch (userId aus JWT)
- PATCH /api/inventory/items/{id} fuer Einzel-Updates
- WebSocket /ws/sync mit Push-Events (inventoryUpdated, fullSyncRequired)
- Minimale Admin-Web-UI unter /admin/ (XSS-sicher)
- Categories/Locations: Surrogate-PK fuer User-Isolation
- Alle Server-Tests gruen (43 Tests)
2026-05-16 19:28:03 +02:00
Jens Reinemann
db72a8b4ad
ci: Android CI deaktivieren (nur noch manuell per workflow_dispatch)
2026-05-16 18:58:49 +02:00
Jens Reinemann
504207376f
docs: SSH-Public-Key fuer VPS-Zugang hinterlegen
2026-05-16 18:56:07 +02:00
Jens Reinemann
748140acbd
docs: VPS-Deploy Skill hinzufügen (1984 Hosting, Docker, SSH)
2026-05-16 18:47:50 +02:00
Jens Reinemann
809e6aa069
feat: FEATURE_CAMERA_ENABLED compile flag für KI-Kamera
...
- build.gradle.kts: buildConfigField FEATURE_CAMERA_ENABLED (default: true)
- ItemListScreen: Camera-Icon nur wenn Flag gesetzt, onCameraClick optional
- KrisenvorratNavGraph: CameraCapture-Route und Lambda nur wenn Flag gesetzt
- SettingsScreen: KI-Erkennung-Section nur wenn Flag gesetzt
2026-05-16 18:06:12 +02:00
Jens Reinemann
dd571f46fc
feat: KI-Kameraerkennung via OpenAI Vision (Issue #48 )
...
- CameraCapture-Screen: Foto aufnehmen, analysieren, Artikel auswählen
- OpenAiVisionService: gpt-4o Vision API via Ktor (buildJsonObject)
- CameraViewModel: @ApplicationContext, Bitmap-Laden, Nav-Event via UiState
- ItemFormViewModel: prefillJson-Route-Parameter, _pendingCategoryName-Matching
- Settings: OpenAI API-Key (OPENAI_API_KEY) speichern/laden
- Screen.CameraCapture + Screen.ItemForm(prefillJson) in NavGraph
- ItemListScreen: PhotoCamera-Icon in TopAppBar
- AndroidManifest: TakePicture braucht keine CAMERA-Permission (Intent-basiert)
- 8 neue CameraViewModel-Tests, 1 neuer ItemFormViewModel-Test (226 Tests grün)
2026-05-16 17:58:08 +02:00
Jens Reinemann
f4b5197b06
infra: DB-Migration-Infrastruktur einrichten ( #49 )
...
- fallbackToDestructiveMigration() entfernt (war inakzeptabel)
- addMigrations(MIGRATION_1_2) in DatabaseModule eingetragen
- Migrations.kt: Migration(1,2) mit Tabellen-Neubau fuer SQLite < 3.25
(kcal_per_100g -> kcal_per_kg, min_stock entfernt)
- exportSchema = true + KSP-Argument room.schemaLocation = app/schemas/
- 2.json Schema-Snapshot eingecheckt (Basis fuer kuenftige Migrationen)
- androidTest-Assets zeigen auf app/schemas/ (fuer MigrationTestHelper)
- KrisenvorratDatabaseMigrationTest: 4 instrumentierte Tests
- Datenerhalt nach Migration
- Korrekte Spalten nach Migration
- Indices nach Migration
- Fresh-Install ohne Migration
2026-05-16 14:52:06 +02:00
Jens Reinemann
018d8dc7da
feat: Monat/Jahr-Picker statt Tages-Datepicker fuer Ablaufdatum ( #52 )
...
- ExpiryDateField: DatePicker entfernt, neuer MonthYearPickerDialog
mit zwei Dropdowns (Monat + Jahr)
- Gespeicherter Wert: letzter Tag des gewaehlten Monats (atEndOfMonth())
- Anzeigeformat: MM/yyyy statt dd.MM.yyyy (ItemFormScreen + ItemListScreen)
- TextField ganzflaechig klickbar (nicht nur Icon)
- Dialog: OK / Entfernen / Abbrechen
- remember fuer DateTimeFormatter und Listenliterale
- Test auf Monatsende-Datum ausgerichtet
2026-05-16 14:39:39 +02:00