160 lines
5 KiB
Markdown
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)
|