Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
FireFading committed Apr 14, 2023
1 parent c9e2ec0 commit 8323abc
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 34 deletions.
8 changes: 8 additions & 0 deletions app/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from app.crud import CRUD
from app.database import Base
from sqlalchemy import Column, ForeignKey, Integer
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import relationship
from sqlalchemy_utils import UUIDType


Expand All @@ -22,3 +24,9 @@ class OrderItem(Base, CRUD):

order_id = Column(UUIDType(binary=False), ForeignKey("orders.guid"))
product_id = Column(UUIDType(binary=False), ForeignKey("products.guid"))

product = relationship("Product", lazy="joined", backref="order_items")

@classmethod
async def get_items(cls, session: AsyncSession, order_id: uuid.UUID):
return await cls.filter(session=session, order_id=order_id)
11 changes: 10 additions & 1 deletion app/models/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from app.crud import CRUD
from app.database import Base
from sqlalchemy import Column, Float, ForeignKey, String
from sqlalchemy import Column, Float, ForeignKey, Integer, String
from sqlalchemy_utils import UUIDType


Expand All @@ -15,10 +15,19 @@ class Product(Base, CRUD):
producer = Column(String, nullable=True)
price = Column(Float, nullable=False)

avg_rating = Column(Float, nullable=True)
reviews_count = Column(Integer, default=0)

user_id = Column(UUIDType(binary=False), ForeignKey("users.guid"))

def __repr__(self):
return f"{self.name}"

def upgrade_rating(self, rating: float):
if self.avg_rating is None:
self.avg_rating = rating
else:
self.avg_rating = (self.avg_rating * self.reviews_count + rating) / (self.reviews_count + 1)


fields = [column.name for column in Product.__table__.columns]
Empty file added app/repositories.py
Empty file.
40 changes: 40 additions & 0 deletions app/routers/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from app.config import jwt_settings
from app.database import get_session
from app.models.orders import Order as m_Order
from app.models.orders import OrderItem as m_OrderItem
from app.schemas.orders import ShowOrderItem
from app.utils.exceptions import get_user_or_404
from fastapi import APIRouter, Depends, Security, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi_jwt_auth import AuthJWT
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/orders", tags=["orders"], responses={404: {"description": "Not found"}})
security = HTTPBearer()


@AuthJWT.load_config
def get_jwt_settings():
return jwt_settings


@router.get(
"/get/",
status_code=status.HTTP_200_OK,
summary="Получение заказов пользователя",
)
async def get_orders(
session: AsyncSession = Depends(get_session),
authorize: AuthJWT = Depends(),
credentials: HTTPAuthorizationCredentials = Security(security),
):
authorize.jwt_required()
email = authorize.get_jwt_subject()
user = await get_user_or_404(email=email, session=session)
orders = await m_Order.filter(session=session, user_id=user.guid)
order_guids = [order.guid for order in orders]
orders = []
for guid in order_guids:
items = await m_OrderItem.get_items(session, order_id=guid)
[ShowOrderItem.from_orm(order_item).dict() for order_item in items] if items else None
# return [Product.from_orm(product).dict() for product in products] if products else None
2 changes: 1 addition & 1 deletion app/routers/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get_jwt_settings():
@router.get(
"/get/{product_id}",
status_code=status.HTTP_200_OK,
summary="Получение продукта по id",
summary="Получение продукта по guid",
)
async def get_product(product_id: uuid.UUID, session: AsyncSession = Depends(get_session)):
return await m_Product.get(session=session, guid=product_id)
Expand Down
25 changes: 17 additions & 8 deletions app/routers/rating.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

from app.config import jwt_settings
from app.database import get_session
from app.models.products import Product as m_Product
from app.models.ratings import Rating as m_Rating
from app.schemas.rating import CreateRating, Rating
from app.utils.exceptions import get_user_or_404
from app.utils.exceptions import get_product_or_404, get_user_or_404
from app.utils.messages import messages
from fastapi import APIRouter, Depends, HTTPException, Security, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
Expand All @@ -26,17 +25,26 @@ def get_jwt_settings():


@router.get(
"/get/{product_id}",
"/get/{product_id}/",
status_code=status.HTTP_200_OK,
summary="Получение всех оценок продуктов по id",
summary="Получение всех оценок продуктов по guid",
)
async def get_product_ratings(product_id: uuid.UUID, session: AsyncSession = Depends(get_session)):
if not (product := await m_Product.get(guid=product_id, session=session)):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=messages.PRODUCT_NOT_FOUND)
product = await get_product_or_404(guid=product_id, session=session)
ratings = await m_Rating.filter(product_id=product.guid, session=session)
return [Rating.from_orm(rating).dict() for rating in ratings] if ratings else None


@router.get(
"/get/avg/{product_id}/",
status_code=status.HTTP_200_OK,
summary="Получение среднего рейтинга продукта по guid",
)
async def get_product_avg_rating(product_id: uuid.UUID, session: AsyncSession = Depends(get_session)):
product = await get_product_or_404(guid=product_id, session=session)
return {"avg_rating": product.avg_rating}


