Commit graph

29 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
504207376f docs: SSH-Public-Key fuer VPS-Zugang hinterlegen 2026-05-16 18:56:07 +02:00
Jens Reinemann
8280a9daf9 refactor: kcal/100g -> kcal/kg umbenennen und Mindestbestand entfernen
- ItemEntity, ItemDto: kcalPer100g -> kcalPerKg (kcal_per_kg),
  minStock-Spalte komplett entfernt
- CalculateSupplyRangeUseCase: Formel angepasst (/ 1000.0 * kcalPerKg)
- GetMinStockWarningsUseCase + MinStockWarning: gelöscht
- UI (ItemFormScreen, WarningsScreen, DashboardScreen): Mindestbestand-
  Felder und Warnungsabschnitte entfernt
- ViewModels, UiState, Repository: alle Referenzen bereinigt
- Server (Tables, InventoryRepository): Schema angepasst
- Room: fallbackToDestructiveMigration() hinzugefügt (keine Produktivdaten)
- Alle 434 Tests gruen
2026-05-16 14:19:10 +02:00
Jens Reinemann
cb576349e0 feat(server): add LAN dev-server integration & end-to-end sync tests
Start scripts (PS1 + Shell), Dockerfile, E2E sync tests, and README
documentation for Phase 2 LAN server deployment.

New files:
- start-server.ps1 / start-server.sh: one-command server startup with
  auto-build, LAN-IP detection, and configurable API key
- Dockerfile: multi-stage build (Gradle → JRE Alpine) for container
  deployment with volume mount for persistent data
- .dockerignore: excludes app/, .git, build artifacts from Docker context
- EndToEndSyncTest.kt: 7 E2E tests covering full push/pull sync cycle,
  multi-client overwrite, empty DB pull, multiple round-trips, and auth
  rejection for unauthenticated requests
- README.md: project overview, build instructions, and complete Phase 2
  server setup docs (4 start options, LAN setup, API reference, security)

Changed files:
- AndroidManifest.xml: added usesCleartextTraffic=true for HTTP in LAN

Closes #46
2026-05-14 21:45:33 +02:00
Jens Reinemann
cb9bd2bdf4 feat(server): add API-Key authentication for REST endpoints
server/plugins/Authentication.kt:
- Custom Ktor AuthenticationProvider supporting both X-API-Key header
  and Authorization: Bearer <key> for API-Key validation
- ApiKeyPrincipal data class implementing Principal interface
- 401 Unauthorized with ErrorResponse body for missing/invalid keys

server/plugins/Routing.kt:
- Inventory routes wrapped in authenticate(api-key) block
- Health endpoint remains public (no auth required)

server/src/main/resources/application.conf:
- API key configurable via krisenvorrat.apiKey property
- Environment variable override via KRISENVORRAT_API_KEY

server/tests:
- 7 new AuthenticationTest cases (valid bearer, valid X-API-Key,
  missing key, invalid bearer, invalid X-API-Key, PUT without key,
  health without key)
- All existing ApplicationTest cases updated with bearer auth header

Closes #43
2026-05-14 20:50:16 +02:00
Jens Reinemann
5974144621 feat(server): add REST-API endpoints for inventory sync & CRUD
server/src/main/kotlin/.../routes/InventoryRoutes.kt:
- GET /api/inventory: returns full inventory as JSON
- PUT /api/inventory: full-sync replaces entire server inventory

server/src/main/kotlin/.../plugins/StatusPages.kt:
- Structured error handling via Ktor StatusPages plugin
- BadRequestException, SerializationException, IllegalArgumentException → 400
- Unhandled exceptions → 500 with logging

server/src/main/kotlin/.../plugins/CallLogging.kt:
- Request logging via Ktor CallLogging plugin (INFO level)

server/src/main/kotlin/.../model/ErrorResponse.kt:
- Serializable error response DTO (status + message)

server/src/main/kotlin/.../plugins/Routing.kt:
- Health endpoint moved from /health to /api/health
- Inventory routes mounted under /api/inventory

server/src/main/kotlin/.../Application.kt:
- Added configurePlugins() for testability (DB init separate)
- StatusPages and CallLogging plugins configured

server/src/test/.../ApplicationTest.kt:
- 8 endpoint tests using Ktor TestApplication with in-memory H2
- Tests: health, 404, empty GET, PUT valid, PUT+GET roundtrip,
  invalid JSON → 400, data replacement, JSON content type

Closes #42
2026-05-14 20:30:34 +02:00
Jens Reinemann
2387c6ee5a feat(server): add Exposed ORM database layer with H2
server/db/Tables.kt:
- Exposed table definitions for Categories, Locations, Items, Settings
- Schema mirrors Room entities from app module
- Foreign keys on Items referencing Categories and Locations

server/db/DatabaseFactory.kt:
- H2 file-based DB initialization (jdbc:h2:file:./data/krisenvorrat)
- Parameterized for testability (in-memory DB for tests)
- Schema auto-creation via SchemaUtils.create()

server/repository/InventoryRepository.kt:
- Full CRUD: saveInventory() and loadInventory()
- Atomic replace via transaction (deleteAll + insert)
- Direct mapping between Exposed rows and shared DTOs

4 repository tests with H2 in-memory covering:
- Empty DB, full round-trip, overwrite, nullable fields

Closes #41
2026-05-14 20:15:07 +02:00
Jens Reinemann
cb190e61e9 feat(server): add Ktor server module with health endpoint
New Gradle module :server (Kotlin/JVM) with Ktor 3.1.2 framework,
configured as an embedded Netty HTTP server.

server/src/main/kotlin/de/krisenvorrat/server/:
- Application.kt: entry point using EngineMain for HOCON config
- plugins/Routing.kt: GET /health endpoint returning 200 OK
- plugins/Serialization.kt: ContentNegotiation with kotlinx.json

Configuration:
- application.conf (HOCON): host 0.0.0.0, port 8080, module reference
- logback.xml: SLF4J/Logback console logging

Build config:
- server/build.gradle.kts: Ktor plugin with Fat JAR (server.jar)
- libs.versions.toml: Ktor 3.1.2, Logback 1.5.18 dependencies
- settings.gradle.kts: include(:server)
- :server depends on :shared for common DTO models

Tests: 2 tests (health endpoint, 404 on unknown route) via
Ktor testApplication.

Closes #40
2026-05-14 20:06:40 +02:00