Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
52 changes: 52 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import uvicorn
from fastapi.responses import JSONResponse
from fastapi_offline import FastAPIOffline as FastAPI

from auth.routers import router as auth_router
from core.base.expections import CustomException
from core.config import settings
from infrastructure.sqlalchemy.utiles import init_models
from users.router import router as users_router


async def create_models(app: FastAPI):
await init_models()
yield


app = FastAPI(lifespan=create_models)


app.include_router(router=users_router, tags=["Users"])
app.include_router(router=auth_router, tags=["Auth"])


@app.exception_handler(CustomException)
async def custom_exception_handler(request, exc: CustomException):
return JSONResponse(
status_code=exc.code,
content={
"success": False,
"error": {
"code": exc.error_code,
"message": exc.message,
"status_code": exc.code,
},
},
)


print("database url is ", settings.DATABASE_URL)


def main():
uvicorn.run(
"main:app",
host=settings.HOST,
port=settings.POST,
reload=True if settings.ENVIRONMENT == "development" else False,
)


if __name__ == "__main__":
main()
55 changes: 55 additions & 0 deletions src/users/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from core.security.utils.hashing import hash_password
from core.security.utils.token_utils import generate_token, hash_token
from infrastructure.sqlalchemy.AsyncSession import AsyncSessionLocal

from .repository import ProfileRepository, UserRepository
from .selectors import UserSelector
from .service import ProfileService, UserRegistrationService, UserService


def get_user_repo():
return UserRepository()


def get_profile_repo():
return ProfileRepository()


def get_user_service(user_repo=Depends(get_user_repo)):
return UserService(
user_repo=user_repo,
token_generator=generate_token,
hasher_password=hash_password,
hasher_token=hash_token,
)


def get_profile_service(profile_repo=Depends(get_profile_repo)):
return ProfileService(
profile_repo=profile_repo,
)


def get_registration_service(
user_service: UserService = Depends(get_user_service),
profile_service: ProfileService = Depends(get_profile_service),
):
return UserRegistrationService(
user_service=user_service,
profile_service=profile_service,
session_factory=AsyncSessionLocal,
)


def get_user_selector(
user_repo=Depends(get_user_repo),
profile_repo=Depends(get_profile_repo),
):
return UserSelector(
user_repo=user_repo,
profile_repo=profile_repo,
session_factory=AsyncSessionLocal,
)
45 changes: 45 additions & 0 deletions src/users/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from core.base.expections import CustomException


class DatabaseOperationError(CustomException):
pass


class DatabaseUnavailableError(CustomException):
pass


class InternalServiceError(CustomException):
pass


class TokenGenerationError(CustomException):
pass


class HashingFailedError(CustomException):
pass


class UserNotExists(CustomException):
pass


class EmailAlreadyExists(CustomException):
pass


class UsernameAlreadyExists(CustomException):
pass


class UserNotFound(CustomException):
pass


class SystemDepnedencyError(Exception):
pass


class ConcurrencyError(Exception):
pass
105 changes: 105 additions & 0 deletions src/users/repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from sqlalchemy import exists, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from core.base.expections import NotFoundException
from core.base.repository import BaseRepository
from models.user import Profile, User

from .schemas import ProfileResponse, UserResponse


class UserRepository(BaseRepository):
def __init__(self):
super().__init__(User)

async def get_by_email(self, session: AsyncSession, email):
stmt = select(User).where(func.lower(User.email) == func.lower(email))
result = await session.execute(stmt)
return result.scalar_one_or_none()

async def check_token_exists(self, session: AsyncSession, token_hash):
stmt = select(exists().where(User.token_hash == token_hash))
result = await session.execute(stmt)
return result.scalar()

async def get_list_user_profile_stmt(
self, *, created_lt, created_gt, limit, offset, profile_model=Profile, **filters
):
"""

PostgreSQL Dependency Notice

This repository leverages PostgreSQL-specific JSON functions:
- json_agg()
- json_build_object()

Make sure your database is PostgreSQL before using these queries.
Other databases are NOT supported.

"""

# stmt = select(
# func.json_build_object(
# 'users'
# ) ,

# self.model
# ).options(joinedload(self.model.profile))

stmt = select(self.model, profile_model).outerjoin(
profile_model, self.model.id == profile_model.user_id
)

if filters:
stmt = self.add_domain_filters(stmt, self.model, filters)

stmt = self.filter_by_timezone(stmt, created_lt, created_gt, self.model)

stmt = self.filter_by_limit_offset(stmt, limit, offset)
# print(stmt)
return stmt

async def _serialize_get_list_user_profile(self, results):

# results = await session.execute(stmt)

# print('res' ,results.all())

data = [
{
"users": UserResponse.model_validate(d["User"]).model_dump(),
"profiles": ProfileResponse.model_validate(d["Profile"]).model_dump(),
}
for d in results.mappings().all()
]

return data

async def update(self, session: AsyncSession, user_id: int, update_data: dict):

invalid_fields = set(update_data) - self._valid_columns

if invalid_fields:
raise NotFoundException(f"invalid fields for {self.model.__name__}")
stmt = (
update(User).where(User.id == user_id).values(**update_data).returning(User)
)
result = await session.execute(stmt)
return result.scalar_one_or_none()

async def get_user_by_username(
self, session: AsyncSession, username: str
) -> User | None:
stmt = select(User).where(User.username == username)
result = await session.execute(stmt)
return result.scalar_one_or_none()


class ProfileRepository(BaseRepository):
def __init__(self):
super().__init__(Profile)

async def get_by_user_id(self, session, user_id):
stmt = select(Profile).where(Profile.user_id == user_id)
result = await session.execute(stmt)
return result.scalar_one_or_none()
Loading
Loading