@router.post("/new/", status_code=status.HTTP_201_CREATED, summary="Добавление новой оценки")
async def create_new_ratings(
create_rating: CreateRating,
Expand All @@ -52,7 +60,8 @@ async def create_new_ratings(
status_code=status.HTTP_400_BAD_REQUEST,
detail=messages.RATING_ALREADY_EXISTS,
)
if not (product := await m_Product.get(guid=create_rating.product_id, session=session)):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=messages.PRODUCT_NOT_FOUND)
product = await get_product_or_404(guid=create_rating.product_id, session=session)
await m_Rating(product_id=product.guid, user_id=user.guid, stars=create_rating.stars).create(session=session)
product.upgrade_rating(rating=create_rating.stars)
await product.update(session=session)
return {"detail": messages.RATING_CREATED}
9 changes: 3 additions & 6 deletions app/routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,12 @@ async def activate_account(token: str, session: AsyncSession = Depends(get_sessi

@router.post("/login/", status_code=status.HTTP_200_OK, summary="Авторизация, получение токенов")
async def login(
user: LoginCredentials,
login_credentials: LoginCredentials,
session: AsyncSession = Depends(get_session),
authorize: AuthJWT = Depends(),
):
db_user = await get_user_or_404(email=user.email, session=session)
print(db_user)
if not db_user.verify_password(password=user.password):
user = await get_user_or_404(email=login_credentials.email, session=session)
if not user.verify_password(password=login_credentials.password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=messages.WRONG_PASSWORD)
return {
"access_token": authorize.create_access_token(subject=user.email),
Expand All @@ -87,7 +86,6 @@ async def logout(authorize: AuthJWT = Depends()):
async def forgot_password(data: Email, session: AsyncSession = Depends(get_session)):
user = await get_user_or_404(email=data.email, session=session)
reset_password_token = create_token(email=user.email)

subject = "Reset password"
recipients = [user.email]
body = html_reset_password_mail(reset_password_token=reset_password_token)
Expand All @@ -108,7 +106,6 @@ async def reset_password(token: str, data: UpdatePassword, session: AsyncSession
)
email = get_email_from_token(token=token)
user = await get_user_or_404(email=email, session=session)
print(data)
if data.password != data.confirm_password:
raise HTTPException(
status_code=status.HTTP_203_NON_AUTHORITATIVE_INFORMATION,
Expand Down
16 changes: 16 additions & 0 deletions app/schemas/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import uuid

from pydantic import BaseModel


class Order(BaseModel):
guid: uuid.UUID
user_id: uuid.UUID


class ShowOrderItem(BaseModel):
quantity: int
product: uuid.UUID

class Config:
orm_mode = True
5 changes: 2 additions & 3 deletions app/schemas/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def validate_price(cls, value: float | None = None) -> float | None:

@validator("order_by")
def validate_order_by(cls, value: str | None) -> str | None:
if value is not None:
if value[1:] not in fields and value not in fields:
raise ValueError("product model didn't have this field")
if value is not None and value[1:] not in fields and value not in fields:
raise ValueError("product model didn't have this field")
return value
9 changes: 9 additions & 0 deletions app/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import uuid

from app.models.products import Product as m_Product
from app.models.users import User as m_User
from app.utils.messages import messages
from fastapi import HTTPException, status
Expand All @@ -8,3 +11,9 @@ async def get_user_or_404(email: str, session: AsyncSession) -> m_User | Excepti
if not (user := await m_User.get(session=session, email=email)):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=messages.USER_NOT_FOUND)
return user


async def get_product_or_404(guid: uuid.UUID, session: AsyncSession) -> m_Product | Exception:
if not (product := await m_Product.get(session=session, guid=guid)):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=messages.PRODUCT_NOT_FOUND)
return product
2 changes: 1 addition & 1 deletion app/utils/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Messages:
PRODUCT_DELETED = "Продукт успешно удален"
PRODUCT_UPDATED = "Продукт успешно обновлен"
PRODUCT_ALREADY_EXISTS = "Данный продукт уже существует"
PRODUCT_NOT_FOUND = "Продукт с таким id не существует"
PRODUCT_NOT_FOUND = "Продукт с таким guid не существует"

RATING_ALREADY_EXISTS = "Продукт уже оценен данным пользователем"
RATING_CREATED = "Оценка продукта принята"
Expand Down
23 changes: 9 additions & 14 deletions app/utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import re
from string import ascii_lowercase, ascii_uppercase, digits, punctuation

from fastapi import HTTPException, status

PASSWORD_RE = re.compile(r"[A-Za-z\d/+=]{44}")
NAME_RE = re.compile(r"[A-Za-zА-яЁё\d]")

Expand All @@ -18,9 +16,9 @@
MAX_NAME_LEN = 100


def validate_password(password: str) -> str | HTTPException:
def validate_password(password: str) -> str | ValueError:
if re.search(PASSWORD_RE, password):
return True
return password
password_chars = set(password)
if not (
(MIN_PASSWORD_LENGTH <= len(password) <= MAX_PASSWORD_LENGTH)
Expand All @@ -30,24 +28,21 @@ def validate_password(password: str) -> str | HTTPException:
and (password_chars & PUNCTUATION)
and not (password_chars - AVAILABLE_CHARS)
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid validate password",
raise ValueError(
"Invalid validate password",
)
return password


def validate_name(name: str | None = None) -> str | None | HTTPException:
def validate_name(name: str | None = None) -> str | None | ValueError:
if not name:
return None
if len(name) > MAX_NAME_LEN:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Name field average max symbols :: {MAX_NAME_LEN}",
raise ValueError(
f"Name field average max symbols :: {MAX_NAME_LEN}",
)
if not bool(re.search(NAME_RE, name)):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid symbols in name field",
raise ValueError(
"Invalid symbols in name field",
)
return name

0 comments on commit 8323abc

Please sign in to comment.