chaors: db optimization

This commit is contained in:
deepvasoya 2025-05-28 11:16:55 +05:30
parent 62b6020897
commit 6c4fcca9bd
9 changed files with 753 additions and 536 deletions

View File

@ -12,15 +12,10 @@ from sqlalchemy.exc import SQLAlchemyError
engine = create_engine( engine = create_engine(
os.getenv("DB_URL"), os.getenv("DB_URL"),
pool_pre_ping=True, pool_pre_ping=True,
pool_size=2, # Reduced from 5 echo=True if os.getenv("IS_DEV") == "True" else False, # Disable in production - this uses memory
max_overflow=3, # Reduced from 10
pool_recycle=1800, # Reduced to 30 minutes
pool_timeout=30, # Add connection timeout
echo=False, # Disable in production - this uses memory
connect_args={ connect_args={
"sslmode": "require" if os.getenv("IS_DEV") == "False" else "disable", "sslmode": "require" if os.getenv("IS_DEV") == "False" else "disable",
"connect_timeout": 10, # Connection timeout "connect_timeout": 10, # Connection timeout
"command_timeout": 60, # Query timeout
}, },
) )

View File

@ -26,7 +26,7 @@ from schemas.BaseSchemas import AuthBase, AuthOTP
from exceptions.unauthorized_exception import UnauthorizedException from exceptions.unauthorized_exception import UnauthorizedException
from database import get_db from database import get_db
from loguru import logger
class AuthService: class AuthService:
@ -35,6 +35,7 @@ class AuthService:
self.db = next(get_db()) self.db = next(get_db())
self.email_service = EmailService() self.email_service = EmailService()
self.url = os.getenv("FRONTEND_URL") self.url = os.getenv("FRONTEND_URL")
self.logger = logger
async def login(self, data: AuthBase) -> str: async def login(self, data: AuthBase) -> str:
@ -58,58 +59,75 @@ class AuthService:
return response return response
def blockEmailSNS(self, body: str): def blockEmailSNS(self, body: str):
# confirm subscription try:
if body["Type"] == "SubscriptionConfirmation": # confirm subscription
urllib.request.urlopen(body["SubscribeURL"]) if body["Type"] == "SubscriptionConfirmation":
urllib.request.urlopen(body["SubscribeURL"])
# disable automatic unsubscribe confirmation by activating subscription again # disable automatic unsubscribe confirmation by activating subscription again
elif body["Type"] == "UnsubscribeConfirmation": elif body["Type"] == "UnsubscribeConfirmation":
urllib.request.urlopen(body["SubscribeURL"]) urllib.request.urlopen(body["SubscribeURL"])
# handle bounce notifications only # handle bounce notifications only
elif body["Type"] == "Notification": elif body["Type"] == "Notification":
msg = json.loads(body["Message"]) msg = json.loads(body["Message"])
# check if msg contains notificationType # check if msg contains notificationType
if "notificationType" not in msg: if "notificationType" not in msg:
return return
recepients = msg["bounce"]["bouncedRecipients"] recepients = msg["bounce"]["bouncedRecipients"]
for recipient in recepients: for recipient in recepients:
blockEmail = BlockedEmail(email=recipient["emailAddress"], reason=msg["notificationType"], severity=msg["bounce"]["bounceType"]) blockEmail = BlockedEmail(email=recipient["emailAddress"], reason=msg["notificationType"], severity=msg["bounce"]["bounceType"])
self.db.add(blockEmail) self.db.add(blockEmail)
self.db.commit() self.db.commit()
return "OK" return "OK"
except Exception as e:
self.logger.error(f"Error blocking email: {e}")
raise
finally:
self.db.close()
async def send_otp(self, email:str): async def send_otp(self, email:str):
otp = generateOTP() try:
self.email_service.send_otp_email(email, otp) otp = generateOTP()
self.email_service.send_otp_email(email, otp)
# Create OTP record with proper datetime handling # Create OTP record with proper datetime handling
expire_time = datetime.datetime.now() + datetime.timedelta(minutes=10) expire_time = datetime.datetime.now() + datetime.timedelta(minutes=10)
otp_record = OTP(email=email, otp=otp, expireAt=expire_time) otp_record = OTP(email=email, otp=otp, expireAt=expire_time)
self.db.add(otp_record) self.db.add(otp_record)
self.db.commit() self.db.commit()
return return
except Exception as e:
self.logger.error(f"Error sending OTP: {e}")
raise
finally:
self.db.close()
async def verify_otp(self, data: AuthOTP): async def verify_otp(self, data: AuthOTP):
db_otp = self.db.query(OTP).filter(OTP.email == data.email, OTP.otp == data.otp).first() try:
if not db_otp: db_otp = self.db.query(OTP).filter(OTP.email == data.email, OTP.otp == data.otp).first()
raise ValidationException("Invalid OTP") if not db_otp:
if db_otp.otp != data.otp: raise ValidationException("Invalid OTP")
raise ValidationException("Invalid OTP") if db_otp.otp != data.otp:
if db_otp.expireAt < datetime.datetime.now(): raise ValidationException("Invalid OTP")
raise ValidationException("OTP expired") if db_otp.expireAt < datetime.datetime.now():
raise ValidationException("OTP expired")
# OTP is valid, delete it to prevent reuse # OTP is valid, delete it to prevent reuse
# self.db.delete(db_otp) self.db.delete(db_otp)
# self.db.commit() self.db.commit()
return
return
except Exception as e:
self.logger.error(f"Error verifying OTP: {e}")
raise
finally:
self.db.close()
async def get_admins(self, user, limit:int, offset:int, search:str): async def get_admins(self, user, limit:int, offset:int, search:str):
try: try:
@ -138,112 +156,144 @@ class AuthService:
return common_response return common_response
except Exception as e: except Exception as e:
self.logger.error(f"Error getting admins: {e}")
raise e raise e
finally:
self.db.close()
async def create_super_admin(self, user, data: CreateSuperAdmin): async def create_super_admin(self, user, data: CreateSuperAdmin):
try:
if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("User is not authorized to perform this action")
if user["userType"] != UserType.SUPER_ADMIN: # password = "admin@123"
raise UnauthorizedException("User is not authorized to perform this action") password = generate_secure_password()
hashed_password = hash_password(password)
# password = "admin@123" # check if username and email are unique
password = generate_secure_password() existing_user = (
hashed_password = hash_password(password) self.db.query(Users)
.filter(
# check if username and email are unique Users.email == data.email.lower(),
existing_user = ( )
self.db.query(Users) .first()
.filter(
Users.email == data.email.lower(),
) )
.first()
)
if existing_user: if existing_user:
raise ValidationException("User with same email already exists") raise ValidationException("User with same email already exists")
user = Users( user = Users(
username=data.username, username=data.username,
email=data.email.lower(), email=data.email.lower(),
password=hashed_password, password=hashed_password,
userType=UserType.SUPER_ADMIN, userType=UserType.SUPER_ADMIN,
) )
self.db.add(user) self.db.add(user)
self.db.commit() self.db.commit()
LOGIN_URL = self.url LOGIN_URL = self.url
# send email to user # send email to user
self.email_service.send_new_admin_email(data.username, data.email, password, LOGIN_URL) self.email_service.send_new_admin_email(data.username, data.email, password, LOGIN_URL)
return return
except Exception as e:
self.logger.error(f"Error creating super admin: {e}")
raise e
finally:
self.db.close()
async def update_super_admin(self, user, user_id: int, data: UpdateSuperAdmin): async def update_super_admin(self, user, user_id: int, data: UpdateSuperAdmin):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("User is not authorized to perform this action") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("User is not authorized to perform this action")
user = self.db.query(Users).filter(Users.id == user_id).first() user = self.db.query(Users).filter(Users.id == user_id).first()
if not user: if not user:
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
user.username = data.username.lower() user.username = data.username.lower()
self.db.add(user) self.db.add(user)
self.db.commit() self.db.commit()
return return
except Exception as e:
self.logger.error(f"Error updating super admin: {e}")
raise e
finally:
self.db.close()
async def delete_super_admin(self, user, user_id: int): async def delete_super_admin(self, user, user_id: int):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("User is not authorized to perform this action") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("User is not authorized to perform this action")
user = self.db.query(Users).filter(Users.id == user_id).first() user = self.db.query(Users).filter(Users.id == user_id).first()
if not user: if not user:
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
user.soft_delete(self.db) user.soft_delete(self.db)
return
return
except Exception as e:
self.logger.error(f"Error deleting super admin: {e}")
raise e
finally:
self.db.close()
async def forget_password(self, email: str): async def forget_password(self, email: str):
user = self.db.query(Users).filter(Users.email == email.lower()).first() try:
user = self.db.query(Users).filter(Users.email == email.lower()).first()
if not user: if not user:
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
# get reset password token # get reset password token
reset_password_token = generate_reset_password_token() reset_password_token = generate_reset_password_token()
reset_password = ResetPasswordTokens(email=email, token=reset_password_token) reset_password = ResetPasswordTokens(email=email, token=reset_password_token)
self.db.add(reset_password) self.db.add(reset_password)
self.db.commit() self.db.commit()
reset_password_url = f"{self.url}/auth/reset-password?token={reset_password_token}" reset_password_url = f"{self.url}/auth/reset-password?token={reset_password_token}"
self.email_service.send_reset_password_email(email, reset_password_url) self.email_service.send_reset_password_email(email, reset_password_url)
return return
except Exception as e:
self.logger.error(f"Error forgetting password: {e}")
raise e
finally:
self.db.close()
async def reset_password(self, token: str, password: str): async def reset_password(self, token: str, password: str):
reset_password = self.db.query(ResetPasswordTokens).filter(ResetPasswordTokens.token == token).first() try:
reset_password = self.db.query(ResetPasswordTokens).filter(ResetPasswordTokens.token == token).first()
if not reset_password: if not reset_password:
raise ResourceNotFoundException("Reset password token not found") raise ResourceNotFoundException("Reset password token not found")
user = self.db.query(Users).filter(Users.email == reset_password.email).first() user = self.db.query(Users).filter(Users.email == reset_password.email).first()
if not user: if not user:
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
user.password = hash_password(password) user.password = hash_password(password)
self.db.delete(reset_password) self.db.delete(reset_password)
self.db.commit() self.db.commit()
return
except Exception as e:
self.logger.error(f"Error resetting password: {e}")
raise e
finally:
self.db.close()
return

