feat: Implement active workout flow with status management
- Added `status`, `total_sets`, and `total_volume` fields to the Workout model. - Introduced `source_kind`, `title_snapshot`, and `image_s3_url_snapshot` fields to the WorkoutItem model. - Created endpoints for managing active workouts, including finishing and discarding workouts. - Updated workout creation to ensure only one active workout exists per user. - Implemented batch addition of workout sets and updates to workout set details. - Enhanced database schema with Alembic migrations to support new fields and constraints. - Added validation to ensure at least one field is provided for workout set updates. - Updated calorie estimation logic to reflect new workout set structure.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""active workout flow
|
||||
|
||||
Revision ID: 0002_active_workout_flow
|
||||
Revises: 0001_initial
|
||||
Create Date: 2026-05-29 08:40:00.000000
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0002_active_workout_flow"
|
||||
down_revision: str | None = "0001_initial"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"logic_workouts",
|
||||
sa.Column("status", sa.String(length=20), nullable=False, server_default="active"),
|
||||
)
|
||||
op.add_column(
|
||||
"logic_workouts",
|
||||
sa.Column("total_sets", sa.Integer(), nullable=False, server_default="0"),
|
||||
)
|
||||
op.add_column(
|
||||
"logic_workouts",
|
||||
sa.Column("total_volume", sa.Numeric(12, 2), nullable=False, server_default="0"),
|
||||
)
|
||||
op.create_index("ix_logic_workouts_status", "logic_workouts", ["status"])
|
||||
op.create_check_constraint(
|
||||
"ck_workout_status",
|
||||
"logic_workouts",
|
||||
"status IN ('active', 'finished', 'discarded')",
|
||||
)
|
||||
op.execute(
|
||||
"UPDATE logic_workouts SET status = 'finished' WHERE finished_at IS NOT NULL"
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"logic_workout_items",
|
||||
sa.Column("source_kind", sa.String(length=20), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"logic_workout_items",
|
||||
sa.Column("title_snapshot", sa.String(length=160), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"logic_workout_items",
|
||||
sa.Column("image_s3_url_snapshot", sa.Text(), nullable=True),
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE logic_workout_items AS item
|
||||
SET
|
||||
source_kind = 'exercise',
|
||||
title_snapshot = exercise.name,
|
||||
image_s3_url_snapshot = exercise.image_s3_url
|
||||
FROM logic_exercises AS exercise
|
||||
WHERE item.exercise_id = exercise.id
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE logic_workout_items AS item
|
||||
SET
|
||||
source_kind = 'equipment',
|
||||
title_snapshot = equipment.name,
|
||||
image_s3_url_snapshot = equipment.image_s3_url
|
||||
FROM logic_equipment AS equipment
|
||||
WHERE item.equipment_id = equipment.id
|
||||
"""
|
||||
)
|
||||
op.alter_column("logic_workout_items", "source_kind", nullable=False)
|
||||
op.alter_column("logic_workout_items", "title_snapshot", nullable=False)
|
||||
op.create_check_constraint(
|
||||
"ck_workout_item_source_kind",
|
||||
"logic_workout_items",
|
||||
"source_kind IN ('exercise', 'equipment')",
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE logic_workouts AS workout
|
||||
SET
|
||||
total_sets = totals.total_sets,
|
||||
total_volume = totals.total_volume,
|
||||
estimated_calories = totals.estimated_calories
|
||||
FROM (
|
||||
SELECT
|
||||
item.workout_id,
|
||||
COUNT(set_row.id) AS total_sets,
|
||||
COALESCE(SUM(set_row.weight * set_row.reps), 0) AS total_volume,
|
||||
COALESCE(SUM(set_row.calories), 0) AS estimated_calories
|
||||
FROM logic_workout_items AS item
|
||||
LEFT JOIN logic_workout_sets AS set_row ON set_row.workout_item_id = item.id
|
||||
GROUP BY item.workout_id
|
||||
) AS totals
|
||||
WHERE workout.id = totals.workout_id
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint("ck_workout_item_source_kind", "logic_workout_items", type_="check")
|
||||
op.drop_column("logic_workout_items", "image_s3_url_snapshot")
|
||||
op.drop_column("logic_workout_items", "title_snapshot")
|
||||
op.drop_column("logic_workout_items", "source_kind")
|
||||
|
||||
op.drop_constraint("ck_workout_status", "logic_workouts", type_="check")
|
||||
op.drop_index("ix_logic_workouts_status", table_name="logic_workouts")
|
||||
op.drop_column("logic_workouts", "total_volume")
|
||||
op.drop_column("logic_workouts", "total_sets")
|
||||
op.drop_column("logic_workouts", "status")
|
||||
Reference in New Issue
Block a user