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:
1 parent
d87c280109
commit
d0690f2f06
67 files changed
+3389
-1066
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user