70 lines
4.6 KiB
Markdown
70 lines
4.6 KiB
Markdown
# Van WebDAV naar REST API: een volwassen sync-architectuur voor de weegschaal-app
|
|
|
|
*20 juli 2026*
|
|
|
|
De afgelopen dagen heb ik de synchronisatie van mijn weegschaal-app volledig herzien. De app slaat metingen lokaal op in SQLite en synchroniseert ze met een centrale server. Tot nu toe gebeurde dat via WebDAV naar Nextcloud — een simpel JSON-bestand dat telkens in zijn geheel werd gedownload, samengevoegd en teruggeüpload. Functioneel, maar niet ideaal.
|
|
|
|
## Waarom WebDAV niet voldeed
|
|
|
|
Het oude sync-mechanisme was primitief: bij elke sync werd het volledige `measurements.json` bestand van de WebDAV-server gehaald, lokaal gemerged op basis van `updatedAt`, en daarna in zijn geheel teruggeschreven. Met 1300+ metingen werd dat bestand steeds groter, en elke sync — ook als er maar één meting was veranderd — verstuurde het hele bestand over de lijn. Bovendien leverde Basic Auth via WebDAV geen fijnmazige controle over wie toegang had, en was er geen logging van foutieve inlogpogingen.
|
|
|
|
## De nieuwe architectuur
|
|
|
|
De oplossing is een eigen REST API, gehost op een subdomein van mijn domein, met een MariaDB-backend en Bearer token-authenticatie. De stack:
|
|
|
|
- **Server**: Fedora Linux, nginx, PHP 8.5 (Slim Framework 4), MariaDB
|
|
- **Client**: React Native (Expo), expo-sqlite, expo-secure-store
|
|
- **Authenticatie**: Bearer token, handmatig aangemaakt via SQL
|
|
- **Sync**: incrementeel via `?since=` timestamps — last-writer-wins
|
|
|
|
### Endpoints
|
|
|
|
De API kent drie endpoints, allemaal beschermd met Bearer token-validatie:
|
|
|
|
| Methode | Pad | Werking |
|
|
|---------|-----|---------|
|
|
| `GET` | `/api/v1/measurements?since=ISO` | Delta-sync: alleen metingen met `updated_at > since` |
|
|
| `POST` | `/api/v1/measurements` | Upsert op client-ID — de server accepteert het client-gegenereerde UUID |
|
|
| `DELETE` | `/api/v1/measurements/:id` | Soft delete: zet `deleted=1` en update `updated_at` |
|
|
|
|
Geen PUT, geen register/token-endpoints. Minimaal en doelgericht.
|
|
|
|
### Incrementele sync
|
|
|
|
De sync-flow is nu gebaseerd op timestamps, zonder `syncStatus` enum. Het mechanisme is hetzelfde als wat CouchDB, PouchDB en tal van productie-apps gebruiken:
|
|
|
|
1. **Delta ophalen**: `GET ?since=lastSyncAt` → alleen gewijzigde records van de server
|
|
2. **Mergen**: last-writer-wins op `updatedAt`. Remote records met `deleted=true` overschrijven lokaal
|
|
3. **Pushen**: alle lokale records met `updatedAt > lastSyncAt` worden naar de server gestuurd
|
|
4. **Verwijderde records**: lokaal een `deletedAt` timestamp gezet, bij volgende sync via `DELETE` naar de server
|
|
5. **High-water mark**: `lastSyncAt` wordt bijgewerkt naar de huidige tijd
|
|
|
|
Bij een netwerkfout blijft een meting gewoon lokaal staan — de volgende sync pakt hem automatisch op omdat `updatedAt > lastSyncAt`.
|
|
|
|
## Fail2ban-vriendelijke logging
|
|
|
|
Een API die op internet staat trekt vroeg of laat ongenode gasten aan. Om hierop voorbereid te zijn logt de API elke mislukte authenticatiepoging naar `/var/log/weegschaal-api/auth.log` in een formaat dat fail2ban direct kan verwerken:
|
|
|
|
```
|
|
[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
|
|
```
|
|
|
|
De fail2ban jail grijpt in bij 5 foutieve pogingen binnen 5 minuten en bant het IP een uur lang. Ook 404's op niet-bestaande routes worden gelogd — een klassiek signaal van fuzzing-tools.
|
|
|
|
De logrotering (12 weken, wekelijks) en SELinux-context (`httpd_log_t`) zijn uiteraard geregeld.
|
|
|
|
## Resultaten delen
|
|
|
|
Een onverwacht leuke toevoeging: de share-knop op het dashboard. Rechtsboven in de kaart met je laatste en voorlaatste meting zit een deel-icoon. Tik erop, en de hele kaart wordt als PNG-afbeelding geëxporteerd. Je kunt hem direct delen via WhatsApp, Signal, e-mail — wat je maar wilt. De techniek erachter: `react-native-view-shot` vangt de view, `expo-sharing` opent het native deelmenu.
|
|
|
|
## Wat nog meer is veranderd
|
|
|
|
- **App-icoon**: vernieuwd met een eigen afbeelding
|
|
- **Dashboard**: toont naast de laatste meting nu ook de voorlaatste en het gewichtsverschil
|
|
- **Laad-indicator**: geen verwarrende "Nog geen metingen" meer tijdens het laden — het dashboard toont nu "Laden..."
|
|
- **Database-migratie**: een `ALTER TABLE` zorgt dat bestaande SQLite-databases de nieuwe `deletedAt`-kolom krijgen zonder dataverlies
|
|
|
|
## Conclusie
|
|
|
|
Wat begon als een simpel sync-scriptje via WebDAV is uitgegroeid tot een volwaardige REST API met productie-grade logging, fail2ban-integratie, en een UX die het delen van meetresultaten net zo makkelijk maakt als een screenshot. De app doet nu wat hij moet doen: metingen opslaan, synchroniseren, en — sinds kort — met één tik delen met wie je maar wilt.
|