Major addition. User register, login and update is now functional on frontend, removed UUID5 for UUID4 for users and many more changes

This commit is contained in:
Thastertyn committed 2025-03-16 22:24:54 +01:00
1 parent d87c280109
commit d0690f2f06
67 files changed
+3389 -1066

No files matched your search

-1
View File
@@ -4,7 +4,6 @@ from app.api.routes import cart_routes, login_routes, shop, user_routes, utils_r
api_router = APIRouter()
api_router.include_router(cart_routes.router)
api_router.include_router(user_routes.router)
api_router.include_router(utils_routes.router)
api_router.include_router(login_routes.router)
+4 -4
View File
@@ -1,18 +1,18 @@
from fastapi import APIRouter, HTTPException, status
from fastapi import APIRouter
from app.api.dependencies import CurrentOwnerUser, SessionDep
from app.crud.shop_crud import create_shop
from app.schemas.shop_schemas import ShopCreate
from app.schemas.user_schemas import Token
from app.crud.shop_crud import create_shop
router = APIRouter(prefix="/shop", tags=["Dashboard"])
@router.post("", response_model=bool, status_code=201)
@router.post("", status_code=201)
def register_new_shop(
session: SessionDep,
current_user: CurrentOwnerUser,
shop_data: ShopCreate
) -> Token:
) -> bool:
create_shop(session, shop_data, current_user)
return True
+9 -11
View File
@@ -7,7 +7,7 @@ from sqlalchemy.exc import IntegrityError
from app.api.dependencies import CurrentOwnerUser, SessionDep
from app.crud import user_crud
from app.database.models.user_model import UserRole
from app.schemas.user_schemas import UserRegister, UserUpdate
from app.schemas.user_schemas import UserPublic, UserRegister, UserUpdate
logger = logging.getLogger(__name__)
@@ -21,8 +21,8 @@ router = APIRouter(
@router.get("", summary="Get information about currently logged in user")
async def get_user(session: SessionDep, current_user: CurrentOwnerUser):
pass
async def get_user(current_user: CurrentOwnerUser) -> UserPublic:
return current_user
@router.post("", summary="Register new user", status_code=status.HTTP_201_CREATED, response_model=bool)
@@ -40,11 +40,8 @@ async def register(session: SessionDep, user_data: UserRegister):
column_name = "email"
detail = f"{field_mapping.get(column_name, column_name or 'Entry').capitalize()} already in use"
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=detail
)
logger.warning("%s already exists in the database", column_name.capitalize)
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=detail)
except Exception as e:
if isinstance(e, HTTPException):
raise
@@ -53,10 +50,11 @@ async def register(session: SessionDep, user_data: UserRegister):
@router.delete("", summary="Delete user")
async def delete_user():
async def delete_user() -> bool:
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="delete_user() not implemented")
@router.put("", summary="Update user details")
async def update_user(data: UserUpdate):
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="update_user() not implemented")
async def update_user(session: SessionDep, data: UserUpdate, current_user: CurrentOwnerUser) -> bool:
user_crud.update_user(session, data, current_user)
return True
+10 -2
View File
@@ -1,5 +1,6 @@
import logging
from typing import Optional
from uuid import UUID, uuid4
from uuid import uuid4
from sqlmodel import Session, select
@@ -7,12 +8,19 @@ from app.database.models.shop_model import Shop, ShopStatus
from app.database.models.user_model import User
from app.schemas.shop_schemas import ShopCreate
logger = logging.getLogger(__name__)
def get_shop_id_from_uuid(session: Session, shop_id: int) -> Optional[UUID]:
def get_shop_by_id(session: Session, shop_id: int) -> Optional[Shop]:
logger.debug("Getting shop by shop_id - %d", id)
stmt = select(Shop).where(Shop.id == shop_id)
db_shop = session.exec(stmt).one_or_none()
return db_shop
def get_shop_by_uuid(session: Session, shop_uuid: str) -> Optional[Shop]:
logger.debug("Getting shop by UUID - %s" , shop_uuid)
stmt = select(Shop).where(Shop.uuid == shop_uuid)
db_shop = session.exec(stmt).one_or_none()
return db_shop
def create_shop(session: Session, shop_data: ShopCreate, creator: User) -> None:
shop_uuid = uuid4()
+50 -25
View File
@@ -1,58 +1,83 @@
import logging
from typing import Optional
from uuid import UUID
from uuid import UUID, uuid4
from sqlmodel import Session, select
from fastapi import HTTPException, status
from sqlmodel import Session, select, and_
from app.core.security import get_password_hash, verify_password
from app.crud.shop_crud import get_shop_id_from_uuid
from app.crud.shop_crud import get_shop_by_uuid
from app.database.models.user_model import User, UserRole
from app.schemas.user_schemas import UserRegister
from app.schemas.user_schemas import UserRegister, UserUpdate
from app.utils.models import generate_user_uuid5
logger = logging.getLogger(__name__)
def get_user_by_generated_uuid(session: Session, email: str, shop_uuid: Optional[UUID]) -> Optional[User]:
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
logger.debug("Generating user UUID5")
user_uuid = generate_user_uuid5(email, shop_id)
stmt = select(User).where(User.uuid == user_uuid)
logger.debug("Executing select query")
db_user = session.exec(stmt).one_or_none()
return db_user
def create_user(session: Session, user_register: UserRegister, shop_uuid: Optional[UUID], user_role: UserRole):
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
logger.debug("Generating user UUID5")
user_uuid = generate_user_uuid5(user_register.email, shop_id)
if shop_uuid:
logger.debug("Fetching shop by UUID")
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
logger.debug("No shop UUID provided -> Owner account is being created")
shop_id = None
logger.debug("Hashing password")
hashed_password = get_password_hash(user_register.password)
new_user = User(
uuid=user_uuid,
uuid=uuid4(),
shop_id=shop_id,
email=user_register.email,
username=user_register.username,
phone_number=user_register.phone_number,
user_role=user_role,
password=hashed_password
)
)
logger.debug("Inserting new user")
session.add(new_user)
session.commit()
def update_user(session: Session, user_update: UserUpdate, current_user: User):
current_user.email = user_update.email
current_user.username = user_update.username
current_user.phone_number = user_update.phone_number
current_user.first_name = user_update.first_name
current_user.last_name = user_update.last_name
session.commit()
def get_user_by_uuid(session: Session, email: str, shop_uuid: Optional[UUID]) -> Optional[User]:
if shop_uuid:
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
shop_id = None
stmt = select(User).where(and_(
User.email == email,
User.shop_id == shop_id
))
logger.debug("Executing select query")
db_user = session.exec(stmt).one_or_none()
return db_user
def authenticate(session: Session, email: str, password: str, shop_uuid: Optional[int]) -> Optional[User]:
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
if shop_uuid:
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
shop_id = None
logger.debug("Fetching user from db by email - %s", email)
db_user = get_user_by_generated_uuid(session, email, shop_id)
db_user = get_user_by_uuid(session, email, shop_id)
if db_user is None:
logger.warn("Didn't find User with email=%s for shop=%s", email, shop_uuid)
logger.warning("Didn't find User with email=%s for shop=%s", email, shop_uuid)
return None
if not verify_password(plain_password=password, hashed_password=db_user.password):
logger.warn("Found user with email=%s for shop=%s", email, shop_uuid)
logger.warning("Found user with email=%s for shop=%s", email, shop_uuid)
return None
return db_user
+4 -1
View File
@@ -4,7 +4,7 @@ from typing import Optional
from uuid import UUID
from sqlalchemy import Column
from sqlmodel import Enum, Field, Relationship, SQLModel
from sqlmodel import Enum, Field, Relationship, SQLModel, UniqueConstraint
class UserRole(PyEnum):
@@ -17,6 +17,9 @@ class UserRole(PyEnum):
class User(SQLModel, table=True):
__tablename__ = "user"
__table_args__ = (
UniqueConstraint("email", "shop_id", name="uix_email_shop_id"),
)
id: int = Field(primary_key=True)
uuid: UUID = Field(nullable=False, unique=True)
+24 -20
View File
@@ -1,5 +1,5 @@
import re
from typing import Optional
import uuid
from pydantic import BaseModel, EmailStr, Field, model_validator
@@ -7,9 +7,13 @@ from pydantic import BaseModel, EmailStr, Field, model_validator
class UserRegister(BaseModel):
username: str = Field(min_length=3, max_length=64)
email: EmailStr = Field()
phone_number: str = Field(min_length=2, max_length=16, pattern=r'^\+[1-9]\d{1,14}$')
password: str = Field(min_length=8, max_length=128, examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```")
phone_number: str = Field(min_length=2, max_length=16, pattern=r"^\+[1-9]\d{1,14}$")
password: str = Field(
min_length=8,
max_length=128,
examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```",
)
@model_validator(mode="after")
def validate_using_regex(self):
@@ -17,28 +21,28 @@ class UserRegister(BaseModel):
return self
def __validate_password(self):
password_regex = r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$"
password_regex = r"^(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^#?!@$ %^&*-]*[#?!@$ %^&*-]).{8,128}$"
if not re.match(password_regex, self.password):
raise ValueError("Password is too weak")
class UserUpdate(BaseModel):
username: Optional[str] = Field(None, min_length=3, max_length=64)
first_name: Optional[str] = Field(None, max_length=64)
last_name: Optional[str] = Field(None, max_length=64)
email: Optional[EmailStr] = Field(None)
phone_number: Optional[str] = Field(None, min_length=2, max_length=16, pattern=r'^\+[1-9]\d{1,14}$')
password: Optional[str] = Field(None, min_length=8, max_length=128, examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```")
email: EmailStr | None = Field()
phone_number: str | None = Field(min_length=2, max_length=16, pattern=r"^\+[1-9]\d{1,14}$")
username: str | None = Field(min_length=3, max_length=64)
first_name: str | None = Field(None, max_length=64)
last_name: str | None = Field(None, max_length=64)
@model_validator(mode="after")
def validate_using_regex(self):
self.__validate_password()
return self
def __validate_password(self):
password_regex = r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$"
if not re.match(password_regex, self.password):
raise ValueError("Password is too weak")
class UserPublic(BaseModel):
uuid: uuid.UUID
username: str
email: str
first_name: str | None
last_name: str | None
phone_number: str
class Token(BaseModel):
access_token: str
+8 -2
View File
@@ -1,6 +1,12 @@
import logging
from typing import Optional
from uuid import uuid5, UUID, NAMESPACE_DNS
from uuid import NAMESPACE_DNS, UUID, uuid5
logger = logging.getLogger(__name__)
def generate_user_uuid5(email: str, shop_id: Optional[int]) -> UUID:
unique_string = f"{email}-{shop_id}" if shop_id else email
return uuid5(NAMESPACE_DNS, unique_string)
generated_uuid = uuid5(NAMESPACE_DNS, unique_string)
logger.debug("Generated user UUID5 - %s", generated_uuid)
return generated_uuid
-1
View File
@@ -30,7 +30,6 @@ select = [
ignore = [
"E501", # line too long, handled by black
"B008", # do not perform function calls in argument defaults
"W191", # indentation contains tabs
"B904", # Allow raising exceptions without from e, for HTTPException
"UP007",
]