View File

@ -13,32 +13,46 @@ from models.CallTranscripts import CallTranscripts
from exceptions.business_exception import BusinessValidationException from exceptions.business_exception import BusinessValidationException
from services.s3Service import get_signed_url from services.s3Service import get_signed_url
from interface.common_response import CommonResponse from interface.common_response import CommonResponse
from loguru import logger
from exceptions.db_exceptions import DBExceptionHandler
class CallTranscriptServices: class CallTranscriptServices:
def __init__(self): def __init__(self):
self.db:Session = next(get_db()) self.db:Session = next(get_db())
self.logger = logger
async def get_call_transcripts(self, limit:int, offset:int): async def get_call_transcripts(self, limit:int, offset:int):
call_transcripts = self.db.query(CallTranscripts).limit(limit).offset(offset).all() try:
call_transcripts = self.db.query(CallTranscripts).limit(limit).offset(offset).all()
total = self.db.query(CallTranscripts).count() total = self.db.query(CallTranscripts).count()
response = [CallTranscriptsResponse(**call_transcript.__dict__.copy()) for call_transcript in call_transcripts] response = [CallTranscriptsResponse(**call_transcript.__dict__.copy()) for call_transcript in call_transcripts]
for call_transcript in response: for call_transcript in response:
call_transcript.transcript_key_id = get_signed_url(call_transcript.transcript_key_id) call_transcript.transcript_key_id = get_signed_url(call_transcript.transcript_key_id)
return_response = CommonResponse(data=response, total=total) return_response = CommonResponse(data=response, total=total)
return return_response return return_response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting call transcripts")
finally:
self.db.close()
def download_call_transcript(self, key_id: str): def download_call_transcript(self, key_id: str):
call_transcript = self.db.query(CallTranscripts).filter(CallTranscripts.transcript_key_id == key_id).first() try:
call_transcript = self.db.query(CallTranscripts).filter(CallTranscripts.transcript_key_id == key_id).first()
if not call_transcript: if not call_transcript:
raise BusinessValidationException("Call transcript not found!") raise BusinessValidationException("Call transcript not found!")
return get_signed_url(call_transcript.transcript_key_id) return get_signed_url(call_transcript.transcript_key_id)
except Exception as e:
DBExceptionHandler.handle_exception(e, context="downloading call transcript")
finally:
self.db.close()
def download_file(self, url: str, file_path: str) -> None: def download_file(self, url: str, file_path: str) -> None:
@ -85,61 +99,65 @@ class CallTranscriptServices:
print(f"Error during cleanup: {e}") print(f"Error during cleanup: {e}")
async def bulk_download_call_transcripts(self, key_ids: list[int], background_tasks: BackgroundTasks): async def bulk_download_call_transcripts(self, key_ids: list[int], background_tasks: BackgroundTasks):
try:
transcript_ids = self.db.query(CallTranscripts).filter(CallTranscripts.id.in_(key_ids)).all()
transcript_ids = self.db.query(CallTranscripts).filter(CallTranscripts.id.in_(key_ids)).all() keys = [transcript.transcript_key_id for transcript in transcript_ids]
keys = [transcript.transcript_key_id for transcript in transcript_ids] if len(keys) < 1:
raise BusinessValidationException("No call transcripts found!")
if len(keys) < 1:
raise BusinessValidationException("No call transcripts found!")
temp_dir = tempfile.mkdtemp(prefix="call_transcripts_") temp_dir = tempfile.mkdtemp(prefix="call_transcripts_")
zip_path = os.path.join(temp_dir, "call_transcripts.zip") zip_path = os.path.join(temp_dir, "call_transcripts.zip")
# Prepare download information # Prepare download information
download_info = [] download_info = []
for key in keys: for key in keys:
# Generate signed URL for each key # Generate signed URL for each key
url = get_signed_url(key) url = get_signed_url(key)
# Determine filename (using key's basename or a formatted name) # Determine filename (using key's basename or a formatted name)
filename = os.path.basename(key) filename = os.path.basename(key)
file_path = os.path.join(temp_dir, filename) file_path = os.path.join(temp_dir, filename)
download_info.append((url, file_path, filename)) download_info.append((url, file_path, filename))
# Use ThreadPoolExecutor for concurrent downloads # Use ThreadPoolExecutor for concurrent downloads
# Adjust max_workers based on your system capabilities and S3 rate limits # Adjust max_workers based on your system capabilities and S3 rate limits
max_workers = min(32, len(download_info)) # Cap at 32 threads or number of files, whichever is smaller max_workers = min(32, len(download_info)) # Cap at 32 threads or number of files, whichever is smaller
with ThreadPoolExecutor(max_workers=max_workers) as executor: with ThreadPoolExecutor(max_workers=max_workers) as executor:
# Submit all download tasks # Submit all download tasks
future_to_file = {executor.submit(self.download_file, url, file_path): (file_path, filename) future_to_file = {executor.submit(self.download_file, url, file_path): (file_path, filename)
for url, file_path, filename in download_info} for url, file_path, filename in download_info}
# Collect results as they complete # Collect results as they complete
file_paths = [] file_paths = []
for future in as_completed(future_to_file): for future in as_completed(future_to_file):
file_path, filename = future_to_file[future] file_path, filename = future_to_file[future]
try: try:
future.result() # Get the result to catch any exceptions future.result() # Get the result to catch any exceptions
file_paths.append((file_path, filename)) file_paths.append((file_path, filename))
except Exception as e: except Exception as e:
print(f"Error downloading {filename}: {e}") print(f"Error downloading {filename}: {e}")
# Create zip file from downloaded files # Create zip file from downloaded files
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file: with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for file_path, arcname in file_paths: for file_path, arcname in file_paths:
if os.path.exists(file_path): if os.path.exists(file_path):
zip_file.write(file_path, arcname=arcname) zip_file.write(file_path, arcname=arcname)
# Add cleanup task to run after response is sent # Add cleanup task to run after response is sent
background_tasks.add_task(self.cleanup_temp_files, temp_dir, zip_path) background_tasks.add_task(self.cleanup_temp_files, temp_dir, zip_path)
# Return the zip file as a response # Return the zip file as a response
return FileResponse( return FileResponse(
path=zip_path, path=zip_path,
media_type="application/zip", media_type="application/zip",
filename="call_transcripts.zip", filename="call_transcripts.zip",
background=background_tasks background=background_tasks
) )
except Exception as e:
DBExceptionHandler.handle_exception(e, context="bulk downloading call transcripts")
finally:
self.db.close()

