Add Garmin Connect auto-sync via python-garminconnect
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 7s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 8s

- GarminConnectConfig model stores encrypted credentials and OAuth token
- garmin_connect_sync service: token-based auth with password fallback,
  activity FIT download + queue, daily wellness from JSON API
- Celery beat schedule: sync_all_garmin_connect fires every hour
- New API router /api/garmin-sync: config CRUD, manual trigger
- Beat container added to docker-compose.yml and docker-compose.deploy.yml
- ProfilePage: Garmin Connect section with connect/update/disconnect and Sync now

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:08:12 +01:00
parent 0cdc653664
commit 6d224d51c5
9 changed files with 691 additions and 3 deletions
+20
View File
@@ -37,6 +37,26 @@ class User(Base):
health_metrics = relationship("HealthMetric", back_populates="user", cascade="all, delete-orphan")
named_routes = relationship("NamedRoute", back_populates="user", cascade="all, delete-orphan")
weight_logs = relationship("WeightLog", back_populates="user", cascade="all, delete-orphan")
garmin_connect_config = relationship("GarminConnectConfig", back_populates="user", uselist=False, cascade="all, delete-orphan")
class GarminConnectConfig(Base):
"""Per-user Garmin Connect credentials and sync state."""
__tablename__ = "garmin_connect_configs"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True, index=True)
email = Column(String(256), nullable=False)
password_enc = Column(String(512), nullable=False) # Fernet-encrypted
token_store = Column(Text, nullable=True) # garth OAuth2 token JSON
sync_enabled = Column(Boolean, default=True)
sync_activities = Column(Boolean, default=True)
sync_wellness = Column(Boolean, default=True)
last_sync_at = Column(DateTime(timezone=True), nullable=True)
last_sync_status = Column(String(512), nullable=True)
created_at = Column(DateTime(timezone=True), default=now_utc)
user = relationship("User", back_populates="garmin_connect_config")
class WeightLog(Base):