From 11c696d9bbb8f09c255c5317ac243559731b8170 Mon Sep 17 00:00:00 2001 From: Jonatas Oliveira Date: Sat, 19 Oct 2024 10:19:33 +0200 Subject: [PATCH] refactor: refactor queries for oders --- app/entities/order.py | 2 + app/entities/payment.py | 4 +- app/entities/user.py | 14 +++++++ app/infra/models.py | 85 +++++++++++++++++++++++++--------------- app/order/services.py | 53 +++++++++++++++++++++---- app/report/repository.py | 8 +++- app/user/repository.py | 13 ++++-- app/user/services.py | 35 ++++++++++++----- 8 files changed, 158 insertions(+), 56 deletions(-) diff --git a/app/entities/order.py b/app/entities/order.py index f90c35a1..86be6085 100644 --- a/app/entities/order.py +++ b/app/entities/order.py @@ -2,6 +2,7 @@ from decimal import Decimal from pydantic import BaseModel, ConfigDict, SecretStr +from app.entities.payment import PaymentInDB from app.entities.product import ProductInDB from app.entities.user import UserInDB @@ -46,6 +47,7 @@ class OrderInDB(BaseModel): freight: str | None coupon_id: int | None items: list[OrderItemInDB] | None + payment: PaymentInDB | None = None model_config = ConfigDict(from_attributes=True) diff --git a/app/entities/payment.py b/app/entities/payment.py index fce2f176..8d16cf92 100644 --- a/app/entities/payment.py +++ b/app/entities/payment.py @@ -142,8 +142,8 @@ class PaymentInDB(BaseModel): authorization: str | None payment_method: str | None payment_gateway: str | None - amount_with_fee: int | None - freight_amount: int | None + amount_with_fee: Decimal | None + freight_amount: Decimal | None model_config = ConfigDict(from_attributes=True) diff --git a/app/entities/user.py b/app/entities/user.py index eb6839f9..d8654682 100644 --- a/app/entities/user.py +++ b/app/entities/user.py @@ -44,6 +44,19 @@ class UserAddress(BaseModel): zip_code: str +class UserAddressInDB(BaseModel): + user_id: int | None + street: str + street_number: str + address_complement: str + neighborhood: str + city: str + state: str + country: str + zipcode: str + model_config = ConfigDict(from_attributes=True) + + class UserDBGet(BaseModel): user_id: int name: str @@ -105,6 +118,7 @@ class UserSchema(BaseModel): email: str | None = None full_name: str | None = None disabled: bool | None = None + addresses: list[UserAddressInDB] | None = None model_config = ConfigDict(from_attributes=True) diff --git a/app/infra/models.py b/app/infra/models.py index 3f7bcf5c..23245f60 100644 --- a/app/infra/models.py +++ b/app/infra/models.py @@ -142,6 +142,39 @@ class CouponsDB(Base): ) +class PaymentDB(Base): + __tablename__ = 'payment' + + payment_id: Mapped[int] = mapped_column(primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey('user.user_id')) + user: Mapped['UserDB'] = relationship( + foreign_keys=[user_id], + backref='payment', + cascade='all,delete', + uselist=False, + ) + order_id: Mapped[int] = mapped_column(ForeignKey('order.order_id')) + amount: Mapped[Decimal] + amount_with_fee: Mapped[Decimal] = mapped_column(server_default='0') + token: Mapped[str] + gateway_payment_id: Mapped[int] = mapped_column(BIGINT, server_default='0') + status: Mapped[str] + authorization: Mapped[str] + payment_method: Mapped[str] + payment_gateway: Mapped[str] + installments: Mapped[int] + processed: Mapped[bool] = mapped_column(default=False) + processed_at: Mapped[datetime | None] + freight_amount: Mapped[Decimal] = mapped_column(server_default='0') + + order: Mapped['OrderDB'] = relationship( + 'OrderDB', + back_populates='payment', + foreign_keys=[order_id], # Verifique se essa linha está correta + uselist=False, + ) + + class OrderDB(Base): __tablename__ = 'order' @@ -178,6 +211,13 @@ class OrderDB(Base): cascade='all,delete', foreign_keys='OrderItemsDB.order_id', lazy='joined', + overlaps="order_items", + ) + payment: Mapped['PaymentDB'] = relationship( + 'PaymentDB', + foreign_keys=[PaymentDB.order_id], + back_populates="order", + uselist=False, ) @@ -192,6 +232,7 @@ class OrderItemsDB(Base): cascade='all,delete', foreign_keys=[order_id], lazy='joined', + overlaps="orders, items", ) product_id: Mapped[int] = mapped_column(ForeignKey('product.product_id')) product = relationship( @@ -268,38 +309,6 @@ class CustomerDB(Base): created_at: Mapped[datetime] = mapped_column(default=func.now()) -class PaymentDB(Base): - __tablename__ = 'payment' - - payment_id: Mapped[int] = mapped_column(primary_key=True) - user_id: Mapped[int] = mapped_column(ForeignKey('user.user_id')) - user: Mapped['UserDB'] = relationship( - foreign_keys=[user_id], - backref='payment', - cascade='all,delete', - uselist=False, - ) - order_id: Mapped[int] = mapped_column(ForeignKey('order.order_id')) - order: Mapped['OrderDB'] = relationship( - foreign_keys=[order_id], - backref='payment', - cascade='all,delete', - uselist=False, - ) - amount: Mapped[Decimal] - amount_with_fee: Mapped[Decimal] = mapped_column(server_default='0') - token: Mapped[str] - gateway_payment_id: Mapped[int] = mapped_column(BIGINT, server_default='0') - status: Mapped[str] - authorization: Mapped[str] - payment_method: Mapped[str] - payment_gateway: Mapped[str] - installments: Mapped[int] - processed: Mapped[bool] = mapped_column(default=False) - processed_at: Mapped[datetime | None] - freight_amount: Mapped[Decimal] = mapped_column(server_default='0') - - class CreditCardFeeConfigDB(Base): __tablename__ = 'credit_card_fee_config' @@ -358,6 +367,13 @@ class UserDB(Base): server_default='0', ) + addresses: Mapped[list['AddressDB'] | None] = relationship( + 'AddressDB', + back_populates='user', + cascade='all, delete', + lazy='joined', + ) + class AddressDB(Base): __tablename__ = 'address' @@ -375,6 +391,11 @@ class AddressDB(Base): uuid: Mapped[str] = mapped_column(nullable=True) active: Mapped[bool] = mapped_column(default=False) + user: Mapped['UserDB'] = relationship( + 'UserDB', + back_populates='addresses', + ) + class UserResetPasswordDB(Base): __tablename__ = 'user_reset_password' diff --git a/app/order/services.py b/app/order/services.py index dd56b6ed..20d805c7 100644 --- a/app/order/services.py +++ b/app/order/services.py @@ -3,12 +3,13 @@ import math from typing import Any +from app.entities.user import UserAddressInDB from app.infra.constants import OrderStatus, PaymentStatus from fastapi import HTTPException, status from loguru import logger from pydantic import TypeAdapter from sqlalchemy import asc, func, select -from sqlalchemy.orm import Session, lazyload, selectinload +from sqlalchemy.orm import Session, joinedload, lazyload, selectinload from app.product import repository from faststream.rabbit import RabbitQueue @@ -23,12 +24,14 @@ ) from app.infra.file_upload import optimize_image from app.infra.models import ( + AddressDB, CategoryDB, ImageGalleryDB, InventoryDB, ProductDB, OrderDB, OrderItemsDB, + UserDB, ) from app.user.services import verify_admin from app.entities.order import ( @@ -241,8 +244,11 @@ def get_orders(page, offset, *, db): """Get Orders Paid.""" with db().begin() as db: orders_query = ( - select(OrderDB) - .options(selectinload(OrderDB.items)) + select(OrderDB).options( + joinedload(OrderDB.user), + joinedload(OrderDB.payment), + selectinload(OrderDB.items), + ) .order_by(OrderDB.order_id.desc()) ) total_records = db.session.scalar(select(func.count(OrderDB.order_id))) @@ -250,7 +256,7 @@ def get_orders(page, offset, *, db): orders_query = orders_query.offset((page - 1) * offset) orders_query = orders_query.limit(offset) - orders_db = db.session.execute(orders_query) + orders_db = db.session.execute(orders_query).unique() adapter = TypeAdapter(list[OrderInDB]) return OrderResponse( orders=adapter.validate_python(orders_db.scalars().all()), @@ -265,7 +271,11 @@ def get_user_order(db: Session, user_id: int) -> list[OrderUserListResponse]: """Given order_id return Order with user data.""" with db: order_query = ( - select(OrderDB) + select(OrderDB).options( + joinedload(OrderDB.user), + joinedload(OrderDB.payment), + selectinload(OrderDB.items), + ) .where(OrderDB.user_id == user_id) .order_by(OrderDB.order_id.desc()) ) @@ -313,18 +323,45 @@ def get_order(db: Session, order_id: int) -> OrderInDB: with db: order_query = ( select(OrderDB) + .options( + joinedload(OrderDB.user), + joinedload(OrderDB.payment), + joinedload(OrderDB.items).joinedload(OrderItemsDB.product), + joinedload(OrderDB.user).joinedload(UserDB.addresses.and_( + AddressDB.address_id == ( + select(func.max(AddressDB.address_id)) + .where(AddressDB.user_id == OrderDB.user_id) + ), + )), + ) .where(OrderDB.order_id == order_id) ) order = db.scalar(order_query) - return OrderInDB.model_validate(order) + _order = OrderInDB.model_validate(order) + last_address_subquery = ( + select(AddressDB) + .where(AddressDB.user_id == order.user_id) + .order_by(AddressDB.address_id.desc()) + .limit(1) + ) + if order and order.user: + last_address = db.scalar(last_address_subquery) + if last_address: + _order.user.addresses = [UserAddressInDB.model_validate(last_address)] + else: + _order.user.addresses = [] + return _order def delete_order(order_id: int, *, cancel: CancelOrder, db) -> None: """Soft delete order.""" with db: order_query = ( - select(OrderDB) - .options(selectinload(OrderDB.items)) + select(OrderDB).options( + joinedload(OrderDB.user), + joinedload(OrderDB.payment), + selectinload(OrderDB.items), + ) .where(OrderDB.order_id == order_id) ) order = db.scalar(order_query) diff --git a/app/report/repository.py b/app/report/repository.py index cbd99092..97604e26 100644 --- a/app/report/repository.py +++ b/app/report/repository.py @@ -4,7 +4,7 @@ from app.entities.user import UserInDB from app.infra.constants import Roles from sqlalchemy import select, and_ -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, selectinload from pydantic import TypeAdapter from app.infra.models import CoProducerFeeDB, FeeDB, InformUserProductDB, SalesCommissionDB, UserDB @@ -76,7 +76,11 @@ async def update_payment_commissions( async def get_admins(transaction): """Get list of admins.""" async with transaction: - query = select(UserDB).where(UserDB.role_id == Roles.ADMIN.value) + query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where( + UserDB.role_id == Roles.ADMIN.value, + ) admin_db = await transaction.scalars(query) adapter = TypeAdapter(list[UserInDB]) return adapter.validate_python(admin_db) diff --git a/app/user/repository.py b/app/user/repository.py index 03c99b70..97fbcb42 100644 --- a/app/user/repository.py +++ b/app/user/repository.py @@ -3,6 +3,7 @@ from pydantic import TypeAdapter from sqlalchemy import asc, func, select, update +from sqlalchemy.orm import selectinload from sqlalchemy.sql import desc from app.entities.user import UserFilters, UserInDB, UsersDBResponse from app.infra import models @@ -24,7 +25,9 @@ async def get_user_by_id( transaction, ) -> models.UserDB: """Get an user by its id.""" - user_query = select(models.UserDB).where(models.UserDB.user_id == user_id) + user_query = select(models.UserDB).options( + selectinload(models.UserDB.addresses), + ).where(models.UserDB.user_id == user_id) return await transaction.session.scalar(user_query) async def get_user_by_username( @@ -33,7 +36,9 @@ async def get_user_by_username( transaction, ) -> models.UserDB | None: """Get an user by its username.""" - user_query = select(models.UserDB).where( + user_query = select(models.UserDB).options( + selectinload(models.UserDB.addresses), + ).where( models.UserDB.username == username, ) async with transaction: @@ -61,7 +66,9 @@ async def get_users( transaction, ): """Select all users.""" - query_users = select(models.UserDB) + query_users = select(models.UserDB).options( + selectinload(models.UserDB.addresses), + ) if filters.search_name: query_users = query_users.where( models.UserDB.name.like(f'%{filters.search_name}'), diff --git a/app/user/services.py b/app/user/services.py index 72e21405..605b7c9b 100644 --- a/app/user/services.py +++ b/app/user/services.py @@ -29,7 +29,7 @@ from jwt import DecodeError, encode, decode from app.user import repository from config import settings -from sqlalchemy.orm import Session, sessionmaker +from sqlalchemy.orm import Session, selectinload, sessionmaker pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') @@ -100,7 +100,10 @@ def raise_credential_error() -> CredentialError: def check_existent_user(db: Session, document: str, password: str) -> UserDB: """Check if user exist.""" with db: - user_query = select(UserDB).where(UserDB.document == document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses)).where( + UserDB.document == document, + ) db_user = db.execute(user_query).scalars().first() if not password: raise_password_empty_error() @@ -203,7 +206,9 @@ def get_current_user( with db() as session: user = session.scalar( - select(UserDB).where(UserDB.document == document), + select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == document), ) if not user: raise CredentialError @@ -243,7 +248,9 @@ async def save_token_reset_password( expires_delta=access_token_expires, ) with db() as session: - user_query = select(UserDB).where(UserDB.document == document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == document) _user = session.scalar(user_query) db_reset = UserResetPasswordDB(user_id=_user.user_id, token=_token) session.add(db_reset) @@ -266,7 +273,9 @@ def reset_password( """Reset password with token created.""" pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') with db() as session: - user_query = select(UserDB).where(UserDB.document == data.document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == data.document) _user = session.scalar(user_query) used_token_query = select(UserResetPasswordDB).where( @@ -324,7 +333,9 @@ async def verify_admin(token: str, *, db): raise_credential_error() async with db() as session: - user_query = select(UserDB).where(UserDB.document == document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == document) user_db = await session.scalar(user_query) if not user_db: raise CredentialError @@ -337,7 +348,9 @@ async def verify_admin(token: str, *, db): def _get_user_from_document(document: str, *, db: sessionmaker) -> UserSchema: """Get user from database.""" with db() as session: - user_query = select(UserDB).where(UserDB.document == document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == document) user_db = session.scalar(user_query) if not user_db: raise CredentialError @@ -347,7 +360,9 @@ def _get_user_from_document(document: str, *, db: sessionmaker) -> UserSchema: async def _get_user_by_document(document: str, *, db) -> UserSchema: """Get user from database.""" async with db as session: - user_query = select(UserDB).where(UserDB.document == document) + user_query = select(UserDB).options( + selectinload(UserDB.addresses), + ).where(UserDB.document == document) user_db = await session.scalar(user_query) if not user_db: raise CredentialError @@ -400,7 +415,9 @@ def authenticate_user( """Authenticate user.""" with db() as session: user_db = session.scalar( - select(UserDB).where(UserDB.document == document), + select(UserDB) + .where(UserDB.document == document) + .options(selectinload(UserDB.addresses)), ) if not user_db or not verify_password(user_db.password, password): raise_credential_error()