weegschaal-app/src/sync/syncService.ts
eddy ed714af8d1 feat: add MeasurementDetailScreen, 365-day charts, and edit support
- New MeasurementDetailScreen with wijzigen/verwijderen
- WeightChart now supports 30 and 365 days
- NewMeasurementScreen now handles both create and edit
- Updated README to match current code
- Add build-toolchain documentation
- Ignore *.apk build artifacts
2026-07-14 09:14:59 +02:00

111 lines
3 KiB
TypeScript

import { Measurement } from '../types'
import { initDatabase, getAllMeasurements, upsertMeasurement } from '../database/db'
import { getWebDAVConfig } from '../screens/SettingsScreen'
const WEBDAV_PATH = 'weegschaal/measurements.json'
type SyncResult = { local: number; remote: number; merged: number }
export function mergeMeasurements(local: Measurement[], remote: Measurement[], excludeIds?: Set<string>): Measurement[] {
const map = new Map<string, Measurement>()
for (const m of local) {
if (excludeIds?.has(m.id)) continue
map.set(m.id, m)
}
for (const m of remote) {
if (excludeIds?.has(m.id)) continue
const existing = map.get(m.id)
if (!existing || new Date(m.updatedAt) > new Date(existing.updatedAt)) {
map.set(m.id, { ...m, syncStatus: 'synced' })
}
}
return Array.from(map.values())
}
async function fetchRemote(config: { url: string; username: string; password: string }): Promise<Measurement[]> {
const url = `${config.url.replace(/\/+$/, '')}/${WEBDAV_PATH}`
const response = await fetch(url, {
headers: {
Authorization: 'Basic ' + btoa(`${config.username}:${config.password}`),
},
})
if (response.status === 404) {
return []
}
if (!response.ok) {
throw new Error(`WebDAV fetch failed: ${response.status}`)
}
return response.json()
}
async function createDir(config: { url: string; username: string; password: string }): Promise<void> {
const base = config.url.replace(/\/+$/, '')
const dirUrl = `${base}/weegschaal`
const response = await fetch(dirUrl, {
method: 'MKCOL',
headers: {
Authorization: 'Basic ' + btoa(`${config.username}:${config.password}`),
},
})
if (response.status !== 201 && response.status !== 405) {
throw new Error(`WebDAV directory create failed: ${response.status}`)
}
}
async function uploadRemote(
config: { url: string; username: string; password: string },
measurements: Measurement[]
): Promise<void> {
const base = config.url.replace(/\/+$/, '')
const url = `${base}/${WEBDAV_PATH}`
const body = JSON.stringify(measurements)
await createDir(config)
const response = await fetch(url, {
method: 'PUT',
headers: {
Authorization: 'Basic ' + btoa(`${config.username}:${config.password}`),
'Content-Type': 'application/json',
},
body,
})
if (!response.ok) {
throw new Error(`WebDAV upload failed: ${response.status}`)
}
}
export async function syncAll(excludeIds?: Set<string>): Promise<SyncResult> {
const config = await getWebDAVConfig()
if (!config) {
throw new Error('Geen WebDAV configuratie. Stel deze eerst in in Instellingen.')
}
await initDatabase()
const local = await getAllMeasurements()
const remote = await fetchRemote(config)
const merged = mergeMeasurements(local, remote, excludeIds)
for (const m of merged) {
await upsertMeasurement(m)
}
await uploadRemote(config, merged)
return {
local: local.length,
remote: remote.length,
merged: merged.length,
}
}