View File

@ -80,6 +80,8 @@ class ClinicDoctorsServices:
self.logger.error(e) self.logger.error(e)
self.db.rollback() self.db.rollback()
raise e raise e
finally:
self.db.close()
async def update_clinic_doctor( async def update_clinic_doctor(
self, user, clinic_doctor_id: int, clinic_doctor_data: ClinicDoctorUpdate self, user, clinic_doctor_id: int, clinic_doctor_data: ClinicDoctorUpdate
@ -155,6 +157,8 @@ class ClinicDoctorsServices:
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
raise e raise e
finally:
self.db.close()
async def delete_clinic_doctor(self, clinic_doctor_id: int): async def delete_clinic_doctor(self, clinic_doctor_id: int):
try: try:
@ -167,26 +171,32 @@ class ClinicDoctorsServices:
self.db.commit() self.db.commit()
except Exception as e: except Exception as e:
raise e raise e
finally:
self.db.close()
async def get_doctor_status_count(self): async def get_doctor_status_count(self):
try:
# Query to count doctors by status # Query to count doctors by status
status_counts = ( status_counts = (
self.db.query( self.db.query(
ClinicDoctors.status, func.count(ClinicDoctors.id).label("count") ClinicDoctors.status, func.count(ClinicDoctors.id).label("count")
)
.group_by(ClinicDoctors.status)
.all()
) )
.group_by(ClinicDoctors.status)
.all()
)
# Initialize result dictionary with all possible statuses set to 0 # Initialize result dictionary with all possible statuses set to 0
result = {status.value: 0 for status in ClinicDoctorStatus} result = {status.value: 0 for status in ClinicDoctorStatus}
# Update with actual counts from the query # Update with actual counts from the query
for status, count in status_counts: for status, count in status_counts:
result[status.value] = count result[status.value] = count
return result return result
except Exception as e:
raise e
finally:
self.db.close()
async def get_clinic_doctors(self, limit: int, offset: int, search: str = "", sort_by: str = DEFAULT_ORDER, sort_order: str = DEFAULT_ORDER_BY): async def get_clinic_doctors(self, limit: int, offset: int, search: str = "", sort_by: str = DEFAULT_ORDER, sort_order: str = DEFAULT_ORDER_BY):
try: try:
@ -258,3 +268,5 @@ class ClinicDoctorsServices:
except Exception as e: except Exception as e:
self.logger.error(e) self.logger.error(e)
raise e raise e
finally:
self.db.close()

View File

@ -14,325 +14,390 @@ from services.s3Service import get_file_key, get_signed_url
from models import ClinicFileVerifications, ClinicOffers, Clinics, Users from models import ClinicFileVerifications, ClinicOffers, Clinics, Users
from schemas.BaseSchemas import ClinicFileVerificationBase, ClinicOffersBase from schemas.BaseSchemas import ClinicFileVerificationBase, ClinicOffersBase
from services.emailService import EmailService from services.emailService import EmailService
from loguru import logger
from sqlalchemy import func
from exceptions.db_exceptions import DBExceptionHandler
class ClinicServices: class ClinicServices:
def __init__(self): def __init__(self):
self.db: Session = next(get_db()) self.db: Session = next(get_db())
self.email_service = EmailService() self.email_service = EmailService()
self.logger = logger
async def get_clinics(self, user, limit:int, offset:int, filter_type: Union[Literal["UNREGISTERED"], Literal["REGISTERED"]] = "UNREGISTERED", search:str = ""): async def get_clinics(self, user, limit:int, offset:int, filter_type: Union[Literal["UNREGISTERED"], Literal["REGISTERED"]] = "UNREGISTERED", search:str = ""):
try:
if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to get clinics")
if user["userType"] != UserType.SUPER_ADMIN: clinics_query = self.db.query(Clinics).order_by(Clinics.update_time.desc())
raise UnauthorizedException("You are not authorized to get clinics")
clinics_query = self.db.query(Clinics).order_by(Clinics.update_time.desc()) if filter_type == "UNREGISTERED":
clinics_query = clinics_query.filter(not_(Clinics.status == ClinicStatus.ACTIVE),not_(Clinics.status == ClinicStatus.INACTIVE))
elif filter_type == "REGISTERED":
clinics_query = clinics_query.filter(or_(Clinics.status == ClinicStatus.ACTIVE,Clinics.status == ClinicStatus.INACTIVE))
if filter_type == "UNREGISTERED": if search:
clinics_query = clinics_query.filter(not_(Clinics.status == ClinicStatus.ACTIVE),not_(Clinics.status == ClinicStatus.INACTIVE)) clinics_query = clinics_query.filter(
elif filter_type == "REGISTERED": or_(
clinics_query = clinics_query.filter(or_(Clinics.status == ClinicStatus.ACTIVE,Clinics.status == ClinicStatus.INACTIVE)) Clinics.name.contains(search),
Clinics.email.contains(search),
if search: Clinics.phone.contains(search),
clinics_query = clinics_query.filter( Clinics.address.contains(search)
or_(
Clinics.name.contains(search),
Clinics.email.contains(search),
Clinics.phone.contains(search),
Clinics.address.contains(search)
)
) )
)
clinics = clinics_query.offset(offset).limit(limit).all() clinics = clinics_query.offset(offset).limit(limit).all()
count_query = text(""" count_query = text("""
SELECT SELECT
COUNT(*) as total, COUNT(*) as total,
COUNT(CASE WHEN status = 'ACTIVE' OR status = 'INACTIVE' THEN 1 END) as active, COUNT(CASE WHEN status = 'ACTIVE' OR status = 'INACTIVE' THEN 1 END) as active,
COUNT(CASE WHEN status != 'ACTIVE' AND status != 'INACTIVE' THEN 1 END) as rejected COUNT(CASE WHEN status != 'ACTIVE' AND status != 'INACTIVE' THEN 1 END) as rejected
FROM clinics FROM clinics
""") """)
result = self.db.execute(count_query).first() result = self.db.execute(count_query).first()
totalClinics = result.total or 0 totalClinics = result.total or 0
totalRegisteredClinics = result.active or 0 totalRegisteredClinics = result.active or 0
totalRejectedClinics = result.rejected or 0 totalRejectedClinics = result.rejected or 0
clinic_response = [Clinic(**clinic.__dict__.copy()) for clinic in clinics] clinic_response = [Clinic(**clinic.__dict__.copy()) for clinic in clinics]
for clinic in clinic_response: for clinic in clinic_response:
clinic.logo = await get_signed_url(clinic.logo) if clinic.logo else None clinic.logo = await get_signed_url(clinic.logo) if clinic.logo else None
clinic_response_with_total = { clinic_response_with_total = {
"clinics": clinic_response, "clinics": clinic_response,
"totalRegisteredClinics": totalRegisteredClinics, "totalRegisteredClinics": totalRegisteredClinics,
"totalRejectedClinics": totalRejectedClinics, "totalRejectedClinics": totalRejectedClinics,
} }
response = CommonResponse(data=clinic_response_with_total, total=totalClinics) response = CommonResponse(data=clinic_response_with_total, total=totalClinics)
return response return response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting clinics")
finally:
self.db.close()
async def get_latest_clinic_id(self) -> int: async def get_latest_clinic_id(self) -> int:
clinic = self.db.query(Clinics).order_by(Clinics.id.desc()).first() try:
return clinic.id if clinic else 0 clinic = self.db.query(Clinics).order_by(Clinics.id.desc()).first()
return clinic.id if clinic else 0
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting latest clinic id")
finally:
self.db.close()
async def get_clinic_by_id(self, clinic_id: int): async def get_clinic_by_id(self, clinic_id: int):
clinic = self.db.query(Clinics).options(joinedload(Clinics.creator)).filter(Clinics.id == clinic_id).first() try:
clinic = self.db.query(Clinics).options(joinedload(Clinics.creator)).filter(Clinics.id == clinic_id).first()
if clinic is None: if clinic is None:
raise ResourceNotFoundException("Clinic not found") raise ResourceNotFoundException("Clinic not found")
clinic_response = Clinic(**clinic.__dict__.copy()) clinic_response = Clinic(**clinic.__dict__.copy())
clinic_response.logo = await get_signed_url(clinic_response.logo) if clinic_response.logo else None clinic_response.logo = await get_signed_url(clinic_response.logo) if clinic_response.logo else None
clinic_response.abn_doc = await get_signed_url(clinic_response.abn_doc) if clinic_response.abn_doc else None clinic_response.abn_doc = await get_signed_url(clinic_response.abn_doc) if clinic_response.abn_doc else None
clinic_response.contract_doc = await get_signed_url(clinic_response.contract_doc) if clinic_response.contract_doc else None clinic_response.contract_doc = await get_signed_url(clinic_response.contract_doc) if clinic_response.contract_doc else None
clinicFiles = None clinicFiles = None
if(clinic.status != ClinicStatus.ACTIVE): if(clinic.status != ClinicStatus.ACTIVE):
clinicFiles = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first() clinicFiles = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
clinic_resp = { clinic_resp = {
"clinic": clinic_response, "clinic": clinic_response,
"creator": { "creator": {
"name": clinic.creator.username, "name": clinic.creator.username,
"email": clinic.creator.email, "email": clinic.creator.email,
"phone": clinic.creator.mobile, "phone": clinic.creator.mobile,
"designation": clinic.creator.clinicRole "designation": clinic.creator.clinicRole
}, },
"clinic_files": await self.get_clinic_files(clinic_id), "clinic_files": await self.get_clinic_files(clinic_id),
"fileStatus": {"reason":clinicFiles.rejection_reason if clinicFiles else None}, "fileStatus": {"reason":clinicFiles.rejection_reason if clinicFiles else None},
} }
return clinic_resp return clinic_resp
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting clinic by id")
finally:
self.db.close()
async def get_clinic_files(self, clinic_id: int): async def get_clinic_files(self, clinic_id: int):
clinic_files = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first() try:
clinic_files = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
if clinic_files is None: if clinic_files is None:
raise ResourceNotFoundException("Clinic not found") raise ResourceNotFoundException("Clinic not found")
response = ClinicFileVerificationBase(**clinic_files.__dict__.copy()) response = ClinicFileVerificationBase(**clinic_files.__dict__.copy())
return response return response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting clinic files")
finally:
self.db.close()
async def update_clinic(self, user, clinic_id: int, clinic_data: ClinicUpdate): async def update_clinic(self, user, clinic_id: int, clinic_data: ClinicUpdate):
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() try:
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first()
if clinic is None: if clinic is None:
raise ResourceNotFoundException("Clinic not found") raise ResourceNotFoundException("Clinic not found")
if clinic.creator_id != user["id"]: if clinic.creator_id != user["id"]:
raise UnauthorizedException("You are not authorized to update this clinic") raise UnauthorizedException("You are not authorized to update this clinic")
clinic_data.abn_doc = get_file_key(clinic_data.abn_doc) if clinic_data.abn_doc else None clinic_data.abn_doc = get_file_key(clinic_data.abn_doc) if clinic_data.abn_doc else None
clinic_data.contract_doc = get_file_key(clinic_data.contract_doc) if clinic_data.contract_doc else None clinic_data.contract_doc = get_file_key(clinic_data.contract_doc) if clinic_data.contract_doc else None
clinic_data.logo = get_file_key(clinic_data.logo) if clinic_data.logo else None clinic_data.logo = get_file_key(clinic_data.logo) if clinic_data.logo else None
update_data = clinic_data.model_dump(exclude_unset=True) update_data = clinic_data.model_dump(exclude_unset=True)
for key, value in update_data.items(): for key, value in update_data.items():
setattr(clinic, key, value) setattr(clinic, key, value)
self.db.add(clinic) self.db.add(clinic)
self.db.commit() self.db.commit()
self.db.refresh(clinic) self.db.refresh(clinic)
clinic_response = Clinic(**clinic.__dict__.copy()) clinic_response = Clinic(**clinic.__dict__.copy())
clinic_response.logo = get_signed_url(clinic_response.logo) if clinic_response.logo else None clinic_response.logo = get_signed_url(clinic_response.logo) if clinic_response.logo else None
return clinic_response return clinic_response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="updating clinic")
finally:
self.db.close()
async def delete_clinic(self, clinic_id: int): async def delete_clinic(self, clinic_id: int):
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() try:
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first()
if clinic is None: if clinic is None:
raise ResourceNotFoundException("Clinic not found") raise ResourceNotFoundException("Clinic not found")
clinic.soft_delete(self.db) clinic.soft_delete(self.db)
except Exception as e:
DBExceptionHandler.handle_exception(e, context="deleting clinic")
finally:
self.db.close()
async def get_clinic_count(self): async def get_clinic_count(self):
from sqlalchemy import func try:
# Get total count
totalClinics = self.db.query(Clinics).count()
# Get total count # Get counts for specific statuses in a single query
totalClinics = self.db.query(Clinics).count() status_counts = self.db.query(
Clinics.status,
func.count(Clinics.id).label('count')
).filter(
Clinics.status.in_([
ClinicStatus.ACTIVE,
ClinicStatus.UNDER_REVIEW,
ClinicStatus.REJECTED
])
).group_by(Clinics.status).all()
# Get counts for specific statuses in a single query # Initialize counts with 0
status_counts = self.db.query( counts = {
Clinics.status, "totalClinics": totalClinics,
func.count(Clinics.id).label('count') "totalActiveClinics": 0,
).filter( "totalUnderReviewClinics": 0,
Clinics.status.in_([ "totalRejectedClinics": 0
ClinicStatus.ACTIVE, }
ClinicStatus.UNDER_REVIEW,
ClinicStatus.REJECTED
])
).group_by(Clinics.status).all()
# Initialize counts with 0 # Map status values to their respective count keys
counts = { status_to_key = {
"totalClinics": totalClinics, ClinicStatus.ACTIVE: "totalActiveClinics",
"totalActiveClinics": 0, ClinicStatus.UNDER_REVIEW: "totalUnderReviewClinics",
"totalUnderReviewClinics": 0, ClinicStatus.REJECTED: "totalRejectedClinics"
"totalRejectedClinics": 0 }
}
# Map status values to their respective count keys # Update counts with actual values from the query
status_to_key = { for status, count in status_counts:
ClinicStatus.ACTIVE: "totalActiveClinics", key = status_to_key.get(status)
ClinicStatus.UNDER_REVIEW: "totalUnderReviewClinics", if key:
ClinicStatus.REJECTED: "totalRejectedClinics" counts[key] = count
}
# Update counts with actual values from the query return counts
for status, count in status_counts: except Exception as e:
key = status_to_key.get(status) DBExceptionHandler.handle_exception(e, context="getting clinic count")
if key: finally:
counts[key] = count self.db.close()
return counts
async def update_clinic_status(self, user, clinic_id: int, status: ClinicStatus, documentStatus: Optional[dict] = None, rejection_reason: Optional[str] = None): async def update_clinic_status(self, user, clinic_id: int, status: ClinicStatus, documentStatus: Optional[dict] = None, rejection_reason: Optional[str] = None):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("You are not authorized to update clinic status") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to update clinic status")
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first()
if clinic is None: if clinic is None:
raise ResourceNotFoundException("Clinic not found") raise ResourceNotFoundException("Clinic not found")
clinic.status = status clinic.status = status
self.db.add(clinic) self.db.add(clinic)
self.db.commit()
self.db.refresh(clinic)
if clinic.status == ClinicStatus.ACTIVE:
clinic_file_verification = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
clinic_file_verification.logo_is_verified = True
clinic_file_verification.abn_doc_is_verified = True
clinic_file_verification.contract_doc_is_verified = True
self.db.add(clinic_file_verification)
self.db.commit() self.db.commit()
self.db.refresh(clinic)
# send mail to user if clinic.status == ClinicStatus.ACTIVE:
self.email_service.send_apporve_clinic_email(clinic.email, clinic.name) clinic_file_verification = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
clinic_file_verification.logo_is_verified = True
clinic_file_verification.abn_doc_is_verified = True
clinic_file_verification.contract_doc_is_verified = True
self.db.add(clinic_file_verification)
self.db.commit()
# send mail to user
self.email_service.send_apporve_clinic_email(clinic.email, clinic.name)
if clinic.status == ClinicStatus.REJECTED or clinic.status == ClinicStatus.UNDER_REVIEW: if clinic.status == ClinicStatus.REJECTED or clinic.status == ClinicStatus.UNDER_REVIEW:
clinic_file_verification = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first() clinic_file_verification = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
if documentStatus and "LOGO" in documentStatus: if documentStatus and "LOGO" in documentStatus:
clinic_file_verification.logo_is_verified = documentStatus.get("LOGO") clinic_file_verification.logo_is_verified = documentStatus.get("LOGO")
clinic_file_verification.rejection_reason = rejection_reason clinic_file_verification.rejection_reason = rejection_reason
if documentStatus and "ABN" in documentStatus: if documentStatus and "ABN" in documentStatus:
clinic_file_verification.abn_doc_is_verified = documentStatus.get("ABN") clinic_file_verification.abn_doc_is_verified = documentStatus.get("ABN")
clinic_file_verification.rejection_reason = rejection_reason clinic_file_verification.rejection_reason = rejection_reason
if documentStatus and "CONTRACT" in documentStatus: if documentStatus and "CONTRACT" in documentStatus:
clinic_file_verification.contract_doc_is_verified = documentStatus.get("CONTRACT") clinic_file_verification.contract_doc_is_verified = documentStatus.get("CONTRACT")
clinic_file_verification.rejection_reason = rejection_reason clinic_file_verification.rejection_reason = rejection_reason
self.db.add(clinic_file_verification) self.db.add(clinic_file_verification)
self.db.commit() self.db.commit()
# send mail to user # send mail to user
self.email_service.send_reject_clinic_email(clinic.email, clinic.name) self.email_service.send_reject_clinic_email(clinic.email, clinic.name)
# if rejected or under review then email to clinic creator # if rejected or under review then email to clinic creator
if clinic.status == ClinicStatus.REJECTED or clinic.status == ClinicStatus.UNDER_REVIEW: if clinic.status == ClinicStatus.REJECTED or clinic.status == ClinicStatus.UNDER_REVIEW:
pass pass
# handle inactive status # handle inactive status
if clinic.status == ClinicStatus.INACTIVE: if clinic.status == ClinicStatus.INACTIVE:
# get clinic creator # get clinic creator
# clinic_creator = self.db.query(Users).options(joinedload(Users.created_clinics)).filter(Clinics.id == clinic.id).first() # clinic_creator = self.db.query(Users).options(joinedload(Users.created_clinics)).filter(Clinics.id == clinic.id).first()
# # block clinic creator # # block clinic creator
# clinic_creator.isBlocked = True # clinic_creator.isBlocked = True
# self.db.add(clinic_creator) # self.db.add(clinic_creator)
# self.db.commit() # self.db.commit()
pass pass
return return
except Exception as e:
DBExceptionHandler.handle_exception(e, context="updating clinic status")
finally:
self.db.close()
async def get_clinic_offer_by_clinic_email(self, clinic_email: str): async def get_clinic_offer_by_clinic_email(self, clinic_email: str):
clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic_email).first() try:
return clinic_offer clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic_email).first()
return clinic_offer
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting clinic offer by clinic email")
finally:
self.db.close()
async def get_clinic_offers(self, user, limit:int, offset:int, search:str = ""): async def get_clinic_offers(self, user, limit:int, offset:int, search:str = ""):
try:
if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to get clinic offers")
if user["userType"] != UserType.SUPER_ADMIN: clinic_offers_query = self.db.query(ClinicOffers)
raise UnauthorizedException("You are not authorized to get clinic offers")
clinic_offers_query = self.db.query(ClinicOffers)
total = clinic_offers_query.count()
if search:
clinic_offers_query = clinic_offers_query.filter(ClinicOffers.clinic_email.contains(search))
total = clinic_offers_query.count() total = clinic_offers_query.count()
clinic_offers = clinic_offers_query.limit(limit).offset(offset).all() if search:
clinic_offers_query = clinic_offers_query.filter(ClinicOffers.clinic_email.contains(search))
total = clinic_offers_query.count()
clinic_offers_response = [ClinicOfferResponse(**clinic_offer.__dict__.copy()) for clinic_offer in clinic_offers] clinic_offers = clinic_offers_query.limit(limit).offset(offset).all()
response = CommonResponse(data=clinic_offers_response, total=total) clinic_offers_response = [ClinicOfferResponse(**clinic_offer.__dict__.copy()) for clinic_offer in clinic_offers]
return response response = CommonResponse(data=clinic_offers_response, total=total)
return response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting clinic offers")
finally:
self.db.close()
async def create_clinic_offer(self, user, clinic_offer_data: ClinicOffersBase): async def create_clinic_offer(self, user, clinic_offer_data: ClinicOffersBase):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("You are not authorized to create clinic offer") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to create clinic offer")
existing_offer = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic_offer_data.clinic_email).first() existing_offer = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic_offer_data.clinic_email).first()
if existing_offer: if existing_offer:
raise ValidationException("Offer already exists for this clinic") raise ValidationException("Offer already exists for this clinic")
clinic_offer = ClinicOffers(**clinic_offer_data.model_dump()) clinic_offer = ClinicOffers(**clinic_offer_data.model_dump())
self.db.add(clinic_offer) self.db.add(clinic_offer)
self.db.commit() self.db.commit()
return return
except Exception as e:
DBExceptionHandler.handle_exception(e, context="creating clinic offer")
finally:
self.db.close()
async def update_clinic_offer(self, user, clinic_offer_id: int, clinic_offer_data: ClinicOffersBase): async def update_clinic_offer(self, user, clinic_offer_id: int, clinic_offer_data: ClinicOffersBase):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("You are not authorized to update clinic offer") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to update clinic offer")
clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.id == clinic_offer_id).first() clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.id == clinic_offer_id).first()
if clinic_offer is None: if clinic_offer is None:
raise ResourceNotFoundException("Clinic offer not found") raise ResourceNotFoundException("Clinic offer not found")
clinic_offer.setup_fees_waived = clinic_offer_data.setup_fees_waived clinic_offer.setup_fees_waived = clinic_offer_data.setup_fees_waived
clinic_offer.special_offer_for_month = clinic_offer_data.special_offer_for_month clinic_offer.special_offer_for_month = clinic_offer_data.special_offer_for_month
self.db.add(clinic_offer) self.db.add(clinic_offer)
self.db.commit() self.db.commit()
return return
except Exception as e:
DBExceptionHandler.handle_exception(e, context="updating clinic offer")
finally:
self.db.close()
async def delete_clinic_offer(self, user, clinic_offer_id: int): async def delete_clinic_offer(self, user, clinic_offer_id: int):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException("You are not authorized to delete clinic offer") if user["userType"] != UserType.SUPER_ADMIN:
raise UnauthorizedException("You are not authorized to delete clinic offer")
clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.id == clinic_offer_id).first() clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.id == clinic_offer_id).first()
if clinic_offer is None: if clinic_offer is None:
raise ResourceNotFoundException("Clinic offer not found") raise ResourceNotFoundException("Clinic offer not found")
clinic_offer.soft_delete(self.db) clinic_offer.soft_delete(self.db)
return return
except Exception as e:
DBExceptionHandler.handle_exception(e, context="deleting clinic offer")
finally:
self.db.close()

View File

@ -7,12 +7,13 @@ from models.SignupPricingMaster import SignupPricingMaster
from exceptions import UnauthorizedException from exceptions import UnauthorizedException
from enums.enums import UserType from enums.enums import UserType
from exceptions import ResourceNotFoundException from exceptions import ResourceNotFoundException
from loguru import logger
class DashboardService: class DashboardService:
def __init__(self): def __init__(self):
self.db = next(get_db()) self.db = next(get_db())
self.clinicDoctorsServices = ClinicDoctorsServices() self.clinicDoctorsServices = ClinicDoctorsServices()
self.clinicServices = ClinicServices() self.clinicServices = ClinicServices()
self.logger = logger
async def get_dashboard_counts(self, isSuperAdmin: bool): async def get_dashboard_counts(self, isSuperAdmin: bool):
if isSuperAdmin: if isSuperAdmin:
@ -25,50 +26,62 @@ class DashboardService:
async def update_signup_pricing_master( async def update_signup_pricing_master(
self, user, pricing_data: SignupPricingMasterBase self, user, pricing_data: SignupPricingMasterBase
): ):
if user["userType"] != UserType.SUPER_ADMIN: try:
raise UnauthorizedException( if user["userType"] != UserType.SUPER_ADMIN:
"You are not authorized to update signup pricing master" raise UnauthorizedException(
) "You are not authorized to update signup pricing master"
)
existing_pricing = self.db.query(SignupPricingMaster).first() existing_pricing = self.db.query(SignupPricingMaster).first()
if existing_pricing is None: if existing_pricing is None:
# Create new record # Create new record
new_pricing = SignupPricingMaster( new_pricing = SignupPricingMaster(
**pricing_data.model_dump() **pricing_data.model_dump()
) )
self.db.add(new_pricing) self.db.add(new_pricing)
self.db.commit() self.db.commit()
self.db.refresh(new_pricing) self.db.refresh(new_pricing)
response = SignupPricingMasterResponse( response = SignupPricingMasterResponse(
**new_pricing.__dict__.copy() **new_pricing.__dict__.copy()
) )
return response return response
else: else:
# Update existing record with values from the request # Update existing record with values from the request
existing_pricing.setup_fees = pricing_data.setup_fees existing_pricing.setup_fees = pricing_data.setup_fees
existing_pricing.subscription_fees = pricing_data.subscription_fees existing_pricing.subscription_fees = pricing_data.subscription_fees
existing_pricing.per_call_charges = pricing_data.per_call_charges existing_pricing.per_call_charges = pricing_data.per_call_charges
self.db.commit() self.db.commit()
self.db.refresh(existing_pricing) self.db.refresh(existing_pricing)
response = SignupPricingMasterResponse( response = SignupPricingMasterResponse(
**existing_pricing.__dict__.copy() **existing_pricing.__dict__.copy()
) )
return response return response
except Exception as e:
self.logger.error(f"Error updating signup pricing master: {e}")
raise e
finally:
self.db.close()
async def get_signup_pricing_master(self): async def get_signup_pricing_master(self):
signup_pricing_master = self.db.query(SignupPricingMaster).first() try:
if signup_pricing_master is None: signup_pricing_master = self.db.query(SignupPricingMaster).first()
raise ResourceNotFoundException("Signup pricing master not found") if signup_pricing_master is None:
raise ResourceNotFoundException("Signup pricing master not found")
response = SignupPricingMasterResponse( response = SignupPricingMasterResponse(
**signup_pricing_master.__dict__.copy() **signup_pricing_master.__dict__.copy()
) )
return response return response
except Exception as e:
self.logger.error(f"Error getting signup pricing master: {e}")
raise e
finally:
self.db.close()

View File

@ -6,61 +6,86 @@ from schemas.BaseSchemas import MasterAppointmentTypeBase
from exceptions import ResourceNotFoundException from exceptions import ResourceNotFoundException
from interface.common_response import CommonResponse from interface.common_response import CommonResponse
from schemas.ResponseSchemas import MasterAppointmentTypeResponse from schemas.ResponseSchemas import MasterAppointmentTypeResponse
from loguru import logger
class MasterAppointmentServices: class MasterAppointmentServices:
def __init__(self): def __init__(self):
self.db: Session = next(get_db()) self.db: Session = next(get_db())
self.logger = logger
async def get_master_appointment_types(self): async def get_master_appointment_types(self):
appointment_types = self.db.query(MasterAppointmentTypes).all() try:
total= self.db.query(MasterAppointmentTypes).count() appointment_types = self.db.query(MasterAppointmentTypes).all()
response = CommonResponse(data=[MasterAppointmentTypeResponse(**appointment_type.__dict__.copy()) for appointment_type in appointment_types], total=total) total= self.db.query(MasterAppointmentTypes).count()
return response response = CommonResponse(data=[MasterAppointmentTypeResponse(**appointment_type.__dict__.copy()) for appointment_type in appointment_types], total=total)
return response
except Exception as e:
self.logger.error(f"Error getting master appointment types: {e}")
raise e
finally:
self.db.close()
def is_appointment_type_exists(self, appointment_type: MasterAppointmentTypeBase): def is_appointment_type_exists(self, appointment_type: MasterAppointmentTypeBase):
existing_appointment_type = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.type == appointment_type.type.lower()).first() existing_appointment_type = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.type == appointment_type.type.lower()).first()
return existing_appointment_type return existing_appointment_type
async def create_master_appointment_type(self, appointment_type: MasterAppointmentTypeBase): async def create_master_appointment_type(self, appointment_type: MasterAppointmentTypeBase):
try:
# get existing appointment type
existing_appointment_type = self.is_appointment_type_exists(appointment_type)
# get existing appointment type if existing_appointment_type:
existing_appointment_type = self.is_appointment_type_exists(appointment_type) return
if existing_appointment_type: appointment_type.type = appointment_type.type.lower()
appointment_type = MasterAppointmentTypes(**appointment_type.model_dump())
self.db.add(appointment_type)
self.db.commit()
return return
except Exception as e:
appointment_type.type = appointment_type.type.lower() self.logger.error(f"Error creating master appointment type: {e}")
raise e
appointment_type = MasterAppointmentTypes(**appointment_type.model_dump()) finally:
self.db.add(appointment_type) self.db.close()
self.db.commit()
return
async def delete_master_appointment_type(self, appointment_type_id: int): async def delete_master_appointment_type(self, appointment_type_id: int):
appointment_type = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.id == appointment_type_id).first() try:
appointment_type = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.id == appointment_type_id).first()
if appointment_type is None: if appointment_type is None:
raise ResourceNotFoundException("Appointment type not found") raise ResourceNotFoundException("Appointment type not found")
appointment_type.soft_delete(self.db) appointment_type.soft_delete(self.db)
self.db.commit() self.db.commit()
return return
except Exception as e:
self.logger.error(f"Error deleting master appointment type: {e}")
raise e
finally:
self.db.close()
async def update_master_appointment_type(self, appointment_type_id: int, appointment_type: MasterAppointmentTypeBase): async def update_master_appointment_type(self, appointment_type_id: int, appointment_type: MasterAppointmentTypeBase):
appointment_type_db = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.id == appointment_type_id).first() try:
appointment_type_db = self.db.query(MasterAppointmentTypes).filter(MasterAppointmentTypes.id == appointment_type_id).first()
if appointment_type_db is None: if appointment_type_db is None:
raise ResourceNotFoundException("Appointment type not found") raise ResourceNotFoundException("Appointment type not found")
# get existing appointment type # get existing appointment type
existing_appointment_type = self.is_appointment_type_exists(appointment_type) existing_appointment_type = self.is_appointment_type_exists(appointment_type)
if existing_appointment_type and existing_appointment_type.id != appointment_type_id: if existing_appointment_type and existing_appointment_type.id != appointment_type_id:
raise ResourceNotFoundException("Appointment type already exists") raise ResourceNotFoundException("Appointment type already exists")
appointment_type_db.type = appointment_type.type appointment_type_db.type = appointment_type.type
self.db.add(appointment_type_db) self.db.add(appointment_type_db)
self.db.commit() self.db.commit()
return return
except Exception as e:
self.logger.error(f"Error updating master appointment type: {e}")
raise e
finally:
self.db.close()

