feat(server): read visits and recorded routes from Dawarich

A new `integrations/dawarich` domain behind its own addon. Each reader
connects their own instance; the API key is encrypted at rest and never
returned. Nothing is written back to Dawarich, and no raw position is
kept in TREK: the recorded route is fetched per request for the window
being looked at and thrown away again.

What it produces are suggestions, never facts. A recorded stay becomes a
proposed place or journal entry, a country or city becomes a proposed
Atlas mark, and a stay near a wish becomes a proposed tick-off; each one
waits for the reader to confirm it. Identity is (user, Dawarich visit),
so a second sync neither duplicates nor overwrites what was accepted,
and deleting the result puts the suggestion back on the list.

The sync runs as a job on the central scheduler, one user at a time,
guarded against overlapping runs.
This commit is contained in:
Maurice
2026-09-12 23:34:54 +02:00
parent dad1316172
commit 218d8baf0a
55 changed files with 7469 additions and 29 deletions
+16
View File
@@ -530,6 +530,22 @@ async function main() {
}
}
// --- dawarich_connections: api_key ---
// Its own table rather than a users column, so it needs its own block. The
// table arrived with a later migration, hence the existence check: an older
// database simply has nothing to rotate here.
if (tableExists('dawarich_connections')) {
const connections = db
.prepare('SELECT user_id, api_key FROM dawarich_connections WHERE api_key IS NOT NULL')
.all() as { user_id: number; api_key: string }[];
for (const row of connections) {
const newVal = migrateApiKeyValue(row.api_key, `dawarich_connections[${row.user_id}].api_key`);
if (newVal !== null) {
db.prepare('UPDATE dawarich_connections SET api_key = ? WHERE user_id = ?').run(newVal, row.user_id);
}
}
}
// --- trek_photos: passphrase ---
const photos = db.prepare('SELECT id, passphrase FROM trek_photos WHERE passphrase IS NOT NULL').all() as { id: number; passphrase: string }[];
for (const row of photos) {