Add Wahoo activity sync with provider-agnostic framework

Provider-agnostic sync framework + Wahoo as first implementation:

Framework (apps/journal/app/lib/sync/):
- SyncProvider interface for OAuth2, webhooks, import, conversion
- Provider registry for settings UI iteration
- Generic sync_connections + sync_imports tables
- Token storage with auto-refresh

Wahoo provider:
- OAuth2 with workouts_read, user_read, offline_data scopes
- Webhook-based auto-import (workout_summary events)
- Manual import page with pagination
- FIT→GPX conversion via fit-file-parser
- Webhook token verification

Routes:
- /api/sync/connect/:provider — OAuth redirect
- /api/sync/callback/:provider — OAuth callback
- /api/sync/disconnect/:provider — remove connection
- /api/sync/webhook/:provider — webhook receiver
- /sync/import/:provider — manual import page

Settings: Connected Services section with per-provider connect/disconnect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-04 11:19:15 +01:00
parent dd6543e313
commit b6711d23d6
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
25 changed files with 1124 additions and 5 deletions

View file

@ -7,9 +7,13 @@ SMTP_URL=ENC[AES256_GCM,data:o4kgOpqNnbtoAfZMM3+cnk0pHIQ/xfQ7Z2wg003qkx+7aU0JdNW
SMTP_FROM=ENC[AES256_GCM,data:pS4B7UxNc9DIyhveRV0LP3wchAFtCY1TN7NhTK9YsM92,iv:jnz4zEjU++PVC1OOyQTvr4mnDNe1hmx4oqZMLJ/7jcQ=,tag:ifclwnVpmcD8x0SLMK+ZPw==,type:str]
SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:Bz+XkGYAJDhCN5iSSPdTkd+21BO1zc7S2rU2NvxaBzmskYve6gHt1KR+OzQ9YLuy7VkhpFlBeYKS592HrpdkCA7Sp3DZvl8=,iv:E3cyWEhDX8WCpvROJdhGhdgkKjV+mhmrj4kMpBz9hWM=,tag:BNb7rsdPQQ4pBS3UX2G4EQ==,type:str]
DEPLOY_GHCR_TOKEN=ENC[AES256_GCM,data:cmyUDf24Mt5iS8rRwjpCMPfGBjKDXW4vXQjEqlO1KWNNxO0fW5SFlg==,iv:rhKAgWZ48JGfq2vS6FJ58E9pU41s6zHYe0xo7+YapoM=,tag:LKl86akqu7E32Lp0ScDWYg==,type:str]
#ENC[AES256_GCM,data:PhxKhxmL30iKEyvTTmmmVZw=,iv:dTLvkB4muDNdBT98EtQdfDnBcpBb7MiBBwDfTkLc2UI=,tag:Qa3jCS4UYHi1kQsXpFwCiw==,type:comment]
WAHOO_CLIENT_ID=ENC[AES256_GCM,data:RWyd1ocUcIeNk0vIqpb21IAJuIEc6M3g2IC8RmHeOozBpUWppOSB2ItLFg==,iv:CKKy/L9+VZSBEcPoOTEMZa6hWet0qA9kwD+byu2M3WE=,tag:3/qloZMHq3StT0KgtLgxvg==,type:str]
WAHOO_CLIENT_SECRET=ENC[AES256_GCM,data:1DGPQqjTZVoJmrgXcMav9y3NMe6+FPNvIqSYM1Q6cQDLLkrsGKzFLCYV6w==,iv:aHcjMvQaXln/5R30etnotdV1Yezpdlc2+bqpXT0ab2U=,tag:ZIzvRFecfU9XuetwCPrngQ==,type:str]
WAHOO_WEBHOOK_TOKEN=ENC[AES256_GCM,data:KQScuqVvDPMyv0rgUC0J225QH3VyL2i3R23PtiC9Ny2ceaD9,iv:jwFRtYLFqlEdbDaJ3WwD+puHMDUpv+8BWPpwnL6iDfQ=,tag:3CxS7Co3p5dfH1JQGpr8FQ==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWblo1Rm9rUlFCQkVVRUVR\nSCtPR1kwMlFvRXplNmdxWVVSOEJzUVB3VlZBCk9Hb1FJSCs1R2llMkk4eGVydnlh\nUmJmU0Joa1pneEZSWFJ5L1VZRnNiZ0UKLS0tIG9aaUkzT0o5dERSekM0cjNxOXBM\nRVhnU1prNjFOa0RQdEVsSlB5QnZzMGcKFFPBeSgQozLR2zohwr/lYBRFzeAK8Hzd\nV635q8YQrXBt9ZUSYvy/b7Gse61+ynaqI7ukLfbc/cRNszQFSuMJyg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1vukt8py0s2mm47fx6qh6vykckfkdqslf9w68ekytfzvc8xp7e3rqn6p64n
sops_lastmodified=2026-03-27T15:54:10Z
sops_mac=ENC[AES256_GCM,data:ESmrI1rbshwEKB3ttElBFD0vpbRQjydPBi5zlP5DNhc9ldBPFiIIFCA/v5MBquPsA4VG0C3RyKJBXrryPiADcvj/bhOWWllVlJ0OPr9GSoNFRpYRsWSTJfu7NG3wP9B24Cm/QpW2p2wYi+Z4nm0KHdHWTshxgUBBFOAOiVJPWIU=,iv:/IzX+/MP+3eTeePcmgfwRmVMAHe/WmRYBfrEFp7JOoA=,tag:nhNbuk8GlbJJMghvx8CnKw==,type:str]
sops_lastmodified=2026-04-04T10:17:34Z
sops_mac=ENC[AES256_GCM,data:MMghvTcbpNT2xgw70shxEVi4nj52ycBiFISCzUBHXBTLYF0K5Hnez6uJaVNPcVTuwcqNW+9MfFbqijKviUyjBVGR1U8sjthORzyLGUyaxmN9nxjBheF8QWiCouyEFsWs0CBW4YIOu+zGfzZa0U/yvN3mORGfJNA1nTgIwiUDDjQ=,iv:G1cC+NgCouTj8o5E4z8je7z+lRK/IsALiSr2M/N3jbc=,tag:LHfh2sZ94nuH+KKEXmRpxg==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.9.4