from sqlalchemy import ( Column, Integer, String, Float, ForeignKey, CheckConstraint, UniqueConstraint, ) from sqlalchemy.orm import relationship from database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, nullable=False, index=True) password_hash = Column(String, nullable=False) display_name = Column(String, default="") role = Column(String, default="personal") # personal | trainer created_at = Column(String) class WorkoutType(Base): __tablename__ = "workout_types" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) name = Column(String, unique=True, nullable=False) sort_order = Column(Integer, default=0) created_at = Column(String) exercises = relationship("Exercise", back_populates="workout_type") workouts = relationship("Workout", back_populates="workout_type") class Exercise(Base): __tablename__ = "exercises" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) # NULL = глобальный справочник, иначе — личное упражнение пользователя owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True) workout_type_id = Column( Integer, ForeignKey("workout_types.id", ondelete="SET NULL"), index=True, ) name = Column(String, nullable=False, index=True) created_at = Column(String) updated_at = Column(String) workout_type = relationship("WorkoutType", back_populates="exercises") workout_exercises = relationship("WorkoutExercise", back_populates="exercise") class Client(Base): __tablename__ = "clients" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True) full_name = Column(String, nullable=False) birth_date = Column(String) goal = Column(String, default="") notes = Column(String, default="") color = Column(String) is_active = Column(Integer, default=1) created_at = Column(String) updated_at = Column(String) workouts = relationship("Workout", back_populates="client") body_metrics = relationship("BodyMetric", back_populates="client") class Workout(Base): __tablename__ = "workouts" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True) client_id = Column(Integer, ForeignKey("clients.id", ondelete="SET NULL"), index=True) workout_type_id = Column( Integer, ForeignKey("workout_types.id", ondelete="SET NULL"), index=True, ) start_time = Column(String, nullable=False) end_time = Column(String) notes = Column(String, default="") synced = Column(Integer, default=0) created_at = Column(String) updated_at = Column(String) client = relationship("Client", back_populates="workouts") workout_type = relationship("WorkoutType", back_populates="workouts") workout_exercises = relationship( "WorkoutExercise", back_populates="workout", cascade="all, delete-orphan", ) cardio_sessions = relationship( "Cardio", back_populates="workout", cascade="all, delete-orphan", ) class WorkoutExercise(Base): __tablename__ = "workout_exercises" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) workout_id = Column(Integer, ForeignKey("workouts.id", ondelete="CASCADE"), index=True) exercise_id = Column(Integer, ForeignKey("exercises.id", ondelete="SET NULL"), index=True) exercise_name = Column(String, nullable=False, index=True) sort_order = Column(Integer, default=0) created_at = Column(String) workout = relationship("Workout", back_populates="workout_exercises") exercise = relationship("Exercise", back_populates="workout_exercises") sets = relationship( "Set", back_populates="workout_exercise", cascade="all, delete-orphan", ) class Set(Base): __tablename__ = "sets" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) workout_exercise_id = Column( Integer, ForeignKey("workout_exercises.id", ondelete="CASCADE"), index=True, ) is_dropset = Column(Integer, default=0) weight = Column(Float) reps = Column(Integer) rating = Column(Integer) notes = Column(String, default="") sort_order = Column(Integer, default=0) timestamp = Column(String, nullable=False) created_at = Column(String) workout_exercise = relationship("WorkoutExercise", back_populates="sets") dropset_items = relationship( "DropsetItem", back_populates="set", cascade="all, delete-orphan", ) __table_args__ = ( CheckConstraint( "(is_dropset = 0 AND weight IS NOT NULL AND reps IS NOT NULL) OR " "(is_dropset = 1 AND weight IS NULL AND reps IS NULL)", name="ck_sets_dropset_shape", ), CheckConstraint( "rating IS NULL OR (rating >= 1 AND rating <= 5)", name="ck_sets_rating", ), ) class DropsetItem(Base): __tablename__ = "dropset_items" id = Column(Integer, primary_key=True, index=True) set_id = Column(Integer, ForeignKey("sets.id", ondelete="CASCADE"), index=True) weight = Column(Float, nullable=False) reps = Column(Integer, nullable=False) sort_order = Column(Integer, default=0) set = relationship("Set", back_populates="dropset_items") __table_args__ = ( CheckConstraint("weight > 0", name="ck_dropset_weight_positive"), CheckConstraint("reps > 0", name="ck_dropset_reps_positive"), ) class Cardio(Base): __tablename__ = "cardio" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) workout_id = Column(Integer, ForeignKey("workouts.id", ondelete="CASCADE"), index=True) cardio_type = Column(String, nullable=False) purpose = Column(String, nullable=False) # warmup | cooldown | separate | другое duration = Column(Integer) distance = Column(Float) speed = Column(Float) incline = Column(Float) resistance = Column(Integer) notes = Column(String, default="") sort_order = Column(Integer, default=0) created_at = Column(String) workout = relationship("Workout", back_populates="cardio_sessions") class MetricType(Base): __tablename__ = "metric_types" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) name = Column(String, unique=True, nullable=False) unit = Column(String, default="см") category = Column(String, default="body") sort_order = Column(Integer, default=0) created_at = Column(String) metric_values = relationship("BodyMetricValue", back_populates="metric_type") class BodyMetric(Base): __tablename__ = "body_metrics" id = Column(Integer, primary_key=True, index=True) sync_id = Column(String, unique=True, nullable=False, index=True) owner_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True) client_id = Column(Integer, ForeignKey("clients.id", ondelete="SET NULL"), index=True) metric_date = Column(String, nullable=False) notes = Column(String, default="") created_at = Column(String) client = relationship("Client", back_populates="body_metrics") values = relationship( "BodyMetricValue", back_populates="body_metric", cascade="all, delete-orphan", ) class BodyMetricValue(Base): __tablename__ = "body_metric_values" id = Column(Integer, primary_key=True, index=True) body_metric_id = Column( Integer, ForeignKey("body_metrics.id", ondelete="CASCADE"), index=True, ) metric_type_id = Column( Integer, ForeignKey("metric_types.id", ondelete="CASCADE"), index=True, ) value = Column(Float, nullable=False) body_metric = relationship("BodyMetric", back_populates="values") metric_type = relationship("MetricType", back_populates="metric_values") __table_args__ = ( UniqueConstraint( "body_metric_id", "metric_type_id", name="uq_body_metric_value_type", ), ) class SyncLog(Base): __tablename__ = "sync_log" device_id = Column(String, primary_key=True) last_sync = Column(String, nullable=False) updated_at = Column(String)