db: fix insertMeasurement/upsertMeasurement — remove syncStatus param
This commit is contained in:
parent
79c91eb537
commit
4b7b7e3d5c
4 changed files with 1697 additions and 6 deletions
1285
docs/superpowers/plans/2026-07-20-rest-api-sync-plan.md
Normal file
1285
docs/superpowers/plans/2026-07-20-rest-api-sync-plan.md
Normal file
File diff suppressed because it is too large
Load diff
266
docs/superpowers/specs/2026-07-20-rest-api-sync-design.md
Normal file
266
docs/superpowers/specs/2026-07-20-rest-api-sync-design.md
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
# REST API Sync — Design Doc
|
||||||
|
|
||||||
|
Vervang WebDAV-sync door een eigen REST API op `weegschaal.eddydevink.nl`,
|
||||||
|
met MariaDB-backend en Bearer token-authenticatie. Incrementele sync i.p.v. volledig bestand.
|
||||||
|
|
||||||
|
## Fase 1 — Server-side: PHP API
|
||||||
|
|
||||||
|
### Infrastructuur
|
||||||
|
|
||||||
|
- **DNS**: `weegschaal.eddydevink.nl` → zelfde server als `git.eddydevink.nl` (nextcloud)
|
||||||
|
- **nginx**: server block in `/etc/nginx/conf.d/weegschaal-api.conf`, proxy naar PHP-FPM socket
|
||||||
|
- **SSL**: `certbot --nginx -d weegschaal.eddydevink.nl`
|
||||||
|
- **Document root**: `/usr/share/nginx/html/weegschaal-api/`
|
||||||
|
|
||||||
|
### MariaDB
|
||||||
|
|
||||||
|
Database `weegschaal` met gebruiker `weegschaal@localhost` (random wachtwoord).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE auth_tokens (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL REFERENCES users(id),
|
||||||
|
token VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
description VARCHAR(255),
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE measurements (
|
||||||
|
id VARCHAR(36) NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
weight DECIMAL(5,2) NOT NULL,
|
||||||
|
bmi DECIMAL(4,1) NOT NULL,
|
||||||
|
body_fat_percentage DECIMAL(4,1) NOT NULL,
|
||||||
|
fat_mass DECIMAL(5,2) NOT NULL,
|
||||||
|
muscle_percentage DECIMAL(4,1) NOT NULL,
|
||||||
|
muscle_mass DECIMAL(5,2) NOT NULL,
|
||||||
|
visceral_fat TINYINT NOT NULL,
|
||||||
|
bmr DECIMAL(6,1) NOT NULL,
|
||||||
|
measured_at DATE NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
deleted TINYINT NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, id),
|
||||||
|
INDEX idx_updated_at (user_id, updated_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
`deleted` flag voor soft deletes op server. De client stuurt harde DELETEs;
|
||||||
|
de server zet `deleted=1` en update `updated_at` zodat de volgende `?since=`
|
||||||
|
query deze doorgeeft zodat andere clients de delete kunnen verwerken.
|
||||||
|
|
||||||
|
### Endpoints (Slim Framework)
|
||||||
|
|
||||||
|
| Methode | Pad | Beschrijving |
|
||||||
|
|---------|-----|-------------|
|
||||||
|
| `GET` | `/api/v1/measurements` | Alle metingen (`?since=ISO` voor delta) |
|
||||||
|
| `POST` | `/api/v1/measurements` | Nieuwe meting of upsert op ID |
|
||||||
|
| `DELETE` | `/api/v1/measurements/:id` | Meting verwijderen (soft delete) |
|
||||||
|
|
||||||
|
**Geen** auth endpoints — eerste token wordt handmatig via SQL INSERT aangemaakt.
|
||||||
|
|
||||||
|
### Auth middleware
|
||||||
|
|
||||||
|
- `Authorization: Bearer <token>` → valideert tegen `auth_tokens` tabel
|
||||||
|
- Zet `user_id` in request context
|
||||||
|
- 401 bij ongeldige/missing token
|
||||||
|
|
||||||
|
### Bestandsstructuur
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/share/nginx/html/weegschaal-api/
|
||||||
|
├── composer.json
|
||||||
|
├── public/
|
||||||
|
│ └── index.php ← Entry point, routing
|
||||||
|
├── src/
|
||||||
|
│ ├── Database.php ← PDO singleton
|
||||||
|
│ ├── Auth.php ← Token validation middleware
|
||||||
|
│ ├── Measurements.php ← CRUD + sync queries
|
||||||
|
│ └── helpers.php
|
||||||
|
└── config.php ← DB credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync-logica server
|
||||||
|
|
||||||
|
- `GET ?since=ISO` → `WHERE user_id = ? AND updated_at > ? ORDER BY updated_at ASC`
|
||||||
|
- `POST` → `INSERT ... ON DUPLICATE KEY UPDATE` — upsert op client-ID
|
||||||
|
- `DELETE` → set `deleted=1`, update `updated_at=NOW()` (soft delete, zodat delta-sync werkt voor andere clients)
|
||||||
|
- Geretourneerde records bevatten `deleted: true` veld zodat de client weet dat dit een soft-deleted record is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 2 — Client-side: App aanpassen
|
||||||
|
|
||||||
|
### Types (`src/types.ts`)
|
||||||
|
|
||||||
|
`WebDAVConfig` en `SyncStatus` vervallen. Toegevoegd: `ApiConfig`, `deletedAt`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type Measurement = {
|
||||||
|
id: string
|
||||||
|
weight: number
|
||||||
|
bmi: number
|
||||||
|
bodyFatPercentage: number
|
||||||
|
fatMass: number
|
||||||
|
musclePercentage: number
|
||||||
|
muscleMass: number
|
||||||
|
visceralFat: number
|
||||||
|
bmr: number
|
||||||
|
measuredAt: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
deletedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiConfig = {
|
||||||
|
url: string
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SettingsScreen (`src/screens/SettingsScreen.tsx`)
|
||||||
|
|
||||||
|
- Vervang URL/username/password door API-URL + token (met oogje)
|
||||||
|
- SecureStore key: `weegschaal_api_config`
|
||||||
|
- `getApiConfig()` (exported) i.p.v. `getWebDAVConfig()`
|
||||||
|
- Sync knop blijft
|
||||||
|
|
||||||
|
### Nieuwe module `src/sync/api.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const createApi = (config: ApiConfig) => ({
|
||||||
|
getMeasurements: (since?: string) => GET /api/v1/measurements?since=...
|
||||||
|
createMeasurement: (m: Measurement) => POST /api/v1/measurements
|
||||||
|
deleteMeasurement: (id: string) => DELETE /api/v1/measurements/:id
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle calls met `Authorization: Bearer <token>` header.
|
||||||
|
|
||||||
|
### syncService.ts — volledig herschreven
|
||||||
|
|
||||||
|
#### `mergeMeasurements(local, remote)`
|
||||||
|
|
||||||
|
Simpele last-writer-wins op `updatedAt`:
|
||||||
|
|
||||||
|
1. Bouw map van alle IDs (lokaal + remote)
|
||||||
|
2. Voor elke ID: als beide bestaan → behoud de met de hoogste `updatedAt`
|
||||||
|
3. Remote metingen die winnen krijgen `deletedAt` overgenomen uit server indien de server record `deleted: true` had
|
||||||
|
|
||||||
|
#### `syncAll()`
|
||||||
|
|
||||||
|
1. Lees `lastSyncAt` uit SecureStore (`weegschaal_last_sync`)
|
||||||
|
2. `GET /api/v1/measurements?since=lastSyncAt` → remote delta
|
||||||
|
3. `getAllMeasurements()` → lokale metingen
|
||||||
|
4. Merge remote delta in lokale DB (last-writer-wins)
|
||||||
|
5. Verwijder lokale records waar remote `deleted: true` en remote `updatedAt > local updatedAt`
|
||||||
|
6. Verzamel lokale metingen met `updatedAt > lastSyncAt` (alleen niet-verwijderde)
|
||||||
|
7. Voor `deletedAt` metingen met `deletedAt > lastSyncAt` → DELETE naar API → bij succes lokaal verwijderen
|
||||||
|
8. Voor overige metingen met `updatedAt > lastSyncAt` → POST naar API
|
||||||
|
9. `lastSyncAt = now()`
|
||||||
|
|
||||||
|
#### `uploadMeasurement(m)`
|
||||||
|
|
||||||
|
Losstaande functie voor directe push na opslaan:
|
||||||
|
|
||||||
|
1. POST naar API
|
||||||
|
2. Bij succes → return
|
||||||
|
3. Bij netwerkfout → throw; meting blijft lokaal, `syncAll` pakt hem later op (want `updatedAt > lastSyncAt`)
|
||||||
|
|
||||||
|
### NewMeasurementScreen
|
||||||
|
|
||||||
|
1. Lokaal opslaan (insert of upsert, met `updatedAt = now()`)
|
||||||
|
2. Direct `uploadMeasurement(m)` proberen
|
||||||
|
3. Bij succes → navigeer terug met "Opgeslagen & gesynct"
|
||||||
|
4. Bij falen → navigeer terug met "Opgeslagen (sync mislukt)"
|
||||||
|
|
||||||
|
### MeasurementDetailScreen
|
||||||
|
|
||||||
|
Bij delete:
|
||||||
|
1. Zet `deletedAt = now()` lokaal
|
||||||
|
2. Fire-and-forget: DELETE API call
|
||||||
|
3. Bij succes → record lokaal verwijderen
|
||||||
|
4. Bij falen → record blijft met `deletedAt`, syncAll pakt het op
|
||||||
|
|
||||||
|
### HistoryScreen
|
||||||
|
|
||||||
|
Bij delete (vanuit alert in HistoryScreen):
|
||||||
|
1. Zet `deletedAt = now()` lokaal
|
||||||
|
2. Fire-and-forget: DELETE API call
|
||||||
|
3. Bij succes → record lokaal verwijderen
|
||||||
|
4. Bij falen → record blijft met `deletedAt`
|
||||||
|
|
||||||
|
Filter bij laden: `!m.deletedAt`.
|
||||||
|
|
||||||
|
### DashboardScreen
|
||||||
|
|
||||||
|
Laatste meting en grafiek: alleen metingen zonder `deletedAt`.
|
||||||
|
Laatste meting = meest recente `measuredAt` zonder `deletedAt`.
|
||||||
|
|
||||||
|
### Database (`db.ts`)
|
||||||
|
|
||||||
|
Nieuw kolom in schema: `deletedAt TEXT`.
|
||||||
|
|
||||||
|
Nieuwe functies:
|
||||||
|
- `getMeasurementsUpdatedSince(since: string)` — metingen met `updatedAt > since`
|
||||||
|
- `setDeletedAt(id: string)` — zet `deletedAt = now()`
|
||||||
|
- `removeMeasurement(id: string)` — harde delete
|
||||||
|
|
||||||
|
Verwijderd/onbruikbaar:
|
||||||
|
- `getUnsyncedMeasurements()` — niet meer nodig
|
||||||
|
- `getPendingMeasurements()` — niet meer nodig
|
||||||
|
- `markAsDeleted()` — vervangen door `setDeletedAt`
|
||||||
|
- `syncStatus` kolom — blijft in DB maar wordt niet meer gebruikt in code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 3 — Migratie (eerste sync)
|
||||||
|
|
||||||
|
Eerste keer dat de app de nieuwe API gebruikt (`lastSyncAt` is `null`):
|
||||||
|
- `GET /api/v1/measurements` (zonder `?since`) → alle remote metingen
|
||||||
|
- Merge met lokale DB: last-writer-wins, remote deleted overschrijft lokaal
|
||||||
|
- Alle lokale metingen (die niet deleted zijn) worden gepusht (`updatedAt > null` = alles)
|
||||||
|
- `lastSyncAt = now()`
|
||||||
|
- Vanaf dan: delta-sync
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Foutafhandeling
|
||||||
|
|
||||||
|
| Situatie | Gedrag |
|
||||||
|
|----------|--------|
|
||||||
|
| Geen netwerk bij opslaan | Lokaal opgeslagen, `updatedAt > lastSyncAt` dus next sync pakt op |
|
||||||
|
| Geen netwerk bij sync | Throw error, scherm toont melding |
|
||||||
|
| Geen API-config | Throw `'Geen API configuratie. Stel deze eerst in in Instellingen.'` |
|
||||||
|
| HTTP 401 (ongeldige token) | Throw `'Authenticatie mislukt. Controleer je token in Instellingen.'` |
|
||||||
|
| HTTP 5xx | Throw met statuscode, scherm toont melding |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testen
|
||||||
|
|
||||||
|
### Client-side (Jest)
|
||||||
|
|
||||||
|
- `mergeMeasurements()` unit tests: local wins, remote wins, deleted remote vs local
|
||||||
|
- `api.ts` calls mocken met fetch-mock
|
||||||
|
- `syncAll()` integratietest met gemockte API
|
||||||
|
|
||||||
|
### Server-side
|
||||||
|
|
||||||
|
- Handmatig testen met curl na deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementatievolgorde
|
||||||
|
|
||||||
|
1. Server-side: nginx, MariaDB, PHP API → testen met curl
|
||||||
|
2. Client-side: types, api.ts, db.ts, syncService.ts, SettingsScreen → testen
|
||||||
|
3. Client-side: DashboardScreen, HistoryScreen, MeasurementDetailScreen, NewMeasurementScreen
|
||||||
|
4. Client-side: unit tests
|
||||||
|
5. Build APK, sideloaden, testen op telefoon
|
||||||
|
|
@ -28,9 +28,9 @@ export async function initDatabase(): Promise<void> {
|
||||||
|
|
||||||
export async function insertMeasurement(m: Measurement): Promise<void> {
|
export async function insertMeasurement(m: Measurement): Promise<void> {
|
||||||
await db!.runAsync(
|
await db!.runAsync(
|
||||||
`INSERT INTO measurements (id, weight, bmi, bodyFatPercentage, fatMass, musclePercentage, muscleMass, visceralFat, bmr, measuredAt, createdAt, updatedAt, syncStatus, deletedAt)
|
`INSERT INTO measurements (id, weight, bmi, bodyFatPercentage, fatMass, musclePercentage, muscleMass, visceralFat, bmr, measuredAt, createdAt, updatedAt, deletedAt)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[m.id, m.weight, m.bmi, m.bodyFatPercentage, m.fatMass, m.musclePercentage, m.muscleMass, m.visceralFat, m.bmr, m.measuredAt, m.createdAt, m.updatedAt, m.syncStatus, m.deletedAt ?? null]
|
[m.id, m.weight, m.bmi, m.bodyFatPercentage, m.fatMass, m.musclePercentage, m.muscleMass, m.visceralFat, m.bmr, m.measuredAt, m.createdAt, m.updatedAt, m.deletedAt ?? null]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,9 +59,9 @@ export async function getMeasurementByDate(date: string): Promise<Measurement |
|
||||||
|
|
||||||
export async function upsertMeasurement(m: Measurement): Promise<void> {
|
export async function upsertMeasurement(m: Measurement): Promise<void> {
|
||||||
await db!.runAsync(
|
await db!.runAsync(
|
||||||
`INSERT OR REPLACE INTO measurements (id, weight, bmi, bodyFatPercentage, fatMass, musclePercentage, muscleMass, visceralFat, bmr, measuredAt, createdAt, updatedAt, syncStatus, deletedAt)
|
`INSERT OR REPLACE INTO measurements (id, weight, bmi, bodyFatPercentage, fatMass, musclePercentage, muscleMass, visceralFat, bmr, measuredAt, createdAt, updatedAt, deletedAt)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[m.id, m.weight, m.bmi, m.bodyFatPercentage, m.fatMass, m.musclePercentage, m.muscleMass, m.visceralFat, m.bmr, m.measuredAt, m.createdAt, m.updatedAt, m.syncStatus, m.deletedAt ?? null]
|
[m.id, m.weight, m.bmi, m.bodyFatPercentage, m.fatMass, m.musclePercentage, m.muscleMass, m.visceralFat, m.bmr, m.measuredAt, m.createdAt, m.updatedAt, m.deletedAt ?? null]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
140
upgrade.md
Normal file
140
upgrade.md
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Upgrade: REST API sync i.p.v. WebDAV
|
||||||
|
|
||||||
|
Vervang WebDAV-sync door een eigen REST API op `weegschaal.eddydevink.nl`,
|
||||||
|
met MariaDB-backend en Bearer token-authenticatie. Incrementele sync i.p.v. volledig bestand.
|
||||||
|
|
||||||
|
## Fase 1 — Server-side: PHP API
|
||||||
|
|
||||||
|
### Subdomein & nginx
|
||||||
|
|
||||||
|
- DNS: `weegschaal.eddydevink.nl` → zelfde server als `git.eddydevink.nl`
|
||||||
|
- nginx server block in `/etc/nginx/conf.d/weegschaal-api.conf`
|
||||||
|
- SSL via Let's Encrypt (`sudo certbot`)
|
||||||
|
- Document root: `/usr/share/nginx/html/weegschaal-api/`
|
||||||
|
|
||||||
|
### MariaDB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE weegschaal;
|
||||||
|
CREATE USER 'weegschaal'@'localhost' IDENTIFIED BY '<random>';
|
||||||
|
GRANT ALL ON weegschaal.* TO 'weegschaal'@'localhost';
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE auth_tokens (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL REFERENCES users(id),
|
||||||
|
token VARCHAR(64) NOT NULL UNIQUE,
|
||||||
|
description VARCHAR(255),
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE measurements (
|
||||||
|
id VARCHAR(36) NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
weight DECIMAL(5,2) NOT NULL,
|
||||||
|
bmi DECIMAL(4,1) NOT NULL,
|
||||||
|
body_fat_percentage DECIMAL(4,1) NOT NULL,
|
||||||
|
fat_mass DECIMAL(5,2) NOT NULL,
|
||||||
|
muscle_percentage DECIMAL(4,1) NOT NULL,
|
||||||
|
muscle_mass DECIMAL(5,2) NOT NULL,
|
||||||
|
visceral_fat TINYINT NOT NULL,
|
||||||
|
bmr DECIMAL(6,1) NOT NULL,
|
||||||
|
measured_at DATE NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, id),
|
||||||
|
INDEX idx_updated_at (user_id, updated_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP API endpoints (Slim Framework via Composer)
|
||||||
|
|
||||||
|
| Methode | Pad | Beschrijving |
|
||||||
|
|---------|-----|-------------|
|
||||||
|
| `POST` | `/api/v1/auth/register` | (later) Nieuwe gebruiker |
|
||||||
|
| `POST` | `/api/v1/auth/tokens` | (later) Nieuwe token aanmaken |
|
||||||
|
| `GET` | `/api/v1/measurements` | Alle metingen (of `?since=ISO` voor delta) |
|
||||||
|
| `POST` | `/api/v1/measurements` | Nieuwe meting aanmaken |
|
||||||
|
| `PUT` | `/api/v1/measurements/:id` | Meting bijwerken |
|
||||||
|
| `DELETE` | `/api/v1/measurements/:id` | Meting verwijderen |
|
||||||
|
|
||||||
|
Authenticatie: `Authorization: Bearer <token>`, gevalideerd tegen `auth_tokens`-tabel.
|
||||||
|
|
||||||
|
Sync-logica server:
|
||||||
|
- `GET /api/v1/measurements?since=2025-01-01T12:00:00Z` → alleen metingen met `updated_at > since`
|
||||||
|
- Bij PUT/DELETE: controleer `updated_at` client, bij conflict → HTTP 409 met server-versie
|
||||||
|
|
||||||
|
### Bestandsstructuur API
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/share/nginx/html/weegschaal-api/
|
||||||
|
├── composer.json
|
||||||
|
├── public/
|
||||||
|
│ └── index.php ← Entry point, routing
|
||||||
|
├── src/
|
||||||
|
│ ├── Database.php ← PDO singleton
|
||||||
|
│ ├── Auth.php ← Token validation middleware
|
||||||
|
│ ├── Measurements.php ← CRUD + sync queries
|
||||||
|
│ └── helpers.php
|
||||||
|
└── config.php ← DB credentials (uit .env of env vars)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fase 2 — Client-side: App aanpassen
|
||||||
|
|
||||||
|
### Types (`src/types.ts`)
|
||||||
|
|
||||||
|
- Vervang `WebDAVConfig` door `ApiConfig { url: string; token: string }`
|
||||||
|
- Verwijder `SyncStatus` (niet meer nodig — sync status volgt uit `updatedAt`)
|
||||||
|
|
||||||
|
### SettingsScreen (`src/screens/SettingsScreen.tsx`)
|
||||||
|
|
||||||
|
- Vervang URL/username/password door API-URL + token (met oogje)
|
||||||
|
- Opslaan → SecureStore
|
||||||
|
- Wissen → SecureStore verwijderen
|
||||||
|
- 'Nu synchroniseren' knop blijft
|
||||||
|
|
||||||
|
### Nieuwe module `src/sync/api.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const api = (config: ApiConfig) => ({
|
||||||
|
getMeasurements: (since?: string) => GET /api/v1/measurements?since=...
|
||||||
|
createMeasurement: (m: Measurement) => POST /api/v1/measurements
|
||||||
|
updateMeasurement: (m: Measurement) => PUT /api/v1/measurements/:id
|
||||||
|
deleteMeasurement: (id: string) => DELETE /api/v1/measurements/:id
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### syncService.ts — volledig herschreven
|
||||||
|
|
||||||
|
Nieuwe sync-flow:
|
||||||
|
|
||||||
|
| Stap | Actie |
|
||||||
|
|------|-------|
|
||||||
|
| 1 | Lees lokale `lastSyncAt` (uit SecureStore) |
|
||||||
|
| 2 | `GET ?since=lastSyncAt` → alleen wijzigingen ophalen |
|
||||||
|
| 3 | Upsert remote wijzigingen lokaal (last-writer-wins) |
|
||||||
|
| 4 | Voor elke lokale meting met `updatedAt > lastSyncAt`: push naar API |
|
||||||
|
| 5 | Update `lastSyncAt` naar server-tijd |
|
||||||
|
|
||||||
|
Bij opslaan in `NewMeasurementScreen`:
|
||||||
|
1. Direct naar lokale SQLite (offline-first)
|
||||||
|
2. Direct erna: push naar API (async, niet-blokkerend via `Promise.allSettled`)
|
||||||
|
|
||||||
|
## Fase 3 — Migratie
|
||||||
|
|
||||||
|
Eerste keer na API-setup:
|
||||||
|
1. App stuurt alle lokale metingen naar de API (1-voor-1 via POST)
|
||||||
|
2. Alle metingen nu in MariaDB
|
||||||
|
3. Vanaf dan: alleen delta-sync
|
||||||
|
|
||||||
|
## Benodigde server-toegang
|
||||||
|
|
||||||
|
- SSH naar nextcloud (`aiaiaiaiagent@nextcloud`) — key werkte niet
|
||||||
|
- `sudo -A mysql` voor DB + user aanmaken
|
||||||
|
- nginx config aanpassen (`/etc/nginx/conf.d/`)
|
||||||
|
- DNS controleren voor subdomein
|
||||||
Loading…
Reference in a new issue