refractor structure project and add user endpoint

This commit is contained in:
2026-01-08 15:05:23 +03:00
parent 423ce8bc62
commit 8e0131451d
25 changed files with 329 additions and 218 deletions
@@ -1,15 +1,14 @@
import logging import logging
from typing import Optional from typing import Optional
import uuid
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from jose import jwt, JWTError from jose import jwt, JWTError
from app.utils.OAuth2WithCookie import OAuth2PasswordBearerWithCookie from app.utils.OAuth2WithCookie import OAuth2PasswordBearerWithCookie
from app.config import settings from app.core.config import settings
from app.users.models import UserModel from app.users.models import UserModel
from app.users.service import UserService from app.users.service import UserService
from app.exceptions import InvalidTokenException from app.core.exceptions import InvalidTokenException
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/v1/auth/login") oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/v1/auth/login")
@@ -52,5 +51,7 @@ async def get_current_superuser(current_user: UserModel = Depends(get_current_us
async def get_current_verified_user(current_user: UserModel = Depends(get_current_user)): async def get_current_verified_user(current_user: UserModel = Depends(get_current_user)):
if not current_user.is_verified: if not current_user.is_verified:
log.debug("User has not confirmed the email.", extra={"user_id": current_user.id}) log.debug("User has not confirmed the email.", extra={"user_id": str(current_user.id)})
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="verify email") raise HTTPException(status.HTTP_403_FORBIDDEN, detail="verify email")
return current_user
+89
View File
@@ -0,0 +1,89 @@
from typing import Dict
import logging
import uuid
from fastapi import APIRouter, status, Response, Depends, Request, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from app.users.schemas import UserCreate, User
from app.auth.schemas import Token
from app.users.service import UserService
from app.auth.service import AuthService
from app.users.models import UserModel
from app.core.exceptions import InvalidCredentialsException
from app.auth.dependencies import get_current_user
from app.core.config import settings
router = APIRouter(prefix="/auth", tags=["Auth"])
log = logging.getLogger(__name__)
@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register(user: UserCreate) -> User:
return await UserService.register_new_user(user)
@router.get("/verify/{token}")
async def verify_email(token: uuid.UUID) -> Dict:
await UserService.verify_email(token)
return {"status": True, "message": "User successfully verified email"}
@router.post("/send/verify-email")
async def resend_verify_email(user: UserModel = Depends(get_current_user)) -> Dict:
if user.is_verified:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Email already verified")
await UserService.send_verify_email(user)
return {"status": True, "message": "Successfully send email letter"}
@router.post("/login")
async def login(response: Response, credentials: OAuth2PasswordRequestForm = Depends()) -> Token:
user = await AuthService.authenticate_user(credentials.username, credentials.password)
if not user:
log.warning("Failed login attempt", extra={"email or username": credentials.username})
raise InvalidCredentialsException
token = await AuthService.create_token(user.id)
response.set_cookie(
'access_token',
token.access_token,
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
httponly=True
)
response.set_cookie(
'refresh_token',
str(token.refresh_token),
max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 30 * 24 * 60,
httponly=True
)
return token
@router.post("/refresh")
async def refresh_token(request: Request, response: Response) -> Token:
new_token = await AuthService.refresh_token(uuid.UUID(request.cookies.get("refresh_token")))
response.set_cookie(
'access_token',
new_token.access_token,
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
httponly=True
)
response.set_cookie(
'refresh_token',
str(new_token.refresh_token),
max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 30 * 24 * 60,
httponly=True
)
log.debug("Token refreshed via endpoint")
return new_token
@router.post("/logout")
async def logout(request: Request, response: Response, user: UserModel = Depends(get_current_user)) -> Dict:
response.delete_cookie("access_token")
response.delete_cookie("refresh_token")
await AuthService.logout(uuid.UUID(request.cookies.get("refresh_token")))
return {"status": True, "message": "Logged out successfully"}
@router.post("/abort")
async def abort_all_sessions(user: UserModel = Depends(get_current_user)) -> Dict:
await AuthService.abort_all_sessions(user.id)
return {"status": True, "message": "All sessions was aborted"}
+9
View File
@@ -0,0 +1,9 @@
import uuid
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
refresh_token: uuid.UUID
token_type: str
+102
View File
@@ -0,0 +1,102 @@
import logging
import uuid
from datetime import datetime, timedelta
from typing import Optional
from jose import jwt
from sqlalchemy import or_
from app.utils.hash_password import verify_password
from app.services.redis_service import RefreshTokenStorage
from app.core.exceptions import InvalidTokenException
from app.users.models import UserModel
from app.users.dao import UserDAO
from app.core.database import async_session_maker
from app.auth.schemas import Token
from app.core.config import settings
log = logging.getLogger(__name__)
class AuthService:
@classmethod
async def create_token(cls, user_id: int) -> Token:
access_token = cls._create_access_token(user_id)
refresh_token_expires = timedelta(
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await RefreshTokenStorage.save_token(refresh_token, user_id, int(refresh_token_expires.total_seconds()))
log.info("Token created has user", extra={"user_id": user_id})
return Token(access_token=access_token, refresh_token=refresh_token, token_type="bearer")
@classmethod
async def logout(cls, token: uuid.UUID) -> None:
user_id = await RefreshTokenStorage.getdel_token(token)
log.info("User logged out", extra={"user_id": user_id})
@classmethod
async def refresh_token(cls, token: uuid.UUID) -> Token:
async with async_session_maker() as session:
refresh_session = await RefreshTokenStorage.getdel_token(token)
if refresh_session is None:
log.warning("Refresh token not found")
raise InvalidTokenException
user = await UserDAO.find_one_or_none(session, id=int(refresh_session))
if user is None:
log.error("User not found during token refresh", extra={"user_id": str(refresh_session.user_id)})
raise InvalidTokenException
access_token = cls._create_access_token(user.id)
refresh_token_expires = timedelta(
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await RefreshTokenStorage.save_token(
refresh_token,
user.id,
int(refresh_token_expires.total_seconds())
)
await session.commit()
log.info("Token refreshed for user", extra={"user_id": str(user.id)})
return Token(access_token=access_token, refresh_token=refresh_token, token_type="bearer")
@classmethod
async def authenticate_user(cls, email_or_username: str, password: str) -> Optional[UserModel]:
async with async_session_maker() as session:
db_user = await UserDAO.find_one_or_none(
session,
or_(
UserModel.email==email_or_username,
UserModel.username==email_or_username
)
)
if db_user and verify_password(password, db_user.hashed_password):
log.info("User authenticated successfully", extra={"username": db_user.username})
return db_user
log.warning("Authentication failed", extra={"email": email_or_username})
return None
@classmethod
async def abort_all_sessions(cls, user_id: int):
await RefreshTokenStorage.abort_all_tokens(user_id)
@classmethod
def _create_access_token(cls, user_id: int) -> str:
to_encode = {
"sub": str(user_id),
"exp": datetime.utcnow() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return f'Bearer {encoded_jwt}'
@classmethod
def _create_refresh_token(cls) -> uuid.UUID:
return uuid.uuid4()
View File
@@ -1,9 +1,9 @@
from celery import Celery from celery import Celery
from app.config import settings from app.core.config import settings
celery_app = Celery( celery_app = Celery(
"app.utils.celery_app", "app.core.celery_app",
broker=settings.RABBITMQ_URL, broker=settings.RABBITMQ_URL,
backend="rpc://" backend="rpc://"
) )
@@ -14,7 +14,7 @@ class Settings(BaseSettings):
WORKERS: int WORKERS: int
URL: str URL: str
CORS_ORIGINS: List[str] = ["*"] CORS_ORIGINS: List[str] = ["http://localhost:5500", "http://127.0.0.1:5500", "http://localhost:8080", "http://127.0.0.1:8080", "null"]
CORS_HEADERS: List[str] = ["*"] CORS_HEADERS: List[str] = ["*"]
CORS_METHODS: List[str] = ["*"] CORS_METHODS: List[str] = ["*"]
@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from pydantic import BaseModel from pydantic import BaseModel
from app.database import Base from app.core.database import Base
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -1,12 +1,11 @@
from datetime import datetime from datetime import datetime
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.engine import create_engine
from sqlalchemy import MetaData, NullPool, func from sqlalchemy import MetaData, NullPool, func
from app.config import settings from app.core.config import settings
from app.constants import DB_NAMING_CONVENTION from app.core.constants import DB_NAMING_CONVENTION
class Base(DeclarativeBase): class Base(DeclarativeBase):
@@ -1,6 +1,6 @@
from logging.config import dictConfig from logging.config import dictConfig
from app.config import settings from app.core.config import settings
LOGGING_CONFIG = { LOGGING_CONFIG = {
"version": 1, "version": 1,
@@ -1,6 +1,6 @@
from redis.asyncio import Redis, from_url from redis.asyncio import Redis, from_url
from app.config import settings from app.core.config import settings
redis_client: Redis = None redis_client: Redis = None
+5 -4
View File
@@ -5,10 +5,11 @@ import logging
from fastapi import FastAPI, APIRouter, Request, Response from fastapi import FastAPI, APIRouter, Request, Response
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.utils.redis import close_redis, init_redis from app.core.redis import close_redis, init_redis
from app.users.router import user_router, auth_router from app.users.router import router as user_router
from app.log_config import set_logging from app.auth.router import router as auth_router
from app.config import settings from app.core.log_config import set_logging
from app.core.config import settings
set_logging() set_logging()
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
+2 -3
View File
@@ -7,9 +7,8 @@ from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context from alembic import context
from app.users.models import UserModel from app.core.database import Base
from app.database import Base from app.core.config import settings
from app.config import settings
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
+1 -1
View File
@@ -1,7 +1,7 @@
import logging import logging
from app.utils.email_client import EmailClient from app.utils.email_client import EmailClient
from app.config import settings from app.core.config import settings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
+1 -1
View File
@@ -1,7 +1,7 @@
import logging import logging
import uuid import uuid
from app.utils.redis import get_redis from app.core.redis import get_redis
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
+1 -1
View File
@@ -1,6 +1,6 @@
import logging import logging
from app.utils.celery_app import celery_app from app.core.celery_app import celery_app
from app.services.email_service import EmailService from app.services.email_service import EmailService
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
+1 -1
View File
@@ -1,4 +1,4 @@
from app.dao import BaseDAO from app.core.dao import BaseDAO
from app.users.models import UserModel from app.users.models import UserModel
from app.users.schemas import UserCreateDB, UserUpdateDB from app.users.schemas import UserCreateDB, UserUpdateDB
+3 -12
View File
@@ -1,10 +1,9 @@
from datetime import date from datetime import date
import uuid
from sqlalchemy.orm import mapped_column, Mapped from sqlalchemy.orm import mapped_column, Mapped
from sqlalchemy import DATE, UUID, ForeignKey from sqlalchemy import DATE
from app.database import Base from app.core.database import Base
class UserModel(Base): class UserModel(Base):
__tablename__ = "user" __tablename__ = "user"
@@ -19,12 +18,4 @@ class UserModel(Base):
is_active: Mapped[bool] = mapped_column(default=True) is_active: Mapped[bool] = mapped_column(default=True)
is_verified: Mapped[bool] = mapped_column(default=False) is_verified: Mapped[bool] = mapped_column(default=False)
is_superuser: Mapped[bool] = mapped_column(default=False) is_superuser: Mapped[bool] = mapped_column(default=False)
hashed_password: Mapped[str] = mapped_column() hashed_password: Mapped[str] = mapped_column()
# class RefreshSessionModel(Base):
# __tablename__ = "refresh_session"
#
# id: Mapped[int] = mapped_column(primary_key=True, index=True)
# refresh_token: Mapped[uuid.UUID] = mapped_column(UUID, index=True)
# expires_in: Mapped[int] = mapped_column()
# user_ud: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"))
+24 -73
View File
@@ -1,88 +1,39 @@
from typing import Dict from typing import Dict
import logging import logging
import uuid
from fastapi import APIRouter, status, Response, Depends, Request, HTTPException from fastapi import APIRouter, Response, Depends
from fastapi.security import OAuth2PasswordRequestForm
from app.users.schemas import UserCreate, User, Token from app.users.schemas import User, UserUpdate
from app.users.service import AuthService, UserService from app.users.service import UserService
from app.auth.service import AuthService
from app.users.models import UserModel from app.users.models import UserModel
from app.exceptions import InvalidCredentialsException from app.auth.dependencies import get_current_verified_user, get_current_superuser
from app.users.dependencies import get_current_user, get_current_verified_user
from app.config import settings
user_router = APIRouter(prefix="/users", tags=["User"]) router = APIRouter(prefix="/users", tags=["User"])
auth_router = APIRouter(prefix="/auth", tags=["Auth"])
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@auth_router.post("/register", status_code=status.HTTP_201_CREATED)
async def register(user: UserCreate) -> User:
return await UserService.register_new_user(user)
@auth_router.get("/verify/{token}") @router.get("/me")
async def verify_email(token: uuid.UUID) -> Dict: async def get_current_user(user: UserModel = Depends(get_current_verified_user)) -> User:
await UserService.verify_email(token) log.debug("Getting current user profile", extra={"user_id": str(user.id)})
return {"status": True, "message": "User successfully verified email"} return user
@auth_router.post("/send/verify-email") @router.get("/")
async def resend_verify_email(user: UserModel = Depends(get_current_user)) -> Dict: async def get_users(offset: int, limit: int, user: UserModel = Depends(get_current_superuser)):
if user.is_verified: log.info("Getting users list", extra={"offset": offset, "limit": limit})
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Email already verified") return await UserService.get_users_list(offset=offset, limit=limit)
await UserService.send_verify_email(user) @router.put("/me")
return {"status": True, "message": "Successfully send email letter"} async def update_current_user(update_user: UserUpdate, user: UserModel = Depends(get_current_verified_user)) -> User:
return await UserService.update_user(user.id, update_user)
@auth_router.post("/login") @router.delete("/me")
async def login(response: Response, credentials: OAuth2PasswordRequestForm = Depends()) -> Token: async def delete_current_user(response: Response, user: UserModel = Depends(get_current_verified_user)) -> Dict:
user = await AuthService.authenticate_user(credentials.username, credentials.password) log.debug("User deleting their account", extra={"user_id": user.id, "email": user.email})
if not user: response.delete_cookie('access_token')
log.warning("Failed login attempt", extra={"email or username": credentials.username}) response.delete_cookie('refresh_token')
raise InvalidCredentialsException
token = await AuthService.create_token(user.id)
response.set_cookie(
'access_token',
token.access_token,
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
httponly=True
)
response.set_cookie(
'refresh_token',
str(token.refresh_token),
max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 30 * 24 * 60,
httponly=True
)
return token
@auth_router.post("/refresh")
async def refresh_token(request: Request, response: Response) -> Token:
new_token = await AuthService.refresh_token(uuid.UUID(request.cookies.get("refresh_token")))
response.set_cookie(
'access_token',
new_token.access_token,
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
httponly=True
)
response.set_cookie(
'refresh_token',
str(new_token.refresh_token),
max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 30 * 24 * 60,
httponly=True
)
log.debug("Token refreshed via endpoint")
return new_token
@auth_router.post("/logout")
async def logout(request: Request, response: Response, user: UserModel = Depends(get_current_user)) -> Dict:
response.delete_cookie("access_token")
response.delete_cookie("refresh_token")
await AuthService.logout(uuid.UUID(request.cookies.get("refresh_token")))
return {"status": True, "message": "Logged out successfully"}
@auth_router.post("/abort")
async def abort_all_sessions(user: UserModel = Depends(get_current_user)) -> Dict:
await AuthService.abort_all_sessions(user.id) await AuthService.abort_all_sessions(user.id)
return {"status": True, "message": "All sessions was aborted"} await UserService.delete_user(user.id)
return {"status": True, "message": "User successfully deleted"}
+1 -7
View File
@@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from datetime import date from datetime import date
import uuid
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
@@ -50,15 +49,10 @@ class UserCreateDB(UserBase):
class UserUpdateDB(UserBase): class UserUpdateDB(UserBase):
email: Optional[str] = None email: Optional[str] = None
hashed_password: str hashed_password: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
birth_day: Optional[date] = None birth_day: Optional[date] = None
is_active: Optional[bool] = None is_active: Optional[bool] = None
is_verified: Optional[bool] = None is_verified: Optional[bool] = None
is_superuser: Optional[bool] = None is_superuser: Optional[bool] = None
class Token(BaseModel):
access_token: str
refresh_token: uuid.UUID
token_type: str
+67 -97
View File
@@ -1,110 +1,25 @@
import logging import logging
import uuid import uuid
from datetime import datetime, timedelta from datetime import timedelta
from typing import Optional from typing import List
from fastapi import HTTPException, status from fastapi import HTTPException, status
from jose import jwt
from sqlalchemy import or_ from sqlalchemy import or_
from app.utils.hash_password import hash_password, verify_password from app.utils.hash_password import hash_password
from app.services.redis_service import RefreshTokenStorage, EmailTokenStorage from app.services.redis_service import EmailTokenStorage
from app.utils.redis import get_redis from app.core.redis import get_redis
from app.exceptions import InvalidTokenException, TokenExpiredException from app.core.exceptions import InvalidTokenException, TokenExpiredException
from app.users.models import UserModel from app.users.models import UserModel
from app.users.dao import UserDAO from app.users.dao import UserDAO
from app.database import async_session_maker from app.core.database import async_session_maker
from app.users.schemas import Token, UserCreate, UserCreateDB, User from app.users.schemas import UserCreate, UserCreateDB, User, UserUpdate, UserUpdateDB
from app.tasks.email_tasks import EmailTasks from app.tasks.email_tasks import EmailTasks
from app.config import settings from app.core.config import settings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class AuthService:
@classmethod
async def create_token(cls, user_id: int) -> Token:
access_token = cls._create_access_token(user_id)
refresh_token_expires = timedelta(
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await RefreshTokenStorage.save_token(refresh_token, user_id, int(refresh_token_expires.total_seconds()))
log.info("Token created has user", extra={"user_id": user_id})
return Token(access_token=access_token, refresh_token=refresh_token, token_type="bearer")
@classmethod
async def logout(cls, token: uuid.UUID) -> None:
user_id = await RefreshTokenStorage.getdel_token(token)
log.info("User logged out", extra={"user_id": user_id})
@classmethod
async def refresh_token(cls, token: uuid.UUID) -> Token:
async with async_session_maker() as session:
refresh_session = await RefreshTokenStorage.getdel_token(token)
if refresh_session is None:
log.warning("Refresh token not found")
raise InvalidTokenException
user = await UserDAO.find_one_or_none(session, id=int(refresh_session))
if user is None:
log.error("User not found during token refresh", extra={"user_id": str(refresh_session.user_id)})
raise InvalidTokenException
access_token = cls._create_access_token(user.id)
refresh_token_expires = timedelta(
days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
refresh_token = cls._create_refresh_token()
await RefreshTokenStorage.save_token(
refresh_token,
user.id,
int(refresh_token_expires.total_seconds())
)
await session.commit()
log.info("Token refreshed for user", extra={"user_id": str(user.id)})
return Token(access_token=access_token, refresh_token=refresh_token, token_type="bearer")
@classmethod
async def authenticate_user(cls, email_or_username: str, password: str) -> Optional[UserModel]:
async with async_session_maker() as session:
db_user = await UserDAO.find_one_or_none(
session,
or_(
UserModel.email==email_or_username,
UserModel.username==email_or_username
)
)
if db_user and verify_password(password, db_user.hashed_password):
log.info("User authenticated successfully", extra={"username": db_user.username})
return db_user
log.warning("Authentication failed", extra={"email": email_or_username})
return None
@classmethod
async def abort_all_sessions(cls, user_id: int):
await RefreshTokenStorage.abort_all_tokens(user_id)
@classmethod
def _create_access_token(cls, user_id: int) -> str:
to_encode = {
"sub": str(user_id),
"exp": datetime.utcnow() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return f'Bearer {encoded_jwt}'
@classmethod
def _create_refresh_token(cls) -> uuid.UUID:
return uuid.uuid4()
class UserService: class UserService:
@classmethod @classmethod
async def get_user(cls, user_id: int) -> User: async def get_user(cls, user_id: int) -> User:
@@ -120,8 +35,6 @@ class UserService:
@classmethod @classmethod
async def register_new_user(cls, user: UserCreate) -> User: async def register_new_user(cls, user: UserCreate) -> User:
redis_client = await get_redis()
async with async_session_maker() as session: async with async_session_maker() as session:
user_exist = await UserDAO.find_one_or_none(session, or_( user_exist = await UserDAO.find_one_or_none(session, or_(
UserModel.email==user.email, UserModel.email==user.email,
@@ -172,7 +85,6 @@ class UserService:
@classmethod @classmethod
async def verify_email(cls, token: uuid.UUID): async def verify_email(cls, token: uuid.UUID):
redis_client = await get_redis()
async with async_session_maker() as session: async with async_session_maker() as session:
user_id = await EmailTokenStorage.getdel_token(token) user_id = await EmailTokenStorage.getdel_token(token)
@@ -192,3 +104,61 @@ class UserService:
) )
await session.commit() await session.commit()
@classmethod
async def get_users_list(cls, offset: int = 0, limit: int = 10) -> List[UserModel]:
async with async_session_maker() as session:
users = await UserDAO.find_all(session, offset, limit)
if users is None:
log.warning("Users not found")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Users not found")
log.debug("Users fetched", extra={"count": len(users), "offset": offset, "limit": limit})
return users
@classmethod
async def update_user(cls, user_id: int, update_user: UserUpdate):
async with async_session_maker() as session:
user_exist = await UserDAO.find_one_or_none(session, id=user_id)
if user_exist is None:
log.warning("User not found", extra={"user_id": user_id})
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
if user_exist.username != update_user.username:
username_exist = await UserDAO.find_one_or_none(session, username=update_user.username)
if username_exist:
log.warning("Username is taken", extra={"user_id": user_id})
raise HTTPException(status.HTTP_409_CONFLICT, detail="Username is taken")
update_user_db = await UserDAO.update(
session,
UserModel.id==user_id,
obj_in=UserUpdateDB(
**update_user.model_dump()
)
)
await session.commit()
log.info("User updated", extra={"user_id": user_id})
return update_user_db
@classmethod
async def delete_user(cls, user_id):
async with async_session_maker() as session:
user_exist = await UserDAO.find_one_or_none(session, id=user_id)
if user_exist is None:
log.warning("User not found", extra={"user_id": user_id})
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
await UserDAO.update(
session,
UserModel.id==user_id,
obj_in={"is_active": False}
)
await session.commit()
log.info("User is inactive", extra={"user_id": user_id})
+7 -2
View File
@@ -1,10 +1,13 @@
import json import json
import logging
from functools import wraps from functools import wraps
from fastapi import Request from fastapi import Request
from fastapi.encoders import jsonable_encoder
from app.utils.redis import get_redis from app.core.redis import get_redis
log = logging.getLogger(__name__)
def cache(ttl: int = 10): def cache(ttl: int = 10):
if ttl <= 0: if ttl <= 0:
@@ -17,10 +20,12 @@ def cache(ttl: int = 10):
request: Request = kwargs.get("request") request: Request = kwargs.get("request")
response_cache = await redis.get(str(request.url)) response_cache = await redis.get(str(request.url))
if response_cache is not None: if response_cache is not None:
log.debug("Getting from cache")
return json.loads(response_cache) return json.loads(response_cache)
response_cache = await func(*args, **kwargs) response_cache = await func(*args, **kwargs)
await redis.setex(str(request.url), ttl, json.dumps(response_cache)) serializable_data = jsonable_encoder(response_cache)
await redis.setex(str(request.url), ttl, json.dumps(serializable_data))
return response_cache return response_cache
return wrapper return wrapper
+1 -1
View File
@@ -7,7 +7,7 @@ from email.mime.multipart import MIMEMultipart
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from app.config import settings from app.core.config import settings
log = logging.getLogger(__name__) log = logging.getLogger(__name__)