docs: uitgebreide README met alle projectaspecten
This commit is contained in:
parent
491ea1ff16
commit
55572a1455
1 changed files with 131 additions and 20 deletions
151
README.md
151
README.md
|
|
@ -1,35 +1,121 @@
|
||||||
# Weegschaal App
|
# Weegschaal App
|
||||||
|
|
||||||
*Gemaakt met vibecoding — DeepSeek V4 Pro*
|
_*Gemaakt met vibecoding — DeepSeek V4 Pro*_
|
||||||
|
|
||||||
React Native (Expo) app voor het handmatig invoeren van meetgegevens van een slimme weegschaal. Data wordt lokaal opgeslagen in SQLite en gesynchroniseerd via een eigen REST API.
|
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.
|
||||||
|
|
||||||
## Meetwaarden
|
## Inhoud
|
||||||
|
|
||||||
- Gewicht (kg)
|
- [Functionaliteit](#functionaliteit)
|
||||||
- BMI
|
- [Schermen](#schermen)
|
||||||
- Vetpercentage (%)
|
- [Synchronisatie](#synchronisatie)
|
||||||
- Vetmassa (kg)
|
- [Server-side API](#server-side-api)
|
||||||
- Spierpercentage (%)
|
- [Beveiliging](#beveiliging)
|
||||||
- Spiermassa (kg)
|
- [Offline resilience](#offline-resilience)
|
||||||
- Buikvet (index)
|
- [Ontwikkeling](#ontwikkeling)
|
||||||
- Stofwisseling in rust (kcal)
|
- [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
|
## Schermen
|
||||||
|
|
||||||
- **Dashboard** — laatste meting, voorlaatste meting met gewichtsverschil, deel-knop om de kaart als PNG te delen
|
| Scherm | Beschrijving |
|
||||||
- **Nieuwe meting / Meting wijzigen** — formulier met datumkiezer, vooraf ingevuld met waarden van de laatste meting
|
|--------|-------------|
|
||||||
- **Meting detail** — alle meetwaarden met knoppen voor wijzigen en verwijderen
|
| Dashboard | Laatste meting, voorlaatste meting met gewichtsverschil, grafiek, deel-knop |
|
||||||
- **Historie** — FlatList gesorteerd op datum (nieuwste bovenaan), pull-to-refresh
|
| Nieuwe meting / Wijzigen | Formulier met datumkiezer, waarden vooraf ingevuld vanaf laatste meting |
|
||||||
- **Instellingen** — REST API URL en token, handmatige sync
|
| 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
|
## Synchronisatie
|
||||||
|
|
||||||
De app sync via een eigen REST API (`weegschaal.eddydevink.nl`) met Bearer token-authenticatie. Incrementele delta-sync op basis van `updatedAt` timestamps — alleen gewijzigde metingen worden uitgewisseld.
|
De app synchroniseert via een zelfgebouwde REST API. Geen WebDAV, geen bestandssync — pure delta-sync.
|
||||||
|
|
||||||
Vul in het Instellingen scherm in:
|
### Hoe het werkt
|
||||||
- **API URL**: `https://weegschaal.eddydevink.nl`
|
|
||||||
- **Token**: je persoonlijke Bearer token
|
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
|
## Ontwikkeling
|
||||||
|
|
||||||
|
|
@ -38,12 +124,37 @@ npm install
|
||||||
npx expo start
|
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
|
## Testen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Tests richten zich op de merge-logica (`mergeMeasurements`) en database-operaties.
|
||||||
|
|
||||||
## Licentie
|
## Licentie
|
||||||
|
|
||||||
Public domain (Unlicense)
|
Public domain (Unlicense)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue