weegschaal-app/README.md

160 lines
5 KiB
Markdown

# Weegschaal App
_*Gemaakt met vibecoding — DeepSeek V4 Pro*_
React Native (Expo) app voor het invoeren en synchroniseren van lichaamsmetingen (weegschaal). Data wordt lokaal opgeslagen in SQLite en gesynchroniseerd via een eigen REST API met timestamp-based delta-sync.
## Inhoud
- [Functionaliteit](#functionaliteit)
- [Schermen](#schermen)
- [Synchronisatie](#synchronisatie)
- [Server-side API](#server-side-api)
- [Beveiliging](#beveiliging)
- [Offline resilience](#offline-resilience)
- [Ontwikkeling](#ontwikkeling)
- [Build & deploy](#build--deploy)
- [Testen](#testen)
- [Licentie](#licentie)
## Functionaliteit
- **Metingen invoeren** van een slimme weegschaal: gewicht, BMI, vetpercentage, vetmassa, spierpercentage, spiermassa, buikvet en stofwisseling
- **Dashboard** met laatste en voorlaatste meting, gewichtsverschil, en grafiek
- **Delen** van meetresultaten als PNG-afbeelding via WhatsApp, e-mail, etc.
- **Historie** van alle metingen, gesorteerd op datum
- **Offline-first**: metingen worden lokaal opgeslagen en later automatisch gesynchroniseerd
- **Instellingen** voor API-configuratie en handmatige sync
## Schermen
| Scherm | Beschrijving |
|--------|-------------|
| Dashboard | Laatste meting, voorlaatste meting met gewichtsverschil, grafiek, deel-knop |
| Nieuwe meting / Wijzigen | Formulier met datumkiezer, waarden vooraf ingevuld vanaf laatste meting |
| Meting detail | Alle meetwaarden met knoppen voor wijzigen en verwijderen |
| Historie | FlatList van alle metingen, pull-to-refresh, tik voor detail |
| Instellingen | API URL + token, opslaan/wissen, handmatige sync |
## Synchronisatie
De app synchroniseert via een zelfgebouwde REST API. Geen WebDAV, geen bestandssync — pure delta-sync.
### Hoe het werkt
1. **Delta ophalen**: `GET /api/v1/measurements?since=lastSyncAt` — alleen gewijzigde records van de server
2. **Mergen**: last-writer-wins op `updatedAt`
3. **Pushen**: alle lokale records met `updatedAt > lastSyncAt` → POST naar API
4. **Verwijderen**: soft delete via `deletedAt` timestamp → DELETE naar API bij volgende sync
5. **High-water mark**: `lastSyncAt` bijgewerkt na elke succesvolle sync
### Waarom timestamp-based?
Geen `syncStatus` enum, geen speciale gevallen. Hetzelfde patroon dat CouchDB en PouchDB gebruiken:
- `updatedAt` op elke wijziging (client-tijd)
- `deletedAt` voor soft deletes
- `lastSyncAt` (opgeslagen in SecureStore) voor delta-tracking
- Merge: last-writer-wins, zonder uitzonderingen
### Offline resilience
Bij netwerkfout tijdens opslaan blijft de meting lokaal staan. De volgende sync (`updatedAt > lastSyncAt`) pikt hem automatisch op. Geen handmatige tussenkomst nodig.
## Server-side API
**Stack**: nginx, PHP 8.5 (Slim Framework 4), MariaDB, Let's Encrypt SSL, SELinux
**Endpoints**:
| Methode | Pad | Auth | Beschrijving |
|---------|-----|------|-------------|
| `GET` | `/api/v1/measurements?since=ISO` | Bearer | Delta-sync |
| `POST` | `/api/v1/measurements` | Bearer | Upsert (INSERT ON DUPLICATE KEY) |
| `DELETE` | `/api/v1/measurements/:id` | Bearer | Soft delete (`deleted=1`, update `updated_at`) |
**Database**: MariaDB met drie tabellen — `users`, `auth_tokens`, `measurements`. De `measurements` tabel gebruikt `ON DUPLICATE KEY UPDATE` voor conflictvrije upserts.
**Bestandsstructuur**:
```
/usr/share/nginx/html/weegschaal-api/
├── composer.json
├── config.php
├── public/
│ └── index.php
└── src/
├── Auth.php ← Bearer token validatie middleware
├── Database.php ← PDO singleton
├── Measurements.php ← CRUD + sync queries
└── helpers.php ← JSON response, datetime conversie, logging
```
## Beveiliging
### Authenticatie
Bearer token-validatie via `auth_tokens` tabel. Tokens worden handmatig aangemaakt via SQL.
### Logging
Elke mislukte authenticatie en 404 wordt gelogd naar `/var/log/weegschaal-api/auth.log`:
```
[2026-07-20 18:06:07] 401 94.157.121.179 GET /api/v1/measurements - invalid Bearer token
[2026-07-20 18:06:07] 404 94.157.121.179 GET /.env - unknown route
```
### Fail2ban
Jail `weegschaal-api`: 5 foutieve pogingen binnen 5 minuten = 1 uur IP-ban. Filter regex:
```
^\[.*\] 401 <HOST> .* - .*$
```
Logrotering wekelijks, 12 weken retentie.
### SELinux
Logmap heeft context `httpd_log_t` gezet via `semanage fcontext`.
## Ontwikkeling
```bash
npm install
npx expo start
```
Scannen met Expo Go op Android of iOS.
## Build & deploy
```bash
# APK bouwen (lokaal via EAS)
npx eas build --local --profile apk --platform android
# Draadloos sideloaden via ADB
adb-wireless-connect
adb install -r build-*.apk
```
Vereisten: Android SDK 36, OpenJDK 21, ADB 37+, Gradle 8.14.
## Database migratie
Bestaande SQLite-databases worden automatisch gemigreerd. `initDatabase()` voegt de `deletedAt` kolom toe via `ALTER TABLE` als deze nog niet bestaat:
```sql
ALTER TABLE measurements ADD COLUMN deletedAt TEXT
```
## Testen
```bash
npm test
```
Tests richten zich op de merge-logica (`mergeMeasurements`) en database-operaties.
## Licentie
Public domain (Unlicense)