Skip to content

Commit dadf376

Browse files
committed
Merge backend changes from other branch
1 parent bebbad4 commit dadf376

32 files changed

Lines changed: 4348 additions & 125 deletions

backend/README.md

Lines changed: 441 additions & 85 deletions
Large diffs are not rendered by default.

backend/app/alembic/env.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
from app.models import SQLModel # noqa
2222
from app.core.config import settings # noqa
2323

24+
# Import all models to ensure they are registered with SQLModel metadata
25+
import app.models.user # noqa
26+
import app.models.item # noqa
27+
import app.models.social # noqa
28+
import app.models.token # noqa
29+
import app.models.workout # noqa
30+
2431
target_metadata = SQLModel.metadata
2532

2633
# other values from the config, defined by the needs of env.py,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Add social models for user follows and workout posts
2+
3+
Revision ID: 20250515_social
4+
Revises:
5+
Create Date: 2025-05-15 15:56:00.000000
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '20250515_social'
15+
down_revision = 'd98dd8ec85a3' # Updated to point to the previous migration
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# Create UserFollow table
22+
op.create_table(
23+
'userfollow',
24+
sa.Column('follower_id', postgresql.UUID(), nullable=False),
25+
sa.Column('followed_id', postgresql.UUID(), nullable=False),
26+
sa.Column('created_at', sa.DateTime(), nullable=False),
27+
sa.ForeignKeyConstraint(['followed_id'], ['user.id'], ),
28+
sa.ForeignKeyConstraint(['follower_id'], ['user.id'], ),
29+
sa.PrimaryKeyConstraint('follower_id', 'followed_id')
30+
)
31+
32+
# Create WorkoutPost table
33+
op.create_table(
34+
'workoutpost',
35+
sa.Column('id', postgresql.UUID(), nullable=False),
36+
sa.Column('user_id', postgresql.UUID(), nullable=False),
37+
sa.Column('title', sa.String(), nullable=False),
38+
sa.Column('description', sa.String(), nullable=True),
39+
sa.Column('workout_type', sa.String(), nullable=False),
40+
sa.Column('duration_minutes', sa.Integer(), nullable=False),
41+
sa.Column('calories_burned', sa.Integer(), nullable=True),
42+
sa.Column('created_at', sa.DateTime(), nullable=False),
43+
sa.Column('updated_at', sa.DateTime(), nullable=True),
44+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
45+
sa.PrimaryKeyConstraint('id')
46+
)
47+
48+
# Add indexes for better query performance
49+
op.create_index(op.f('ix_workoutpost_user_id'), 'workoutpost', ['user_id'], unique=False)
50+
op.create_index(op.f('ix_workoutpost_created_at'), 'workoutpost', ['created_at'], unique=False)
51+
op.create_index(op.f('ix_userfollow_follower_id'), 'userfollow', ['follower_id'], unique=False)
52+
op.create_index(op.f('ix_userfollow_followed_id'), 'userfollow', ['followed_id'], unique=False)
53+
54+
55+
def downgrade():
56+
# Drop indexes
57+
op.drop_index(op.f('ix_userfollow_followed_id'), table_name='userfollow')
58+
op.drop_index(op.f('ix_userfollow_follower_id'), table_name='userfollow')
59+
op.drop_index(op.f('ix_workoutpost_created_at'), table_name='workoutpost')
60+
op.drop_index(op.f('ix_workoutpost_user_id'), table_name='workoutpost')
61+
62+
# Drop tables
63+
op.drop_table('workoutpost')
64+
op.drop_table('userfollow')
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Add workout models for fitness tracking
2+
3+
Revision ID: 20250520_workout
4+
Revises: 20250515_social
5+
Create Date: 2025-05-20 10:38:00.000000
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '20250520_workout'
15+
down_revision = '20250515_social' # Points to the previous migration
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# Create Workout table
22+
op.create_table(
23+
'workout',
24+
sa.Column('id', postgresql.UUID(), nullable=False),
25+
sa.Column('user_id', postgresql.UUID(), nullable=False),
26+
sa.Column('name', sa.String(), nullable=False),
27+
sa.Column('description', sa.String(), nullable=True),
28+
sa.Column('scheduled_date', sa.DateTime(), nullable=True),
29+
sa.Column('completed_date', sa.DateTime(), nullable=True),
30+
sa.Column('duration_minutes', sa.Integer(), nullable=True),
31+
sa.Column('is_completed', sa.Boolean(), nullable=False),
32+
sa.Column('created_at', sa.DateTime(), nullable=False),
33+
sa.Column('updated_at', sa.DateTime(), nullable=True),
34+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
35+
sa.PrimaryKeyConstraint('id')
36+
)
37+
38+
# Create Exercise table
39+
op.create_table(
40+
'exercise',
41+
sa.Column('id', postgresql.UUID(), nullable=False),
42+
sa.Column('workout_id', postgresql.UUID(), nullable=False),
43+
sa.Column('name', sa.String(), nullable=False),
44+
sa.Column('description', sa.String(), nullable=True),
45+
sa.Column('category', sa.String(), nullable=False),
46+
sa.Column('sets', sa.Integer(), nullable=True),
47+
sa.Column('reps', sa.Integer(), nullable=True),
48+
sa.Column('weight', sa.Float(), nullable=True),
49+
sa.Column('created_at', sa.DateTime(), nullable=False),
50+
sa.Column('updated_at', sa.DateTime(), nullable=True),
51+
sa.ForeignKeyConstraint(['workout_id'], ['workout.id'], ondelete='CASCADE'),
52+
sa.PrimaryKeyConstraint('id')
53+
)
54+
55+
# Add indexes for better query performance
56+
op.create_index(op.f('ix_workout_user_id'), 'workout', ['user_id'], unique=False)
57+
op.create_index(op.f('ix_workout_scheduled_date'), 'workout', ['scheduled_date'], unique=False)
58+
op.create_index(op.f('ix_workout_is_completed'), 'workout', ['is_completed'], unique=False)
59+
op.create_index(op.f('ix_exercise_workout_id'), 'exercise', ['workout_id'], unique=False)
60+
op.create_index(op.f('ix_exercise_category'), 'exercise', ['category'], unique=False)
61+
62+
63+
def downgrade():
64+
# Drop indexes
65+
op.drop_index(op.f('ix_exercise_category'), table_name='exercise')
66+
op.drop_index(op.f('ix_exercise_workout_id'), table_name='exercise')
67+
op.drop_index(op.f('ix_workout_is_completed'), table_name='workout')
68+
op.drop_index(op.f('ix_workout_scheduled_date'), table_name='workout')
69+
op.drop_index(op.f('ix_workout_user_id'), table_name='workout')
70+
71+
# Drop tables
72+
op.drop_table('exercise')
73+
op.drop_table('workout')

