from __future__ import annotations import enum from datetime import datetime, timezone from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column def utcnow() -> datetime: return datetime.now(timezone.utc) class Base(DeclarativeBase): pass class HealthState(str, enum.Enum): HEALTHY = "healthy" SYNCING = "syncing" DEGRADED = "degraded" ACTION_REQUIRED = "action_required" DISABLED = "disabled" class ActivityStatus(str, enum.Enum): DISCOVERED = "discovered" DOWNLOADED = "downloaded" CONVERTED = "converted" IMPORTED = "imported" DUPLICATE = "duplicate" FAILED = "failed" class SyncRunStatus(str, enum.Enum): RUNNING = "running" SUCCESS = "success" PARTIAL = "partial" FAILED = "failed" class SyncUser(Base): __tablename__ = "sync_users" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(120), nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) health_state: Mapped[HealthState] = mapped_column(Enum(HealthState), nullable=False, default=HealthState.HEALTHY) mywhoosh_state: Mapped[str] = mapped_column(String(32), nullable=False, default="unknown") garmin_state: Mapped[str] = mapped_column(String(32), nullable=False, default="unknown") action_reason: Mapped[str | None] = mapped_column(Text) mywhoosh_email_enc: Mapped[str] = mapped_column(Text, nullable=False) mywhoosh_password_enc: Mapped[str] = mapped_column(Text, nullable=False) garmin_email_enc: Mapped[str] = mapped_column(Text, nullable=False) garmin_password_enc: Mapped[str] = mapped_column(Text, nullable=False) notify_email_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) notification_email: Mapped[str | None] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class Activity(Base): __tablename__ = "activities" __table_args__ = (UniqueConstraint("user_id", "mywhoosh_activity_id", name="uq_activity_user_mywhoosh"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("sync_users.id", ondelete="CASCADE"), nullable=False, index=True) mywhoosh_activity_id: Mapped[str] = mapped_column(String(255), nullable=False) activity_name: Mapped[str] = mapped_column(String(255), nullable=False) activity_timestamp: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) source_fit_path: Mapped[str | None] = mapped_column(Text) converted_fit_path: Mapped[str | None] = mapped_column(Text) status: Mapped[ActivityStatus] = mapped_column(Enum(ActivityStatus), nullable=False, default=ActivityStatus.DISCOVERED) last_completed_stage: Mapped[ActivityStatus] = mapped_column(Enum(ActivityStatus), nullable=False, default=ActivityStatus.DISCOVERED) retryable: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) garmin_activity_id: Mapped[str | None] = mapped_column(String(255)) last_error: Mapped[str | None] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class SystemLogEntry(Base): __tablename__ = "system_log_entries" id: Mapped[int] = mapped_column(Integer, primary_key=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True) source: Mapped[str] = mapped_column(String(64), nullable=False) message: Mapped[str] = mapped_column(Text, nullable=False) user_id: Mapped[int | None] = mapped_column(ForeignKey("sync_users.id", ondelete="SET NULL")) class SyncRun(Base): __tablename__ = "sync_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("sync_users.id", ondelete="CASCADE"), nullable=False, index=True) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status: Mapped[SyncRunStatus] = mapped_column(Enum(SyncRunStatus), nullable=False, default=SyncRunStatus.RUNNING) discovered_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) imported_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) skipped_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) failed_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) summary_error: Mapped[str | None] = mapped_column(Text)