From b65c7a70438d47a1ddc5ccd98442e2c8192826f4 Mon Sep 17 00:00:00 2001 From: eddy Date: Mon, 20 Jul 2026 19:50:19 +0200 Subject: [PATCH] docs: blogartikel over de REST API migratie --- docs/blog-sync-upgrade.md | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/blog-sync-upgrade.md diff --git a/docs/blog-sync-upgrade.md b/docs/blog-sync-upgrade.md new file mode 100644 index 0000000..9e0e2de --- /dev/null +++ b/docs/blog-sync-upgrade.md @@ -0,0 +1,70 @@ +# 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.