From 5ae79ff49b162ba48b9697c51be382cacbabdc45 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sat, 1 Feb 2025 14:06:18 +0200 Subject: [PATCH 01/20] added shoping_cart schemas --- src/schemas/shoping_cart.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/schemas/shoping_cart.py diff --git a/src/schemas/shoping_cart.py b/src/schemas/shoping_cart.py new file mode 100644 index 0000000..5937b67 --- /dev/null +++ b/src/schemas/shoping_cart.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel +from typing import List + + +class CartCreate(BaseModel): + movie_id: int + + +class CartItemResponse(BaseModel): + message: str + + +class CartItemDetail(BaseModel): + movie_id: int + title: str + price: float + genre: str + release_year: int + + +class CartResponse(BaseModel): + user_id: int + movies: List[CartItemDetail] From b149b43c3684aa9790ee060597a0607923c1adfa Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sat, 1 Feb 2025 15:52:30 +0200 Subject: [PATCH 02/20] added crud with db query for shoping_cart routes & validations --- src/database/crud/__init__.py | 0 src/database/crud/shopping_cart.py | 40 +++++++++++ .../{shoping_cart.py => shopping_cart.py} | 0 src/routes/shopping_cart.py | 71 +++++++++++++++++++ src/validation/shopping_cart.py | 38 ++++++++++ 5 files changed, 149 insertions(+) create mode 100644 src/database/crud/__init__.py create mode 100644 src/database/crud/shopping_cart.py rename src/database/models/{shoping_cart.py => shopping_cart.py} (100%) create mode 100644 src/routes/shopping_cart.py create mode 100644 src/validation/shopping_cart.py diff --git a/src/database/crud/__init__.py b/src/database/crud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py new file mode 100644 index 0000000..ad18435 --- /dev/null +++ b/src/database/crud/shopping_cart.py @@ -0,0 +1,40 @@ +from sqlalchemy.orm import Session + +from database import ( + Cart, + User, + Movie, + CartItem +) + + +def get_user_cart(user: User, db: Session): + return db.query(Cart).filter(Cart.user_id == user.id).first() + + +def get_movie_by_id(movie_id: int, db: Session): + return db.query(Movie).filter(Movie.id == movie_id).first() + + +def get_cart_item(cart: Cart, movie_id: int, db: Session): + return db.query(CartItem).filter( + CartItem.cart_id == cart.id, CartItem.movie_id == movie_id + ).first() + + +def create_cart(user: User, db: Session): + cart = Cart(user_id=user.id) + db.add(cart) + db.flush() + return cart + + +def add_cart_item(cart: Cart, movie: Movie, db: Session): + cart_item = CartItem(cart_id=cart.id, movie_id=movie.id) + db.add(cart_item) + db.commit() + + +def delete_cart_item(cart_item: CartItem, db: Session): + db.delete(cart_item) + db.commit() diff --git a/src/database/models/shoping_cart.py b/src/database/models/shopping_cart.py similarity index 100% rename from src/database/models/shoping_cart.py rename to src/database/models/shopping_cart.py diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py new file mode 100644 index 0000000..a0e6e8c --- /dev/null +++ b/src/routes/shopping_cart.py @@ -0,0 +1,71 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from database.session_postgresql import get_postgresql_db +from schemas.shoping_cart import CartCreate, CartResponse, CartItemResponse, CartItemDetail +from database.models.accounts import User +from security.auth import get_current_user +from validation.shoping_cart import validate_not_in_cart, validate_not_purchased, validate_movie_availability +from database.crud.shopping_cart import get_user_cart, get_movie_by_id, get_cart_item, create_cart, add_cart_item, delete_cart_item + + +router = APIRouter(prefix="/cart", tags=["Shopping Cart"]) + + +@router.post("/add", response_model=CartResponse) +def add_to_cart(cart_data: CartCreate, db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): + movie = get_movie_by_id(cart_data.movie_id, db) + if not movie: + raise HTTPException(status_code=404, detail="Movie not found") + + validate_movie_availability(movie) + validate_not_purchased(user, movie, db) + validate_not_in_cart(user, movie, db) + + cart = get_user_cart(user, db) + if not cart: + cart = create_cart(user, db) + + add_cart_item(cart, movie, db) + db.refresh(cart) + + cart_items = [CartItemDetail( + movie_id=item.movie.id, + title=item.movie.name, + price=item.movie.price, + genre=item.movie.genres[0].name if item.movie.genres else "Unknown", + release_year=item.movie.year + ) for item in cart.items] + + return CartResponse(user_id=user.id, movies=cart_items) + + +@router.get("/", response_model=CartResponse) +def get_cart(db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): + cart = get_user_cart(user, db) + if not cart: + return CartResponse(user_id=user.id, movies=[]) + + cart_items = [CartItemDetail( + movie_id=item.movie.id, + title=item.movie.name, + price=item.movie.price, + genre=item.movie.genres[0].name if item.movie.genres else "Unknown", + release_year=item.movie.year + ) for item in cart.items] + + return CartResponse(user_id=user.id, movies=cart_items) + + +@router.delete("/remove/{movie_id}", response_model=CartItemResponse) +def remove_from_cart(movie_id: int, db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): + cart = get_user_cart(user, db) + if not cart: + raise HTTPException(status_code=404, detail="Cart not found") + + cart_item = get_cart_item(cart, movie_id, db) + if not cart_item: + raise HTTPException(status_code=404, detail="Movie not in cart") + + delete_cart_item(cart_item, db) + return CartItemResponse(message="Movie removed from cart") diff --git a/src/validation/shopping_cart.py b/src/validation/shopping_cart.py new file mode 100644 index 0000000..6107bdc --- /dev/null +++ b/src/validation/shopping_cart.py @@ -0,0 +1,38 @@ +from fastapi import HTTPException +from sqlalchemy.orm import Session + +from database import Movie, User, CartItem, Cart + + +def validate_movie_availability(movie: Movie): + if not movie: + raise HTTPException( + status_code=400, + detail="Movie is not available for purchase." + ) + + +def validate_not_purchased(user: User, movie: Movie, db: Session): + purchased_movies = db.query(CartItem).join(Cart).filter( + Cart.user_id == user.id, + CartItem.movie_id == movie.id + ).first() + if purchased_movies: + raise HTTPException( + status_code=400, + detail="You have already purchased this movie." + ) + + +def validate_not_in_cart(user: User, movie: Movie, db: Session): + cart = db.query(Cart).filter(Cart.user_id == user.id).first() + if cart: + existing_item = db.query(CartItem).filter( + CartItem.cart_id == cart.id, + CartItem.movie_id == movie.id + ).first() + if existing_item: + raise HTTPException( + status_code=400, + detail="Movie already in cart." + ) From 6be1f9635a772a80574d3cdacae6ec2bc08f2c78 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sat, 1 Feb 2025 16:02:25 +0200 Subject: [PATCH 03/20] added shoping_cart routes --- src/main.py | 6 ++- src/routes/shopping_cart.py | 95 ++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/main.py b/src/main.py index 64157b4..6867c08 100644 --- a/src/main.py +++ b/src/main.py @@ -1,8 +1,12 @@ from fastapi import FastAPI +from routes.shopping_cart import router as shopping_carts_router + app = FastAPI( title="Movies Cinema", ) -api_version_prefix = "/api/v1" \ No newline at end of file +api_version_prefix = "/api/v1" + +app.include_router(shopping_carts_router, prefix=f"{api_version_prefix}/carts", tags=["carts"]) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index a0e6e8c..db6f835 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -1,19 +1,54 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import ( + APIRouter, + Depends, + HTTPException +) from sqlalchemy.orm import Session from database.session_postgresql import get_postgresql_db -from schemas.shoping_cart import CartCreate, CartResponse, CartItemResponse, CartItemDetail +from schemas.shoping_cart import ( + CartCreate, + CartResponse, + CartItemResponse, + CartItemDetail +) from database.models.accounts import User -from security.auth import get_current_user -from validation.shoping_cart import validate_not_in_cart, validate_not_purchased, validate_movie_availability -from database.crud.shopping_cart import get_user_cart, get_movie_by_id, get_cart_item, create_cart, add_cart_item, delete_cart_item +from database.crud.shopping_cart import ( + get_user_cart, + get_movie_by_id, + get_cart_item, + create_cart, + add_cart_item, + delete_cart_item +) +from validation.shopping_cart import ( + validate_not_in_cart, + validate_not_purchased, + validate_movie_availability +) router = APIRouter(prefix="/cart", tags=["Shopping Cart"]) +@router.get("/", response_model=CartResponse) +def get_cart( + db: Session = Depends(get_postgresql_db), + user: User = Depends(get_current_user) +): + cart = get_user_cart(user, db) + if not cart: + return CartResponse(user_id=user.id, movies=[]) + + return CartResponse(user_id=user.id, movies=get_cart_items_details(cart)) + + @router.post("/add", response_model=CartResponse) -def add_to_cart(cart_data: CartCreate, db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): +def add_to_cart( + cart_data: CartCreate, + db: Session = Depends(get_postgresql_db), + user: User = Depends(get_current_user) +): movie = get_movie_by_id(cart_data.movie_id, db) if not movie: raise HTTPException(status_code=404, detail="Movie not found") @@ -29,36 +64,15 @@ def add_to_cart(cart_data: CartCreate, db: Session = Depends(get_postgresql_db), add_cart_item(cart, movie, db) db.refresh(cart) - cart_items = [CartItemDetail( - movie_id=item.movie.id, - title=item.movie.name, - price=item.movie.price, - genre=item.movie.genres[0].name if item.movie.genres else "Unknown", - release_year=item.movie.year - ) for item in cart.items] - - return CartResponse(user_id=user.id, movies=cart_items) - - -@router.get("/", response_model=CartResponse) -def get_cart(db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): - cart = get_user_cart(user, db) - if not cart: - return CartResponse(user_id=user.id, movies=[]) - - cart_items = [CartItemDetail( - movie_id=item.movie.id, - title=item.movie.name, - price=item.movie.price, - genre=item.movie.genres[0].name if item.movie.genres else "Unknown", - release_year=item.movie.year - ) for item in cart.items] - - return CartResponse(user_id=user.id, movies=cart_items) + return CartResponse(user_id=user.id, movies=get_cart_items_details(cart)) @router.delete("/remove/{movie_id}", response_model=CartItemResponse) -def remove_from_cart(movie_id: int, db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user)): +def remove_from_cart( + movie_id: int, + db: Session = Depends(get_postgresql_db), + user: User = Depends(get_current_user) +): cart = get_user_cart(user, db) if not cart: raise HTTPException(status_code=404, detail="Cart not found") @@ -69,3 +83,18 @@ def remove_from_cart(movie_id: int, db: Session = Depends(get_postgresql_db), us delete_cart_item(cart_item, db) return CartItemResponse(message="Movie removed from cart") + + +def get_cart_items_details(cart): + return [ + CartItemDetail( + movie_id=item.movie.id, + title=item.movie.name, + price=item.movie.price, + genre=( + item.movie.genres[0].name if item.movie.genres else "Unknown" + ), + release_year=item.movie.year + ) + for item in cart.items + ] if cart else [] From 18ac11a85389734ac776ccc6e2c1adc1b3e6418d Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sat, 1 Feb 2025 16:37:47 +0200 Subject: [PATCH 04/20] fixed imports with old naming --- src/database/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/__init__.py b/src/database/__init__.py index eac3e24..3c5c3ba 100644 --- a/src/database/__init__.py +++ b/src/database/__init__.py @@ -17,7 +17,7 @@ RefreshToken, UserProfile ) -from database.models.shoping_cart import Cart, CartItem +from database.models.shopping_cart import Cart, CartItem from database.models.orders import Order, OrderItem from database.models.payments import Payment, PaymentItem, PaymentStatusEnum from database.models.base import Base From f10aa3248a9b0b450ac634d81fd1d50268b04f1b Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sun, 2 Feb 2025 16:25:17 +0200 Subject: [PATCH 05/20] "fixed old naming" --- src/routes/shopping_cart.py | 2 +- src/schemas/{shoping_cart.py => shopping_cart.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/schemas/{shoping_cart.py => shopping_cart.py} (100%) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index db6f835..fe4637b 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import Session from database.session_postgresql import get_postgresql_db -from schemas.shoping_cart import ( +from schemas.shopping_cart import ( CartCreate, CartResponse, CartItemResponse, diff --git a/src/schemas/shoping_cart.py b/src/schemas/shopping_cart.py similarity index 100% rename from src/schemas/shoping_cart.py rename to src/schemas/shopping_cart.py From 2148c199e6642e13cd52ae77727760a1f19e9168 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sun, 2 Feb 2025 17:33:40 +0200 Subject: [PATCH 06/20] added custome routes --- src/database/__init__.py | 2 +- src/database/crud/shopping_cart.py | 4 ++ src/main.py | 6 +-- src/routes/shopping_cart.py | 64 ++++++++++++++++++++++++------ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/database/__init__.py b/src/database/__init__.py index 791639b..0823c61 100644 --- a/src/database/__init__.py +++ b/src/database/__init__.py @@ -20,7 +20,7 @@ ) from database.models.orders import Order, OrderItem from database.models.payments import Payment, PaymentItem, PaymentStatusEnum -from database.models.shoping_cart import Cart, CartItem +from database.models.shopping_cart import Cart, CartItem from database.session_postgresql import get_postgresql_db as get_db from database.session_postgresql import ( get_postgresql_db_contextmanager as get_db_contextmanager, diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index ad18435..3b94701 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -38,3 +38,7 @@ def add_cart_item(cart: Cart, movie: Movie, db: Session): def delete_cart_item(cart_item: CartItem, db: Session): db.delete(cart_item) db.commit() + + +def get_purchased_movies(user: User, db: Session): + return db.query(Movie).join(CartItem).join(Cart).filter(Cart.user_id == user.id).all() diff --git a/src/main.py b/src/main.py index ace7d80..9934a3b 100644 --- a/src/main.py +++ b/src/main.py @@ -16,6 +16,6 @@ app.include_router( profiles_router, prefix=f"{api_version_prefix}/profiles", tags=["profiles"] ) - - -app.include_router(shopping_carts_router, prefix=f"{api_version_prefix}/carts", tags=["carts"]) +app.include_router( + shopping_carts_router, prefix=f"{api_version_prefix}/carts", tags=["carts"] +) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index fe4637b..9f520c4 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -1,10 +1,7 @@ -from fastapi import ( - APIRouter, - Depends, - HTTPException -) +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session +from database import CartItem, Cart, User from database.session_postgresql import get_postgresql_db from schemas.shopping_cart import ( CartCreate, @@ -12,21 +9,21 @@ CartItemResponse, CartItemDetail ) -from database.models.accounts import User from database.crud.shopping_cart import ( get_user_cart, get_movie_by_id, get_cart_item, create_cart, add_cart_item, - delete_cart_item + delete_cart_item, + get_purchased_movies ) from validation.shopping_cart import ( validate_not_in_cart, validate_not_purchased, validate_movie_availability ) - +from security import get_current_user router = APIRouter(prefix="/cart", tags=["Shopping Cart"]) @@ -39,7 +36,6 @@ def get_cart( cart = get_user_cart(user, db) if not cart: return CartResponse(user_id=user.id, movies=[]) - return CartResponse(user_id=user.id, movies=get_cart_items_details(cart)) @@ -57,10 +53,7 @@ def add_to_cart( validate_not_purchased(user, movie, db) validate_not_in_cart(user, movie, db) - cart = get_user_cart(user, db) - if not cart: - cart = create_cart(user, db) - + cart = get_user_cart(user, db) or create_cart(user, db) add_cart_item(cart, movie, db) db.refresh(cart) @@ -81,10 +74,55 @@ def remove_from_cart( if not cart_item: raise HTTPException(status_code=404, detail="Movie not in cart") + # notify_moderators(f"User {user.id} removed movie {cart_item.movie.name} from the cart.") delete_cart_item(cart_item, db) return CartItemResponse(message="Movie removed from cart") +@router.delete("/clear", response_model=CartItemResponse) +def clear_cart( + db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user) +): + cart = get_user_cart(user, db) + if not cart or not cart.items: + raise HTTPException(status_code=404, detail="Cart is already empty") + + db.query(CartItem).filter(CartItem.cart_id == cart.id).delete() + db.commit() + + return CartItemResponse(message="Cart cleared successfully") + + +@router.get("/purchased") +def get_purchased_movies( + db: Session = Depends(get_postgresql_db), + user: User = Depends(get_current_user) +): + purchased_movies = get_purchased_movies(user, db) + + return {"purchased_movies": [movie.name for movie in purchased_movies]} + + +@router.get("/admin/carts") +def get_all_carts( + db: Session = Depends(get_postgresql_db), + user: User = Depends(get_current_user) +): + if user.group.name != "moderator": + raise HTTPException(status_code=403, detail="You are not authorized") + + all_carts = db.query(Cart).all() + return { + "carts": [ + { + "user_id": cart.user_id, + "movies": [item.movie.name for item in cart.items] + } + for cart in all_carts + ] + } + + def get_cart_items_details(cart): return [ CartItemDetail( From 1834e724031fbb845a051a628d9129cda5ddd89c Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Sun, 2 Feb 2025 17:47:26 +0200 Subject: [PATCH 07/20] "fixed naming conflict" --- src/routes/shopping_cart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 9f520c4..a3eb0e8 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -94,7 +94,7 @@ def clear_cart( @router.get("/purchased") -def get_purchased_movies( +def list_purchased_movies( db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user) ): From d4d2224004ea585983b3cb0d9038ff85c5290c65 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 12:16:53 +0200 Subject: [PATCH 08/20] added specific response schema for purchased movies --- src/schemas/shopping_cart.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/schemas/shopping_cart.py b/src/schemas/shopping_cart.py index 5937b67..97cdcad 100644 --- a/src/schemas/shopping_cart.py +++ b/src/schemas/shopping_cart.py @@ -1,5 +1,5 @@ from pydantic import BaseModel -from typing import List +from typing import List, Optional class CartCreate(BaseModel): @@ -14,10 +14,14 @@ class CartItemDetail(BaseModel): movie_id: int title: str price: float - genre: str + genre: Optional[str] release_year: int class CartResponse(BaseModel): user_id: int movies: List[CartItemDetail] + + +class PurchasedMoviesResponse(BaseModel): + purchased_movies: List[str] From 73c860982a515fbd3df20a29aca32c97e88fa294 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 12:19:10 +0200 Subject: [PATCH 09/20] removed auxiliary func from routes to crud & improve some crud foo --- src/database/crud/shopping_cart.py | 53 ++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 3b94701..d8ba2f5 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -1,44 +1,77 @@ +from typing import Type + +from fastapi import HTTPException from sqlalchemy.orm import Session from database import ( Cart, User, Movie, - CartItem + CartItem, + Order ) +from schemas.shopping_cart import CartItemDetail -def get_user_cart(user: User, db: Session): +def get_user_cart(user: User, db: Session) -> Cart | None: return db.query(Cart).filter(Cart.user_id == user.id).first() -def get_movie_by_id(movie_id: int, db: Session): +def get_movie_by_id(movie_id: int, db: Session) -> Movie | None: return db.query(Movie).filter(Movie.id == movie_id).first() -def get_cart_item(cart: Cart, movie_id: int, db: Session): +def get_cart_item(cart: Cart, movie_id: int, db: Session) -> CartItem | None: return db.query(CartItem).filter( CartItem.cart_id == cart.id, CartItem.movie_id == movie_id ).first() -def create_cart(user: User, db: Session): +def create_cart(user: User, db: Session) -> Cart: cart = Cart(user_id=user.id) db.add(cart) - db.flush() + db.commit() + db.refresh(cart) return cart -def add_cart_item(cart: Cart, movie: Movie, db: Session): +def add_cart_item(cart: Cart, movie: Movie, db: Session) -> CartItem: + existing_item = get_cart_item(cart, movie.id, db) + if existing_item: + raise HTTPException(status_code=400, detail="Movie is already in the cart") + cart_item = CartItem(cart_id=cart.id, movie_id=movie.id) db.add(cart_item) db.commit() + db.refresh(cart_item) + return cart_item -def delete_cart_item(cart_item: CartItem, db: Session): +def delete_cart_item(cart_item: CartItem, db: Session) -> None: db.delete(cart_item) db.commit() -def get_purchased_movies(user: User, db: Session): - return db.query(Movie).join(CartItem).join(Cart).filter(Cart.user_id == user.id).all() +def get_purchased_movies_from_db(user: User, db: Session) -> list[Type[Movie]]: + return ( + db.query(Movie) + .join(CartItem) + .join(Cart) + .join(Order, Order.user_id == Cart.user_id) + .filter(Order.user_id == user.id) + .distinct() + .all() + ) + + +def get_cart_items_details(cart): + return [ + CartItemDetail( + movie_id=item.movie.id, + title=item.movie.name, + price=item.movie.price, + genre=item.movie.genres[0].name if item.movie.genres else "Unknown", + release_year=item.movie.year + ) + for item in cart.items + ] if cart else [] From ec0aa1f0d24f63824f06b23442ed7e6510e8fc41 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 12:20:37 +0200 Subject: [PATCH 10/20] updated routes to the main project code style & improved them according to the task --- src/routes/shopping_cart.py | 238 +++++++++++++++++++++++++++++------- 1 file changed, 192 insertions(+), 46 deletions(-) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index a3eb0e8..b54751c 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -1,13 +1,28 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import ( + APIRouter, + Depends, + HTTPException, + status +) from sqlalchemy.orm import Session -from database import CartItem, Cart, User +from config import get_jwt_auth_manager +from database import ( + CartItem, + User, + Order, + PaymentItem, + OrderItem, + Payment, + PaymentStatusEnum +) from database.session_postgresql import get_postgresql_db +from schemas.accounts import MessageResponseSchema from schemas.shopping_cart import ( CartCreate, CartResponse, CartItemResponse, - CartItemDetail + PurchasedMoviesResponse ) from database.crud.shopping_cart import ( get_user_cart, @@ -16,23 +31,41 @@ create_cart, add_cart_item, delete_cart_item, - get_purchased_movies + get_purchased_movies_from_db, + get_cart_items_details ) +from security.http import get_token from validation.shopping_cart import ( validate_not_in_cart, validate_not_purchased, validate_movie_availability ) -from security import get_current_user -router = APIRouter(prefix="/cart", tags=["Shopping Cart"]) +router = APIRouter() @router.get("/", response_model=CartResponse) def get_cart( db: Session = Depends(get_postgresql_db), - user: User = Depends(get_current_user) + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + cart = get_user_cart(user, db) if not cart: return CartResponse(user_id=user.id, movies=[]) @@ -43,8 +76,25 @@ def get_cart( def add_to_cart( cart_data: CartCreate, db: Session = Depends(get_postgresql_db), - user: User = Depends(get_current_user) + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + movie = get_movie_by_id(cart_data.movie_id, db) if not movie: raise HTTPException(status_code=404, detail="Movie not found") @@ -64,8 +114,25 @@ def add_to_cart( def remove_from_cart( movie_id: int, db: Session = Depends(get_postgresql_db), - user: User = Depends(get_current_user) + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + cart = get_user_cart(user, db) if not cart: raise HTTPException(status_code=404, detail="Cart not found") @@ -74,15 +141,32 @@ def remove_from_cart( if not cart_item: raise HTTPException(status_code=404, detail="Movie not in cart") - # notify_moderators(f"User {user.id} removed movie {cart_item.movie.name} from the cart.") delete_cart_item(cart_item, db) return CartItemResponse(message="Movie removed from cart") @router.delete("/clear", response_model=CartItemResponse) def clear_cart( - db: Session = Depends(get_postgresql_db), user: User = Depends(get_current_user) + db: Session = Depends(get_postgresql_db), + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + cart = get_user_cart(user, db) if not cart or not cart.items: raise HTTPException(status_code=404, detail="Cart is already empty") @@ -93,46 +177,108 @@ def clear_cart( return CartItemResponse(message="Cart cleared successfully") -@router.get("/purchased") -def list_purchased_movies( +@router.post("/checkout") +def checkout( db: Session = Depends(get_postgresql_db), - user: User = Depends(get_current_user) + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): - purchased_movies = get_purchased_movies(user, db) + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) - return {"purchased_movies": [movie.name for movie in purchased_movies]} + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + if not user.is_active: + raise HTTPException( + status_code=403, + detail="Please activate your account before making a purchase." + ) -@router.get("/admin/carts") -def get_all_carts( + cart = get_user_cart(user, db) + if not cart or not cart.items: + raise HTTPException(status_code=400, detail="Your cart is empty") + + order = Order( + user_id=user.id, + total_amount=sum(item.movie.price for item in cart.items) + ) + db.add(order) + db.commit() + db.refresh(order) + + order_items = [] + for item in cart.items: + order_item = OrderItem( + order_id=order.id, + movie_id=item.movie.id, + price_at_order=item.movie.price + ) + db.add(order_item) + order_items.append(order_item) + + db.commit() + + payment = Payment( + user_id=user.id, + order_id=order.id, + status=PaymentStatusEnum.PENDING, + amount=order.total_amount, + external_payment_id=None + ) + db.add(payment) + db.commit() + db.refresh(payment) + + for order_item in order_items: + payment_item = PaymentItem( + payment_id=payment.id, + order_item_id=order_item.id, + price_at_payment=order_item.price_at_order + ) + db.add(payment_item) + + db.commit() + + db.query(CartItem).filter(CartItem.cart_id == cart.id).delete() + db.commit() + + return MessageResponseSchema(message="Payment successful") + + +@router.get("/purchased") +def get_purchased_movies( db: Session = Depends(get_postgresql_db), - user: User = Depends(get_current_user) + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) ): - if user.group.name != "moderator": - raise HTTPException(status_code=403, detail="You are not authorized") - - all_carts = db.query(Cart).all() - return { - "carts": [ - { - "user_id": cart.user_id, - "movies": [item.movie.name for item in cart.items] - } - for cart in all_carts - ] - } - - -def get_cart_items_details(cart): - return [ - CartItemDetail( - movie_id=item.movie.id, - title=item.movie.name, - price=item.movie.price, - genre=( - item.movie.genres[0].name if item.movie.genres else "Unknown" - ), - release_year=item.movie.year + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) ) - for item in cart.items - ] if cart else [] + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + purchased_movies = get_purchased_movies_from_db(user, db) + return PurchasedMoviesResponse( + purchased_movies=[movie.name for movie in purchased_movies] + ) From e36c12e1b507e21c722899d220f8e4a16ffa1160 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 12:37:56 +0200 Subject: [PATCH 11/20] added functionality for admin so they can view the contents of users' carts --- src/routes/shopping_cart.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index b54751c..4f84650 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -14,7 +14,8 @@ PaymentItem, OrderItem, Payment, - PaymentStatusEnum + PaymentStatusEnum, + UserGroupEnum ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema @@ -282,3 +283,34 @@ def get_purchased_movies( return PurchasedMoviesResponse( purchased_movies=[movie.name for movie in purchased_movies] ) + + +@router.get("/admin/{user_id}", response_model=CartResponse) +def get_user_cart_admin( + user_id: int, + db: Session = Depends(get_postgresql_db), + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) +): + try: + payload = jwt_manager.decode_access_token(token) + admin_id = payload.get("user_id") + admin = db.query(User).filter(User.id == admin_id).first() + + if not admin or not admin.has_group(UserGroupEnum.ADMIN): + raise HTTPException(status_code=403, detail="Access denied") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + + cart = get_user_cart(user, db) + if not cart: + return CartResponse(user_id=user.id, movies=[]) + + return CartResponse(user_id=user.id, movies=get_cart_items_details(cart)) From 1abc2fd1129d05b9c62fa9f4e62e1137fb61938e Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 12:49:23 +0200 Subject: [PATCH 12/20] added route for notifying moderators when attempting to delete a movie that exists in users' carts --- src/routes/shopping_cart.py | 51 ++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 4f84650..75164b0 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -15,7 +15,8 @@ OrderItem, Payment, PaymentStatusEnum, - UserGroupEnum + UserGroupEnum, + Movie ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema @@ -178,7 +179,7 @@ def clear_cart( return CartItemResponse(message="Cart cleared successfully") -@router.post("/checkout") +@router.post("/checkout", response_model=MessageResponseSchema) def checkout( db: Session = Depends(get_postgresql_db), token: str = Depends(get_token), @@ -257,7 +258,7 @@ def checkout( return MessageResponseSchema(message="Payment successful") -@router.get("/purchased") +@router.get("/purchased", response_model=PurchasedMoviesResponse) def get_purchased_movies( db: Session = Depends(get_postgresql_db), token: str = Depends(get_token), @@ -314,3 +315,47 @@ def get_user_cart_admin( return CartResponse(user_id=user.id, movies=[]) return CartResponse(user_id=user.id, movies=get_cart_items_details(cart)) + + +@router.delete( + "/admin/movies/{movie_id}", + response_model=MessageResponseSchema +) +def delete_movie( + movie_id: int, + db: Session = Depends(get_postgresql_db), + token: str = Depends(get_token), + jwt_manager=Depends(get_jwt_auth_manager) +): + try: + payload = jwt_manager.decode_access_token(token) + user_id = payload.get("user_id") + user = db.query(User).filter(User.id == user_id).first() + + if not user or not ( + user.has_group(UserGroupEnum.ADMIN) + or user.has_group(UserGroupEnum.MODERATOR) + ): + raise HTTPException(status_code=403, detail="Access denied") + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(e) + ) + + movie = db.query(Movie).filter(Movie.id == movie_id).first() + if not movie: + raise HTTPException(status_code=404, detail="Movie not found") + + cart_items = ( + db.query(CartItem).filter(CartItem.movie_id == movie.id).count() + ) + if cart_items > 0: + raise HTTPException( + status_code=400, + detail="Movie cannot be deleted because it exists in user carts" + ) + + db.delete(movie) + db.commit() + return MessageResponseSchema(message="Movie deleted successfully") From bdc910917e1d09dfdd68c3e13904bed69b27d538 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 13:13:32 +0200 Subject: [PATCH 13/20] fixed imoprts with ruff --- src/database/crud/shopping_cart.py | 8 +----- src/main.py | 1 - src/routes/shopping_cart.py | 39 +++++++++++++----------------- src/schemas/shopping_cart.py | 3 ++- src/validation/shopping_cart.py | 2 +- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index d8ba2f5..0297e5e 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -3,13 +3,7 @@ from fastapi import HTTPException from sqlalchemy.orm import Session -from database import ( - Cart, - User, - Movie, - CartItem, - Order -) +from database import Cart, CartItem, Movie, Order, User from schemas.shopping_cart import CartItemDetail diff --git a/src/main.py b/src/main.py index 9934a3b..01f37e7 100644 --- a/src/main.py +++ b/src/main.py @@ -3,7 +3,6 @@ from routes import accounts_router, profiles_router from routes.shopping_cart import router as shopping_carts_router - app = FastAPI( title="Movies Cinema", ) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 75164b0..a0d1211 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -1,46 +1,41 @@ -from fastapi import ( - APIRouter, - Depends, - HTTPException, - status -) +from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from config import get_jwt_auth_manager from database import ( CartItem, - User, + Movie, Order, - PaymentItem, OrderItem, Payment, + PaymentItem, PaymentStatusEnum, + User, UserGroupEnum, - Movie +) +from database.crud.shopping_cart import ( + add_cart_item, + create_cart, + delete_cart_item, + get_cart_item, + get_cart_items_details, + get_movie_by_id, + get_purchased_movies_from_db, + get_user_cart, ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema from schemas.shopping_cart import ( CartCreate, - CartResponse, CartItemResponse, - PurchasedMoviesResponse -) -from database.crud.shopping_cart import ( - get_user_cart, - get_movie_by_id, - get_cart_item, - create_cart, - add_cart_item, - delete_cart_item, - get_purchased_movies_from_db, - get_cart_items_details + CartResponse, + PurchasedMoviesResponse, ) from security.http import get_token from validation.shopping_cart import ( + validate_movie_availability, validate_not_in_cart, validate_not_purchased, - validate_movie_availability ) router = APIRouter() diff --git a/src/schemas/shopping_cart.py b/src/schemas/shopping_cart.py index 97cdcad..ed565d0 100644 --- a/src/schemas/shopping_cart.py +++ b/src/schemas/shopping_cart.py @@ -1,6 +1,7 @@ -from pydantic import BaseModel from typing import List, Optional +from pydantic import BaseModel + class CartCreate(BaseModel): movie_id: int diff --git a/src/validation/shopping_cart.py b/src/validation/shopping_cart.py index 6107bdc..3eae43e 100644 --- a/src/validation/shopping_cart.py +++ b/src/validation/shopping_cart.py @@ -1,7 +1,7 @@ from fastapi import HTTPException from sqlalchemy.orm import Session -from database import Movie, User, CartItem, Cart +from database import Cart, CartItem, Movie, User def validate_movie_availability(movie: Movie): From 99d1550c93309d124b20821ab6e8d565dfd6fa70 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 15:56:48 +0200 Subject: [PATCH 14/20] separated db logic from routes into crud --- src/database/crud/shopping_cart.py | 85 +++++++++++++++++++++++++++++- src/routes/shopping_cart.py | 68 ++++++------------------ 2 files changed, 99 insertions(+), 54 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 0297e5e..055d43c 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -1,9 +1,19 @@ -from typing import Type +from typing import Type, List from fastapi import HTTPException from sqlalchemy.orm import Session -from database import Cart, CartItem, Movie, Order, User +from database import ( + Cart, + CartItem, + Movie, + Order, + User, + OrderItem, + Payment, + PaymentItem, + PaymentStatusEnum +) from schemas.shopping_cart import CartItemDetail @@ -46,6 +56,77 @@ def delete_cart_item(cart_item: CartItem, db: Session) -> None: db.commit() +def delete_cart_item_by_cart(db: Session, cart_id: int) -> None: + db.query(CartItem).filter(CartItem.cart_id == cart_id).delete() + db.commit() + + +def create_order(db: Session, order: Order) -> None: + db.add(order) + db.commit() + db.refresh(order) + + +def create_order_items(db: Session, order: Order, cart: Cart) -> List[OrderItem]: + order_items: List[OrderItem] = [] + for item in cart.items: + order_item = OrderItem( + order_id=order.id, + movie_id=item.movie.id, + price_at_order=item.movie.price + ) + db.add(order_item) + order_items.append(order_item) + + db.commit() + return order_items + + +def create_payment(db: Session, user: User, order: Order) -> Payment: + payment = Payment( + user_id=user.id, + order_id=order.id, + status=PaymentStatusEnum.PENDING, + amount=order.total_amount, + external_payment_id=None + ) + db.add(payment) + db.commit() + db.refresh(payment) + return payment + + +def create_payment_items(db: Session, payment: Payment, order_items: List[OrderItem]) -> None: + for order_item in order_items: + payment_item = PaymentItem( + payment_id=payment.id, + order_item_id=order_item.id, + price_at_payment=order_item.price_at_order + ) + db.add(payment_item) + + db.commit() + + +def process_order_payment_and_clear_cart(db: Session, user: User, order: Order, cart: Cart) -> Payment: + order_items = create_order_items(db, order, cart) + payment = create_payment(db, user, order) + create_payment_items(db, payment, order_items) + + delete_cart_item_by_cart(db, cart.id) + + return payment + + +def is_movie_in_any_cart(db: Session, movie_id: int) -> bool: + return db.query(CartItem).filter(CartItem.movie_id == movie_id).count() > 0 + + +def delete_movie(db: Session, movie: Movie) -> None: + db.delete(movie) + db.commit() + + def get_purchased_movies_from_db(user: User, db: Session) -> list[Type[Movie]]: return ( db.query(Movie) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index a0d1211..4564368 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -13,6 +13,7 @@ User, UserGroupEnum, ) +from database.crud.accounts import get_user_by_id from database.crud.shopping_cart import ( add_cart_item, create_cart, @@ -22,6 +23,10 @@ get_movie_by_id, get_purchased_movies_from_db, get_user_cart, + delete_cart_item_by_cart, + create_order, + process_order_payment_and_clear_cart, + is_movie_in_any_cart, ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema @@ -123,7 +128,7 @@ def remove_from_cart( detail=str(e) ) - user = db.query(User).filter(User.id == user_id).first() + user = get_user_by_id(db, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -157,7 +162,7 @@ def clear_cart( detail=str(e) ) - user = db.query(User).filter(User.id == user_id).first() + user = get_user_by_id(db, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -168,8 +173,7 @@ def clear_cart( if not cart or not cart.items: raise HTTPException(status_code=404, detail="Cart is already empty") - db.query(CartItem).filter(CartItem.cart_id == cart.id).delete() - db.commit() + delete_cart_item_by_cart(db, cart.id) return CartItemResponse(message="Cart cleared successfully") @@ -189,7 +193,7 @@ def checkout( detail=str(e) ) - user = db.query(User).filter(User.id == user_id).first() + user = get_user_by_id(db, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -210,45 +214,9 @@ def checkout( user_id=user.id, total_amount=sum(item.movie.price for item in cart.items) ) - db.add(order) - db.commit() - db.refresh(order) - - order_items = [] - for item in cart.items: - order_item = OrderItem( - order_id=order.id, - movie_id=item.movie.id, - price_at_order=item.movie.price - ) - db.add(order_item) - order_items.append(order_item) + create_order(db, order) - db.commit() - - payment = Payment( - user_id=user.id, - order_id=order.id, - status=PaymentStatusEnum.PENDING, - amount=order.total_amount, - external_payment_id=None - ) - db.add(payment) - db.commit() - db.refresh(payment) - - for order_item in order_items: - payment_item = PaymentItem( - payment_id=payment.id, - order_item_id=order_item.id, - price_at_payment=order_item.price_at_order - ) - db.add(payment_item) - - db.commit() - - db.query(CartItem).filter(CartItem.cart_id == cart.id).delete() - db.commit() + process_order_payment_and_clear_cart(db, user, order, cart) return MessageResponseSchema(message="Payment successful") @@ -268,7 +236,7 @@ def get_purchased_movies( detail=str(e) ) - user = db.query(User).filter(User.id == user_id).first() + user = get_user_by_id(db, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -301,7 +269,7 @@ def get_user_cart_admin( detail=str(e) ) - user = db.query(User).filter(User.id == user_id).first() + user = get_user_by_id(db, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") @@ -338,19 +306,15 @@ def delete_movie( detail=str(e) ) - movie = db.query(Movie).filter(Movie.id == movie_id).first() + movie = get_movie_by_id(db, movie_id) if not movie: raise HTTPException(status_code=404, detail="Movie not found") - cart_items = ( - db.query(CartItem).filter(CartItem.movie_id == movie.id).count() - ) - if cart_items > 0: + if is_movie_in_any_cart(db, movie_id): raise HTTPException( status_code=400, detail="Movie cannot be deleted because it exists in user carts" ) - db.delete(movie) - db.commit() + delete_movie(db, movie) return MessageResponseSchema(message="Movie deleted successfully") From aaf688fd921d8ef9b5a6936419fa16d68c0fdef1 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 16:06:18 +0200 Subject: [PATCH 15/20] fixed code with ruff --- src/database/crud/shopping_cart.py | 19 ++++++++++++++----- src/routes/shopping_cart.py | 6 +++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 055d43c..764ccc3 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -1,4 +1,4 @@ -from typing import Type, List +from typing import List, Type from fastapi import HTTPException from sqlalchemy.orm import Session @@ -8,11 +8,11 @@ CartItem, Movie, Order, - User, OrderItem, Payment, PaymentItem, - PaymentStatusEnum + PaymentStatusEnum, + User, ) from schemas.shopping_cart import CartItemDetail @@ -96,7 +96,11 @@ def create_payment(db: Session, user: User, order: Order) -> Payment: return payment -def create_payment_items(db: Session, payment: Payment, order_items: List[OrderItem]) -> None: +def create_payment_items( + db: Session, + payment: Payment, + order_items: List[OrderItem] +) -> None: for order_item in order_items: payment_item = PaymentItem( payment_id=payment.id, @@ -108,7 +112,12 @@ def create_payment_items(db: Session, payment: Payment, order_items: List[OrderI db.commit() -def process_order_payment_and_clear_cart(db: Session, user: User, order: Order, cart: Cart) -> Payment: +def process_order_payment_and_clear_cart( + db: Session, + user: User, + order: Order, + cart: Cart +) -> Payment: order_items = create_order_items(db, order, cart) payment = create_payment(db, user, order) create_payment_items(db, payment, order_items) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 4564368..2b375e7 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -17,16 +17,16 @@ from database.crud.shopping_cart import ( add_cart_item, create_cart, + create_order, delete_cart_item, + delete_cart_item_by_cart, get_cart_item, get_cart_items_details, get_movie_by_id, get_purchased_movies_from_db, get_user_cart, - delete_cart_item_by_cart, - create_order, - process_order_payment_and_clear_cart, is_movie_in_any_cart, + process_order_payment_and_clear_cart, ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema From b1913abea06ece3810c84d1270f7f507112ece0e Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 17:57:21 +0200 Subject: [PATCH 16/20] fixed some typy annotations --- src/database/crud/shopping_cart.py | 6 +++--- src/routes/shopping_cart.py | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 764ccc3..ed9f6ef 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -128,7 +128,7 @@ def process_order_payment_and_clear_cart( def is_movie_in_any_cart(db: Session, movie_id: int) -> bool: - return db.query(CartItem).filter(CartItem.movie_id == movie_id).count() > 0 + return bool(db.query(CartItem).filter(CartItem.movie_id == movie_id).count()) def delete_movie(db: Session, movie: Movie) -> None: @@ -136,7 +136,7 @@ def delete_movie(db: Session, movie: Movie) -> None: db.commit() -def get_purchased_movies_from_db(user: User, db: Session) -> list[Type[Movie]]: +def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: return ( db.query(Movie) .join(CartItem) @@ -148,7 +148,7 @@ def get_purchased_movies_from_db(user: User, db: Session) -> list[Type[Movie]]: ) -def get_cart_items_details(cart): +def get_cart_items_details(cart) -> List[CartItemDetail]: return [ CartItemDetail( movie_id=item.movie.id, diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 2b375e7..cfecb14 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session @@ -48,10 +50,10 @@ @router.get("/", response_model=CartResponse) def get_cart( - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[object, Depends(get_jwt_auth_manager)] +) -> CartResponse: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") From de393810c3968d808f964684d9853e76dc20de38 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 18:04:58 +0200 Subject: [PATCH 17/20] fixed return type annotation for jwt_manager --- src/routes/shopping_cart.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index cfecb14..e25b788 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -39,6 +39,7 @@ PurchasedMoviesResponse, ) from security.http import get_token +from security.token_manager import JWTAuthManager from validation.shopping_cart import ( validate_movie_availability, validate_not_in_cart, @@ -52,7 +53,7 @@ def get_cart( db: Annotated[Session, Depends(get_postgresql_db)], token: Annotated[str, Depends(get_token)], - jwt_manager: Annotated[object, Depends(get_jwt_auth_manager)] + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] ) -> CartResponse: try: payload = jwt_manager.decode_access_token(token) From af9c679bcf40ea2fad29e216302f780461a92439 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 18:23:20 +0200 Subject: [PATCH 18/20] fixed mypy --- src/database/crud/shopping_cart.py | 6 ++-- src/routes/shopping_cart.py | 56 +++++++++++++++--------------- src/validation/shopping_cart.py | 6 ++-- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index ed9f6ef..59b2991 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -1,4 +1,4 @@ -from typing import List, Type +from typing import Any, List, Optional from fastapi import HTTPException from sqlalchemy.orm import Session @@ -136,7 +136,7 @@ def delete_movie(db: Session, movie: Movie) -> None: db.commit() -def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: +def get_purchased_movies_from_db(user: User, db: Session) -> list[Any]: return ( db.query(Movie) .join(CartItem) @@ -148,7 +148,7 @@ def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: ) -def get_cart_items_details(cart) -> List[CartItemDetail]: +def get_cart_items_details(cart: Optional[Cart]) -> List[CartItemDetail]: return [ CartItemDetail( movie_id=item.movie.id, diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index e25b788..32c5778 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -80,10 +80,10 @@ def get_cart( @router.post("/add", response_model=CartResponse) def add_to_cart( cart_data: CartCreate, - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> CartResponse: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") @@ -118,10 +118,10 @@ def add_to_cart( @router.delete("/remove/{movie_id}", response_model=CartItemResponse) def remove_from_cart( movie_id: int, - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> CartItemResponse: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") @@ -152,10 +152,10 @@ def remove_from_cart( @router.delete("/clear", response_model=CartItemResponse) def clear_cart( - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> CartItemResponse: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") @@ -183,10 +183,10 @@ def clear_cart( @router.post("/checkout", response_model=MessageResponseSchema) def checkout( - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> MessageResponseSchema: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") @@ -226,10 +226,10 @@ def checkout( @router.get("/purchased", response_model=PurchasedMoviesResponse) def get_purchased_movies( - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> PurchasedMoviesResponse: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") @@ -255,10 +255,10 @@ def get_purchased_movies( @router.get("/admin/{user_id}", response_model=CartResponse) def get_user_cart_admin( user_id: int, - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> CartResponse: try: payload = jwt_manager.decode_access_token(token) admin_id = payload.get("user_id") @@ -289,10 +289,10 @@ def get_user_cart_admin( ) def delete_movie( movie_id: int, - db: Session = Depends(get_postgresql_db), - token: str = Depends(get_token), - jwt_manager=Depends(get_jwt_auth_manager) -): + db: Annotated[Session, Depends(get_postgresql_db)], + token: Annotated[str, Depends(get_token)], + jwt_manager: Annotated[JWTAuthManager, Depends(get_jwt_auth_manager)] +) -> MessageResponseSchema: try: payload = jwt_manager.decode_access_token(token) user_id = payload.get("user_id") diff --git a/src/validation/shopping_cart.py b/src/validation/shopping_cart.py index 3eae43e..98fa319 100644 --- a/src/validation/shopping_cart.py +++ b/src/validation/shopping_cart.py @@ -4,7 +4,7 @@ from database import Cart, CartItem, Movie, User -def validate_movie_availability(movie: Movie): +def validate_movie_availability(movie: Movie | None) -> None: if not movie: raise HTTPException( status_code=400, @@ -12,7 +12,7 @@ def validate_movie_availability(movie: Movie): ) -def validate_not_purchased(user: User, movie: Movie, db: Session): +def validate_not_purchased(user: User, movie: Movie, db: Session) -> None: purchased_movies = db.query(CartItem).join(Cart).filter( Cart.user_id == user.id, CartItem.movie_id == movie.id @@ -24,7 +24,7 @@ def validate_not_purchased(user: User, movie: Movie, db: Session): ) -def validate_not_in_cart(user: User, movie: Movie, db: Session): +def validate_not_in_cart(user: User, movie: Movie, db: Session) -> None: cart = db.query(Cart).filter(Cart.user_id == user.id).first() if cart: existing_item = db.query(CartItem).filter( From 6ad98531b9428be5fd9c035ad609fc0ba3032de3 Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 18:29:51 +0200 Subject: [PATCH 19/20] fixed mypy --- src/database/crud/shopping_cart.py | 2 +- src/routes/shopping_cart.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 59b2991..03e1ae1 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -136,7 +136,7 @@ def delete_movie(db: Session, movie: Movie) -> None: db.commit() -def get_purchased_movies_from_db(user: User, db: Session) -> list[Any]: +def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: return ( db.query(Movie) .join(CartItem) diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 32c5778..5c56897 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -28,7 +28,7 @@ get_purchased_movies_from_db, get_user_cart, is_movie_in_any_cart, - process_order_payment_and_clear_cart, + process_order_payment_and_clear_cart, delete_movie, ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema @@ -287,7 +287,7 @@ def get_user_cart_admin( "/admin/movies/{movie_id}", response_model=MessageResponseSchema ) -def delete_movie( +def delete_movie_route( movie_id: int, db: Annotated[Session, Depends(get_postgresql_db)], token: Annotated[str, Depends(get_token)], @@ -309,7 +309,7 @@ def delete_movie( detail=str(e) ) - movie = get_movie_by_id(db, movie_id) + movie = get_movie_by_id(movie_id, db) if not movie: raise HTTPException(status_code=404, detail="Movie not found") From 28cc15ef3a28d1b4ddeb4a1e2bb7e550a55bb42e Mon Sep 17 00:00:00 2001 From: Andrey Dementyev Date: Mon, 3 Feb 2025 18:41:08 +0200 Subject: [PATCH 20/20] deleted unusable imports --- src/database/crud/shopping_cart.py | 5 +++-- src/routes/shopping_cart.py | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/database/crud/shopping_cart.py b/src/database/crud/shopping_cart.py index 03e1ae1..11b3cbc 100644 --- a/src/database/crud/shopping_cart.py +++ b/src/database/crud/shopping_cart.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional +from typing import List, Optional, cast from fastapi import HTTPException from sqlalchemy.orm import Session @@ -137,7 +137,7 @@ def delete_movie(db: Session, movie: Movie) -> None: def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: - return ( + result = ( db.query(Movie) .join(CartItem) .join(Cart) @@ -146,6 +146,7 @@ def get_purchased_movies_from_db(user: User, db: Session) -> list[Movie]: .distinct() .all() ) + return cast(List[Movie], result) def get_cart_items_details(cart: Optional[Cart]) -> List[CartItemDetail]: diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py index 5c56897..b8d39ae 100644 --- a/src/routes/shopping_cart.py +++ b/src/routes/shopping_cart.py @@ -5,13 +5,7 @@ from config import get_jwt_auth_manager from database import ( - CartItem, - Movie, Order, - OrderItem, - Payment, - PaymentItem, - PaymentStatusEnum, User, UserGroupEnum, ) @@ -22,13 +16,14 @@ create_order, delete_cart_item, delete_cart_item_by_cart, + delete_movie, get_cart_item, get_cart_items_details, get_movie_by_id, get_purchased_movies_from_db, get_user_cart, is_movie_in_any_cart, - process_order_payment_and_clear_cart, delete_movie, + process_order_payment_and_clear_cart, ) from database.session_postgresql import get_postgresql_db from schemas.accounts import MessageResponseSchema