- setDeletedAt update nu ook updatedAt (bug 2) - getAllMeasurements filtert deletedAt IS NULL, nieuwe getAllMeasurementsForSync voor sync - mergeMeasurements detecteert server deleted:true → deletedAt (bug 1) - syncAll: pull-push split met remoteUpdatedMap, geen double-push (bug 3) - hard delete alleen na API-succes (bug 4) - DashboardScreen: refreshCache() na syncAll zodat UI niet op Laden blijft hangen - deleteMeasurement() dead code verwijderd (bug 5) - tests: 3 nieuwe testcases voor deleted → deletedAt mapping, 15/15 pass
5.5 KiB
5.5 KiB
Sync refactor — Implementation Plan ✅ (completed 2026-07-25)
Fix 6 sync bugs + herstel architectuur naar best-practice bidirectional timestamp sync.
Status: Alle 6 bugs gefixt. 15/15 tests passen. APK build + geinstalleerd.
Bug Overzicht
| # | Bug | Oorzaak | Fix |
|---|---|---|---|
| 1 | Server deleted:true wordt genegeerd |
mergeMeasurements mapt deleted niet naar deletedAt |
Detecteer (r as any).deleted in merge, zet deletedAt = r.updatedAt |
| 2 | setDeletedAt update updatedAt niet |
Alleen deletedAt wordt gezet, updatedAt blijft oud |
Voeg updatedAt = ? toe aan de UPDATE |
| 3 | Pull-then-push double-push | Na upsert van remote records hebben die updatedAt > lastSyncAt → worden teruggestuurd |
Skip in push-fase records die uit remote pull komen en waar remote.updatedAt ≥ local.updatedAt |
| 4 | Hard delete ondanks falende API | removeMeasurement() wordt ALTIJD aangeroepen, ook na catch |
Alleen hard delete bij API-succes |
| 5 | syncStatus dead column |
In schema, nooit gebruikt | Laat staan, geen impact |
| 6 | getAllMeasurements filtert deletedAt niet |
UI-schermen moeten zelf filteren (.filter(m => !m.deletedAt)) |
Rename naar getAllMeasurementsForSync (ongefilterd), vervang UI-calls door getAllMeasurements met WHERE deletedAt IS NULL |
Sync Protocol (vastgelegd)
lastSyncAt = SecureStore.getItem(LAST_SYNC_KEY)
── PULL ──
remote[] = GET /api/v1/measurements?since=lastSyncAt
local[] = getAllMeasurementsForSync() // ALLE records (incl. deleted)
remoteUpdatedMap: Map<id, updatedAt> // track welke IDs + timestamps we binnenhaalden
for each r in remote:
remoteUpdatedMap.set(r.id, r.updatedAt)
if (r as any).deleted:
r.deletedAt = r.updatedAt // Bug 1 fix
── MERGE ──
remoteDeletedIds: Set<id> // track welke remote records 'deleted' zijn
for each r in remote:
if (r as any).deleted:
remoteDeletedIds.add(r.id)
merged = mergeMeasurements(local, remote)
for each m in merged:
remoteUA = remoteUpdatedMap.get(m.id)
if remoteUA && new Date(remoteUA) >= new Date(m.updatedAt):
// remote won de merge
if remoteDeletedIds.has(m.id):
removeMeasurement(m.id) // hard-delete lokaal (server heeft 'm al)
else:
upsertMeasurement(m) // remote gewijzigd, niet verwijderd
else:
upsertMeasurement(m) // local won of nieuw → gewoon bewaren
── PUSH ──
cutoff = lastSyncAt ?? '1970-01-01T00:00:00.000Z'
localChanges[] = getMeasurementsUpdatedSince(cutoff)
for each m in localChanges:
remoteUA = remoteUpdatedMap.get(m.id)
if remoteUA && new Date(remoteUA) >= new Date(m.updatedAt):
continue // remote won merge → niet terugpushen (Bug 3 fix)
if m.deletedAt:
DELETE /api/v1/measurements/:id
if success:
removeMeasurement(m.id) // Bug 4 fix: alleen hard-delete bij API-succes
else:
POST /api/v1/measurements
SecureStore.setItem(LAST_SYNC_KEY, now())
Delete Protocol (vastgelegd)
Lokaal: setDeletedAt(id) + updatedAt = now() (Bug 2 fix)
Cache: removeFromCache(id)
API: DELETE /api/v1/measurements/:id
if success:
Lokaal: removeMeasurement(id) (Bug 4 fix: alleen bij succes)
// bij falen: blijft met deletedAt in DB → syncAll pakt op
Files om te wijzigen
1. src/sync/syncService.ts — REWRITE syncAll + mergeMeasurements
Wijzigingen:
mergeMeasurements: detecteer(r as any).deleted→ zetdeletedAt = r.updatedAtsyncAll:- Track
remoteUpdatedMapin pull-fase - Merge via bestaande
mergeMeasurements - Push: skip records waar remote.updatedAt ≥ local.updatedAt (remote won)
- Alleen
removeMeasurementna succesvolle API DELETE
- Track
- Exporteer
cleanupDeletedRecord(id)voor gebruik door screens (DRY)
2. src/database/db.ts — setDeletedAt fix + query filters
Wijzigingen:
setDeletedAt(id): voegupdatedAt = ?toe aan SQLgetAllMeasurements(): voegWHERE deletedAt IS NULLtoegetLatestMeasurement(): voegWHERE deletedAt IS NULLtoe- Nieuw:
getAllMeasurementsForSync()— ongefilterd (voor syncService) - Verwijder:
deleteMeasurement()(dode code, duplicate vanremoveMeasurement)
3. src/screens/HistoryScreen.tsx — delete fix
Wijzigingen:
confirmDelete: alleenremoveMeasurement()na API-succesload: gebruikgetMeasurementsSince('1970-01-01')i.p.v.getAllMeasurements()(of de nieuwegetAllMeasurementsdie filtert)
4. src/screens/MeasurementDetailScreen.tsx — delete fix
Wijzigingen:
handleDelete: alleenremoveMeasurement()na API-succes
5. src/store/measurementStore.ts — geen wijzigingen nodig
De store gebruikt getLatestMeasurements(count) en getMeasurementsSince(date) die al filteren. /loadFromDb gebruikt getAllMeasurements() → die gaat nu ook filteren. ✓
6. tests/syncService.test.ts — nieuwe test cases
Wijzigingen:
- Test:
deletedvan server wordt correct naardeletedAtgemapt - Test: syncAll push skips remote-won records
- Test: syncAll push stuurt local-won records wel
7. tests/database.test.ts — update voor nieuwe functies
Wijzigingen:
- Test
setDeletedAtupdate ookupdatedAt - Test
getAllMeasurementsForSyncreturned deleted records
Uitvoeringsvolgorde
db.ts— setDeletedAt fix + query filterssyncService.ts— merge fix + syncAll refactorHistoryScreen.tsx+MeasurementDetailScreen.tsx— delete fix- Tests updaten + TypeScript check +
npm test - Build APK, testen