Skip to content

Commit 88a8cb6

Browse files
committed
almebic migrations and items filters in get endpoint
1 parent dc2c228 commit 88a8cb6

3 files changed

Lines changed: 157 additions & 15 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Add metadata fields to Item
2+
3+
Revision ID: bc408f47ce8b
4+
Revises: 1a31ce608336
5+
Create Date: 2025-06-16 19:13:48.082374
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'bc408f47ce8b'
15+
down_revision = '1a31ce608336'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('item', sa.Column('location', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
23+
op.add_column('item', sa.Column('price', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
24+
op.add_column('item', sa.Column('difficulty', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
25+
op.add_column('item', sa.Column('type', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
26+
op.add_column('item', sa.Column('category', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
27+
op.add_column('item', sa.Column('tags', sa.ARRAY(sa.String()), nullable=True))
28+
op.add_column('item', sa.Column('link', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
29+
op.add_column('item', sa.Column('picture', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True))
30+
op.add_column('item', sa.Column('in_featured', sa.Boolean(), nullable=False))
31+
op.add_column('item', sa.Column('rating', sa.Float(), nullable=True))
32+
op.add_column('item', sa.Column('tries', sa.Integer(), nullable=True))
33+
op.add_column('item', sa.Column('favorites', sa.Integer(), nullable=True))
34+
op.add_column('item', sa.Column('duration', sa.Interval(), nullable=True))
35+
# ### end Alembic commands ###
36+
37+
38+
def downgrade():
39+
# ### commands auto generated by Alembic - please adjust! ###
40+
op.drop_column('item', 'favorites')
41+
op.drop_column('item', 'tries')
42+
op.drop_column('item', 'rating')
43+
op.drop_column('item', 'in_featured')
44+
op.drop_column('item', 'picture')
45+
op.drop_column('item', 'link')
46+
op.drop_column('item', 'tags')
47+
op.drop_column('item', 'category')
48+
op.drop_column('item', 'type')
49+
op.drop_column('item', 'difficulty')
50+
op.drop_column('item', 'price')
51+
op.drop_column('item', 'location')
52+
op.drop_column('item', 'duration')
53+
# ### end Alembic commands ###
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Add column to item model
2+
3+
Revision ID: f77762e21457
4+
Revises: bc408f47ce8b
5+
Create Date: 2025-06-25 14:30:07.278621
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'f77762e21457'
15+
down_revision = 'bc408f47ce8b'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('item', sa.Column('duration', sa.Interval(), nullable=True))
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade():
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_column('item', 'duration')
29+
# ### end Alembic commands ###

backend/app/api/routes/items.py

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import uuid
2+
import datetime
23
from typing import Any
34

45
from fastapi import APIRouter, HTTPException
6+
from pydantic import BaseModel
57
from sqlmodel import func, select
68

79
from app.api.deps import CurrentUser, SessionDep
8-
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
10+
from app.models import Item, ItemBase, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
911
from app.openfga.fgaMiddleware import check_user_has_relation, create_fga_tuple, delete_fga_tuple, initialize_fga_client
1012
from app.openfga.fgaMiddleware import check_user_has_permission
1113
from openfga_sdk.client.models.tuple import ClientTuple
@@ -15,40 +17,97 @@
1517

1618
@router.get("/", response_model=ItemsPublic)
1719
def read_items(
18-
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100, get_completed: bool = False
20+
session: SessionDep,
21+
current_user: CurrentUser,
22+
skip: int = 0,
23+
limit: int = 100,
24+
get_completed: bool = False,
25+
get_owned: bool = False,
26+
title: str | None = None,
27+
description: str | None = None,
28+
location: str | None = None,
29+
price: str | None = None,
30+
difficulty: str | None = None,
31+
type: str | None = None,
32+
category: str | None = None,
33+
tags: list[str] | None = None,
34+
link: str | None = None,
35+
picture: str | None = None,
36+
in_featured: bool | None = None,
37+
rating: float | None = None,
38+
tries: int | None = None,
39+
favorites: int | None = None,
40+
duration: datetime.timedelta | None = None,
1941
) -> Any:
2042
"""
2143
Retrieve items.
2244
"""
2345
fga_client = initialize_fga_client()
2446

25-
if current_user.is_superuser:
26-
count_statement = select(func.count()).select_from(Item)
27-
count = session.exec(count_statement).one()
28-
statement = select(Item).offset(skip).limit(limit)
29-
items = session.exec(statement).all()
47+
# Database filters
48+
if title or description or location or price or difficulty or type or category or tags or link or picture or in_featured or rating or tries or favorites or duration:
49+
query = select(Item)
50+
# instead of appending to filters, we can use a where builder results = session.exec(query).all()
51+
if title:
52+
query = query.where(Item.title == title)
53+
if description:
54+
query = query.where(Item.description == description)
55+
if location:
56+
query = query.where(Item.location == location)
57+
if price:
58+
query = query.where(Item.price == price)
59+
if difficulty:
60+
query = query.where(Item.difficulty == difficulty)
61+
if type:
62+
query = query.where(Item.type == type)
63+
if category:
64+
query = query.where(Item.category == category)
65+
if tags:
66+
query = query.where(Item.tags == tags)
67+
if link:
68+
query = query.where(Item.link == link)
69+
if picture:
70+
query = query.where(Item.picture == picture)
71+
if in_featured:
72+
query = query.where(Item.in_featured == in_featured)
73+
if rating:
74+
query = query.where(Item.rating == rating)
75+
if tries:
76+
query = query.where(Item.tries == tries)
77+
if favorites:
78+
query = query.where(Item.favorites == favorites)
79+
if duration:
80+
query = query.where(Item.duration == duration)
81+
82+
query = query.offset(skip).limit(limit)
83+
items = session.exec(query).all()
84+
count = len(items)
3085
else:
31-
count_statement = (
32-
select(func.count())
33-
.select_from(Item)
34-
# .where(Item.owner_id == current_user.id) # only returns items that the user owns
35-
)
36-
count = session.exec(count_statement).one()
86+
# get all items
87+
count = session.exec(select(func.count()).select_from(Item)).one()
3788
statement = (
3889
select(Item)
39-
# .where(Item.owner_id == current_user.id)
4090
.offset(skip)
4191
.limit(limit)
4292
)
4393
items = session.exec(statement).all()
94+
95+
# FGA filters
96+
if get_owned:
97+
item_ids = check_user_has_relation(fga_client, relation="owner", user=f"{current_user.email}")
98+
if len(item_ids) == 0:
99+
return ItemsPublic(data=[], count=0)
100+
else:
101+
# filter items to only those that match the item_ids
102+
items = [item for item in items if str(item.id) in item_ids]
44103

45104
if get_completed:
46105
item_ids = check_user_has_relation(fga_client, relation="completed", user=f"{current_user.email}")
47106
if len(item_ids) == 0:
48107
return ItemsPublic(data=[], count=0)
49108
else:
50109
# get those items with those ids
51-
items = [session.get(Item, id) for id in item_ids]
110+
items = [item for item in items if str(item.id) in item_ids]
52111

53112
return ItemsPublic(data=[ItemPublic.model_validate(item) for item in items], count=count)
54113

@@ -150,3 +209,4 @@ async def delete_item(
150209
delete_fga_tuple(fga_client, [ClientTuple(user=f"user:{current_user.id}", relation="owner", object=f"item:{id}")])
151210

152211
return Message(message="Item deleted successfully")
212+

0 commit comments

Comments
 (0)