backend/app/api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, private, users, utils
3+
from app.api.routes import items, login, private, social, users, utils, workouts
44
from app.core.config import settings
55

66
api_router = APIRouter()
77
api_router.include_router(login.router)
88
api_router.include_router(users.router)
99
api_router.include_router(utils.router)
1010
api_router.include_router(items.router)
11+
api_router.include_router(social.router)
12+
api_router.include_router(workouts.router)
1113

1214

1315
if settings.ENVIRONMENT == "local":

backend/app/api/routes/items.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@
1212

1313
@router.get("/", response_model=ItemsPublic)
1414
def read_items(
15-
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
15+
session: SessionDep,
16+
current_user: CurrentUser,
17+
skip: int = 0,
18+
limit: int = 100
1619
) -> Any:
1720
"""
1821
Retrieve items.
22+
23+
This endpoint retrieves a list of items. For regular users, it returns only their own items.
24+
For superusers, it returns all items in the system.
25+
26+
Parameters:
27+
- **skip**: Number of records to skip for pagination
28+
- **limit**: Maximum number of records to return
29+
30+
Returns a list of items and the total count.
1931
"""
2032

2133
if current_user.is_superuser:
@@ -42,9 +54,24 @@ def read_items(
4254

4355

4456
@router.get("/{id}", response_model=ItemPublic)
45-
def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
57+
def read_item(
58+
session: SessionDep,
59+
current_user: CurrentUser,
60+
id: uuid.UUID
61+
) -> Any:
4662
"""
4763
Get item by ID.
64+
65+
This endpoint retrieves a specific item by its ID.
66+
67+
Parameters:
68+
- **id**: The UUID of the item to retrieve
69+
70+
Returns the item details.
71+
72+
Raises:
73+
- 404: If the item is not found
74+
- 400: If the user doesn't have permission to access this item
4875
"""
4976
item = session.get(Item, id)
5077
if not item:
@@ -56,10 +83,29 @@ def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) ->
5683

5784
@router.post("/", response_model=ItemPublic)
5885
def create_item(
59-
*, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate
86+
*,
87+
session: SessionDep,
88+
current_user: CurrentUser,
89+
item_in: ItemCreate
6090
) -> Any:
6191
"""
6292
Create new item.
93+
94+
This endpoint allows users to create a new item.
95+
96+
Parameters:
97+
- **title**: Required. The title of the item
98+
- **description**: Optional. A description of the item
99+
100+
Returns the created item with its ID and owner information.
101+
102+
Example request body:
103+
```json
104+
{
105+
"title": "My Item",
106+
"description": "This is a description of my item"
107+
}
108+
```
63109
"""
64110
item = Item.model_validate(item_in, update={"owner_id": current_user.id})
65111
session.add(item)
@@ -78,6 +124,27 @@ def update_item(
78124
) -> Any:
79125
"""
80126
Update an item.
127+
128+
This endpoint allows users to update an existing item.
129+
130+
Parameters:
131+
- **id**: The UUID of the item to update
132+
- **title**: Optional. The updated title
133+
- **description**: Optional. The updated description
134+
135+
Returns the updated item.
136+
137+
Raises:
138+
- 404: If the item is not found
139+
- 400: If the user doesn't have permission to update this item
140+
141+
Example request body:
142+
```json
143+
{
144+
"title": "Updated Title",
145+
"description": "Updated description"
146+
}
147+
```
81148
"""
82149
item = session.get(Item, id)
83150
if not item:
@@ -94,10 +161,23 @@ def update_item(
94161

95162
@router.delete("/{id}")
96163
def delete_item(
97-
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
164+
session: SessionDep,
165+
current_user: CurrentUser,
166+
id: uuid.UUID
98167
) -> Message:
99168
"""
100169
Delete an item.
170+
171+
This endpoint allows users to delete an item.
172+
173+
Parameters:
174+
- **id**: The UUID of the item to delete
175+
176+
Returns a success message upon successful deletion.
177+
178+
Raises:
179+
- 404: If the item is not found
180+
- 400: If the user doesn't have permission to delete this item
101181
"""
102182
item = session.get(Item, id)
103183
if not item:

0 commit comments

Comments
 (0)