feat: user routes

fix: global delete filter
refactor: user update schema
This commit is contained in:
deepvasoya 2025-05-12 18:46:11 +05:30
parent 8d8f205eb1
commit c235196990
6 changed files with 145 additions and 26 deletions

View File

@ -5,7 +5,7 @@ from fastapi.security import HTTPBearer
# Import the security scheme # Import the security scheme
bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication") bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication")
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3 from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users
api_router = APIRouter() api_router = APIRouter()
# api_router.include_router(twilio.router, prefix="/twilio") # api_router.include_router(twilio.router, prefix="/twilio")
@ -21,3 +21,4 @@ api_router.include_router(
tags=["admin"]) tags=["admin"])
api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(s3.router, dependencies=[Depends(auth_required)], prefix="/s3", tags=["s3"]) api_router.include_router(s3.router, dependencies=[Depends(auth_required)], prefix="/s3", tags=["s3"])
api_router.include_router(users.router, prefix="/users", tags=["users"], dependencies=[Depends(auth_required)])

59
apis/endpoints/users.py Normal file
View File

@ -0,0 +1,59 @@
from fastapi import APIRouter, Request, Depends
from middleware.auth_dependency import auth_required
from services.userServices import UserServices
from schemas.ApiResponse import ApiResponse
from schemas.UpdateSchemas import UserUpdate
router = APIRouter()
@router.get("/")
async def get_users():
user = await UserServices().get_users()
return ApiResponse(
data=user,
message="User fetched successfully"
)
@router.get("/me")
async def get_user(request: Request):
user_id = request.state.user["id"]
user = await UserServices().get_user(user_id)
return ApiResponse(
data=user,
message="User fetched successfully"
)
@router.get("/{user_id}")
async def get_user(request: Request, user_id: int):
user = await UserServices().get_user(user_id)
return ApiResponse(
data=user,
message="User fetched successfully"
)
@router.delete("/")
async def delete_user(request: Request):
user_id = request.state.user["id"]
await UserServices().delete_user(user_id)
return ApiResponse(
data="OK",
message="User deleted successfully"
)
@router.put("/")
async def update_user(request: Request, user_data: UserUpdate):
user_id = request.state.user["id"]
user = await UserServices().update_user(user_id, user_data)
return ApiResponse(
data=user,
message="User updated successfully"
)
@router.put("/{user_id}")
async def update_user(request: Request, user_id: int, user_data: UserUpdate):
user = await UserServices().update_user(user_id, user_data)
return ApiResponse(
data=user,
message="User updated successfully"
)

View File

@ -23,25 +23,21 @@ class CustomBase:
session.commit() session.commit()
# Global filter for deleted records # Global filter for deleted records
@staticmethod @event.listens_for(SessionLocal, "do_orm_execute")
@event.listens_for(Query, "before_compile", retval=True) def _add_filtering_criteria(execute_state):
def _filter_deleted(query): if (
# Skip filtering if this query explicitly asks to include deleted items execute_state.is_select
for option in query._with_options: and not execute_state.execution_options.get("include_deleted", False)
if getattr(option, 'name', None) == 'include_deleted': ):
return query # Check if any of the entities inherit from CustomBase
for entity in execute_state.statement.column_descriptions:
# Find entities that inherit from CustomBase entity_class = entity.get("entity", None)
for entity in query._entities: if entity_class and issubclass(entity_class, CustomBase):
if hasattr(entity, 'entity_zero'): # Add filter condition to exclude soft-deleted records
entity_zero = entity.entity_zero execute_state.statement = execute_state.statement.filter(
if hasattr(entity_zero, 'mapper') and entity_zero.mapper: entity_class.deleted_at.is_(None)
mapper = entity_zero.mapper )
if issubclass(mapper.class_, CustomBase): break
# Apply filter for this entity
query = query.filter(mapper.class_.deleted_at.is_(None))
return query
# Option to include deleted records # Option to include deleted records
class IncludeDeleted(object): class IncludeDeleted(object):

View File

@ -36,3 +36,11 @@ class AppointmentUpdate(BaseModel):
class CalendarUpdate(BaseModel): class CalendarUpdate(BaseModel):
doc_id: Optional[int] = None doc_id: Optional[int] = None
rrule: Optional[str] = None rrule: Optional[str] = None
class UserUpdate(BaseModel):
username: Optional[str] = None
clinicRole: Optional[ClinicUserRoles] = None
userType: Optional[UserType] = None
profile_pic: Optional[str] = None
password: Optional[str] = None

View File

@ -7,6 +7,8 @@ from exceptions.validation_exception import ValidationException
from schemas.ResponseSchemas import UserResponse from schemas.ResponseSchemas import UserResponse
from models import Clinics from models import Clinics
from enums.enums import ClinicStatus from enums.enums import ClinicStatus
from schemas.UpdateSchemas import UserUpdate
from exceptions.unauthorized_exception import UnauthorizedException
from utils.password_utils import hash_password from utils.password_utils import hash_password
from schemas.CreateSchemas import UserCreate from schemas.CreateSchemas import UserCreate
from exceptions.resource_not_found_exception import ResourceNotFoundException from exceptions.resource_not_found_exception import ResourceNotFoundException
@ -118,6 +120,23 @@ class UserServices:
return user_response return user_response
def get_users(self, limit:int, offset:int, search:str):
query = self.db.query(Users)
if search:
query = query.filter(
or_(
Users.username.contains(search),
Users.email.contains(search),
Users.clinicRole.contains(search),
Users.userType.contains(search)
)
)
users = query.limit(limit).offset(offset).all()
user_response = [UserResponse(**user.__dict__.copy()) for user in users]
return user_response
async def get_user_by_email(self, email: str) -> UserResponse: async def get_user_by_email(self, email: str) -> UserResponse:
user = self.db.query(Users).filter(Users.email == email.lower()).first() user = self.db.query(Users).filter(Users.email == email.lower()).first()
@ -131,8 +150,44 @@ class UserServices:
return user_response return user_response
def update_user(self, user_id, user): def update_user(self, admin_id:int|None, user_id: int, user_data: UserUpdate):
if admin_id:
admin = self.db.query(Users).filter(Users.id == admin_id).first()
if not admin:
logger.error("Admin not found")
raise ResourceNotFoundException("Admin not found")
if admin.userType != UserType.ADMIN:
logger.error("User is not authorized to perform this action")
raise UnauthorizedException("User is not authorized to perform this action")
user = self.db.query(Users).filter(Users.id == user_id).first()
if not user:
logger.error("User not found")
raise ResourceNotFoundException("User not found")
user.username = user_data.username
user.clinicRole = user_data.clinicRole
user.userType = user_data.userType
user.profile_pic = user_data.profile_pic
self.db.add(user)
self.db.commit()
return user return user
def delete_user(self, user_id): def delete_user(self, user_id: int):
return user user = self.db.query(Users).filter(Users.id == user_id).first()
if not user:
logger.error("User not found")
raise ResourceNotFoundException("User not found")
# Use the soft_delete method from CustomBase
user.soft_delete(self.db)
return True