View File

@ -186,6 +186,8 @@ class StripeServices:
except stripe.error.StripeError as e: except stripe.error.StripeError as e:
self.logger.error(f"Error creating checkout session: {e}") self.logger.error(f"Error creating checkout session: {e}")
raise raise
finally:
self.db.close()
async def handle_webhook(self, request: Request): async def handle_webhook(self, request: Request):
@ -212,6 +214,8 @@ class StripeServices:
except stripe.error.SignatureVerificationError as e: except stripe.error.SignatureVerificationError as e:
self.logger.error(f"Invalid signature: {e}") self.logger.error(f"Invalid signature: {e}")
raise raise
finally:
self.db.close()
async def update_payment_log(self, unique_clinic_id:str, clinic_id:int): async def update_payment_log(self, unique_clinic_id:str, clinic_id:int):
try: try:
@ -228,3 +232,5 @@ class StripeServices:
except Exception as e: except Exception as e:
self.logger.error(f"Error updating payment log: {e}") self.logger.error(f"Error updating payment log: {e}")
raise raise
finally:
self.db.close()

View File

@ -173,6 +173,7 @@ class UserServices:
DBExceptionHandler.handle_exception(e, context="creating user") DBExceptionHandler.handle_exception(e, context="creating user")
finally: finally:
self.db.commit() self.db.commit()
self.db.close()
async def get_user(self, user_id) -> UserResponse: async def get_user(self, user_id) -> UserResponse:
@ -205,88 +206,113 @@ class UserServices:
# Return the response as a dictionary # Return the response as a dictionary
return user_response.model_dump() return user_response.model_dump()
except Exception as e: except Exception as e:
# Use the centralized exception handler
from twillio.exceptions.db_exceptions import DBExceptionHandler
DBExceptionHandler.handle_exception(e, context="getting user") DBExceptionHandler.handle_exception(e, context="getting user")
finally:
self.db.close()
async def get_users(self, limit:int, offset:int, search:str): async def get_users(self, limit:int, offset:int, search:str):
query = self.db.query(Users) try:
if search: query = self.db.query(Users)
query = query.filter( if search:
or_( query = query.filter(
Users.username.contains(search), or_(
Users.email.contains(search), Users.username.contains(search),
Users.email.contains(search),
Users.clinicRole.contains(search), Users.clinicRole.contains(search),
Users.userType.contains(search) Users.userType.contains(search)
) )
) )
users = query.limit(limit).offset(offset).all() users = query.limit(limit).offset(offset).all()
total = self.db.query(Users).count() total = self.db.query(Users).count()
response = CommonResponse(data=[UserResponse(**user.__dict__.copy()) for user in users], total=total) response = CommonResponse(data=[UserResponse(**user.__dict__.copy()) for user in users], total=total)
return response return response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting users")
finally:
self.db.close()
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() try:
user = self.db.query(Users).filter(Users.email == email.lower()).first()
if not user: if not user:
logger.error("User not found") logger.error("User not found")
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
user_dict = user.__dict__.copy() user_dict = user.__dict__.copy()
user_response = UserResponse(**user_dict) user_response = UserResponse(**user_dict)
return user_response return user_response
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting user by email")
finally:
self.db.close()
async def update_user(self, admin_id:int|None, user_id: int, user_data: UserUpdate) -> UserResponse: async def update_user(self, admin_id:int|None, user_id: int, user_data: UserUpdate) -> UserResponse:
# Check admin authorization if admin_id is provided try:
if admin_id: # Check admin authorization if admin_id is provided
admin = self.db.query(Users).filter(Users.id == admin_id).first() if admin_id:
if not admin: admin = self.db.query(Users).filter(Users.id == admin_id).first()
logger.error("Admin not found") if not admin:
raise ResourceNotFoundException("Admin not found") logger.error("Admin not found")
raise ResourceNotFoundException("Admin not found")
# Only check admin type if admin_id was provided # Only check admin type if admin_id was provided
if admin.userType != UserType.SUPER_ADMIN: if admin.userType != UserType.SUPER_ADMIN:
logger.error("User is not authorized to perform this action") logger.error("User is not authorized to perform this action")
raise UnauthorizedException("User is not authorized to perform this action") raise UnauthorizedException("User is not authorized to perform this action")
# Find the user to update # Find the user to update
user = self.db.query(Users).filter(Users.id == user_id).first() user = self.db.query(Users).filter(Users.id == user_id).first()
if not user: if not user:
logger.error("User not found") logger.error("User not found")
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
# Update only the fields that were provided # Update only the fields that were provided
update_data = user_data.model_dump(exclude_unset=True) update_data = user_data.model_dump(exclude_unset=True)
for key, value in update_data.items(): for key, value in update_data.items():
setattr(user, key, value) setattr(user, key, value)
self.db.add(user) self.db.add(user)
self.db.commit() self.db.commit()
self.db.refresh(user) self.db.refresh(user)
# Return properly serialized response # Return properly serialized response
return UserResponse.model_validate(user) return UserResponse.model_validate(user)
except Exception as e:
DBExceptionHandler.handle_exception(e, context="updating user")
finally:
self.db.close()
async def delete_user(self, user_id: int): async def delete_user(self, user_id: int):
user = self.db.query(Users).filter(Users.id == user_id).first() try:
user = self.db.query(Users).filter(Users.id == user_id).first()
if not user: if not user:
logger.error("User not found") logger.error("User not found")
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
# Use the soft_delete method from CustomBase # Use the soft_delete method from CustomBase
user.soft_delete(self.db) user.soft_delete(self.db)
return True return True
except Exception as e:
DBExceptionHandler.handle_exception(e, context="deleting user")
finally:
self.db.close()
async def get_super_admins(self): async def get_super_admins(self):
return self.db.query(Users).filter(Users.userType == UserType.SUPER_ADMIN).all() try:
return self.db.query(Users).filter(Users.userType == UserType.SUPER_ADMIN).all()
except Exception as e:
DBExceptionHandler.handle_exception(e, context="getting super admins")
finally:
self.db.close()
async def _send_emails_to_admins(self, clinic_name): async def _send_emails_to_admins(self, clinic_name):
"""Helper method to send emails to all super admins""" """Helper method to send emails to all super admins"""
@ -300,12 +326,19 @@ class UserServices:
except Exception as e: except Exception as e:
# Log the error but don't interrupt the main flow # Log the error but don't interrupt the main flow
logger.error(f"Error sending admin emails: {str(e)}") logger.error(f"Error sending admin emails: {str(e)}")
finally:
self.db.close()
async def create_payment_link(self, user_id: int): async def create_payment_link(self, user_id: int):
user = self.db.query(Users).filter(Users.id == user_id).first() try:
user = self.db.query(Users).filter(Users.id == user_id).first()
if not user: if not user:
logger.error("User not found") logger.error("User not found")
raise ResourceNotFoundException("User not found") raise ResourceNotFoundException("User not found")
return self.stripe_service.create_payment_link(user_id) return self.stripe_service.create_payment_link(user_id)
except Exception as e:
DBExceptionHandler.handle_exception(e, context="creating payment link")
finally:
self.db.close()