feat: full clinic signup

This commit is contained in:
2025-05-12 16:18:25 +05:30
parent 80c61dc127
commit 25e105e714
15 changed files with 473 additions and 44 deletions
+3 -4
View File
@@ -26,7 +26,6 @@ class AuthService:
token = create_jwt_token(user_dict)
return token
async def register(self, user_data: UserCreate) -> str:
user = await self.user_service.create_user(user_data)
token = create_jwt_token(user)
return token
async def register(self, user_data: UserCreate) -> None:
await self.user_service.create_user(user_data)
return
+149
View File
@@ -0,0 +1,149 @@
from enum import Enum
from typing import Optional, Dict, Any
import os
from datetime import datetime
from urllib.parse import urlparse
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from fastapi import HTTPException
from pydantic_settings import BaseSettings
from enums.enums import S3FolderNameEnum
from exceptions.business_exception import BusinessValidationException
class Settings(BaseSettings):
AWS_REGION: str
AWS_ACCESS_KEY: str
AWS_SECRET_KEY: str
AWS_BUCKET_NAME: str
AWS_S3_EXPIRES: int = 60 * 60 # Default 1 hour
class Config:
env_file = ".env"
extra = "ignore" # Allow extra fields from environment
class S3Service:
def __init__(self):
self.settings = Settings()
self.bucket_name = self.settings.AWS_BUCKET_NAME
self.s3 = boto3.client(
's3',
region_name=self.settings.AWS_REGION,
aws_access_key_id=self.settings.AWS_ACCESS_KEY,
aws_secret_access_key=self.settings.AWS_SECRET_KEY,
config=Config(signature_version='s3v4')
)
def get_s3_service():
return S3Service()
async def upload_file(
user_id: str,
folder: S3FolderNameEnum,
file_name: str,
clinic_id: Optional[str] = None
) -> Dict[str, str]:
"""
Generate a pre-signed URL for uploading a file to S3.
Args:
user_id: The ID of the user
folder: The folder enum to store the file in
file_name: The name of the file
clinic_id: Optional design ID for assets
Returns:
Dict containing the URLs and key information
"""
s3_service = get_s3_service()
if folder == S3FolderNameEnum.ASSETS and not clinic_id:
raise BusinessValidationException("Clinic id is required!")
if folder != S3FolderNameEnum.PROFILE and not user_id:
raise BusinessValidationException("User id is required!")
timestamp = int(datetime.now().timestamp() * 1000)
if folder == S3FolderNameEnum.PROFILE:
key = f"common/{S3FolderNameEnum.PROFILE.value}/{user_id}/{timestamp}_{file_name}"
else:
key = f"common/{S3FolderNameEnum.ASSETS.value}/clinic/{clinic_id}/{timestamp}_{file_name}"
try:
put_url = s3_service.s3.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': s3_service.bucket_name,
'Key': key,
},
ExpiresIn=s3_service.settings.AWS_S3_EXPIRES
)
get_url = s3_service.s3.generate_presigned_url(
ClientMethod='get_object',
Params={
'Bucket': s3_service.bucket_name,
'Key': key,
},
ExpiresIn=s3_service.settings.AWS_S3_EXPIRES
)
url = urlparse(put_url)
return {
"api_url": put_url,
"key": key,
"location": f"{url.scheme}://{url.netloc}/{key}",
"get_url": get_url,
}
except ClientError as e:
print(f"Error generating pre-signed URL: {e}")
raise BusinessValidationException(str(e))
async def get_signed_url(key: str) -> str:
"""
Generate a pre-signed URL for retrieving a file from S3.
Args:
key: The key of the file in S3
Returns:
The pre-signed URL for getting the object
"""
s3_service = get_s3_service()
try:
url = s3_service.s3.generate_presigned_url(
ClientMethod='get_object',
Params={
'Bucket': s3_service.bucket_name,
'Key': key,
},
ExpiresIn=3600 # 1 hour
)
return url
except ClientError as e:
print(f"Error in get_signed_url: {e}")
raise BusinessValidationException(str(e))
def get_file_key(url: str) -> str:
"""
Extract the file key from a URL or return the key if already provided.
Args:
url: The URL or key
Returns:
The file key
"""
try:
if not url.startswith("http://") and not url.startswith("https://"):
return url
parsed_url = urlparse(url)
return parsed_url.path.lstrip('/')
except Exception as e:
print(f"Error in get_file_key: {e}")
raise BusinessValidationException(str(e))
+63 -18
View File
@@ -1,12 +1,12 @@
from loguru import logger
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy import or_
from database import get_db
from models.Users import Users
from exceptions.validation_exception import ValidationException
from schemas.ResponseSchemas import UserResponse
from models import Clinics
from enums.enums import ClinicStatus
from utils.password_utils import hash_password
from schemas.CreateSchemas import UserCreate
from exceptions.resource_not_found_exception import ResourceNotFoundException
@@ -17,11 +17,13 @@ class UserServices:
self.db: Session = next(get_db())
async def create_user(self, user_data: UserCreate):
# Start a transaction
try:
user = user_data.user
# Check if user with same username or email exists
existing_user = (
self.db.query(Users)
.filter(Users.email == user_data.email.lower())
.filter(Users.email == user.email.lower())
.first()
)
@@ -32,29 +34,72 @@ class UserServices:
# Create a new user instance
new_user = Users(
username=user_data.username,
email=user_data.email.lower(),
password=hash_password(user_data.password),
clinicRole=user_data.clinicRole,
userType=user_data.userType,
username=user.username,
email=user.email.lower(),
password=hash_password(user.password),
clinicRole=user.clinicRole,
userType=user.userType,
)
# Add to database and commit
# Add user to database but don't commit yet
self.db.add(new_user)
# Get clinic data
clinic = user_data.clinic
# cross verify domain, in db
# Convert to lowercase and keep only alphabetic characters, hyphens, and underscores
domain = ''.join(char for char in clinic.name.lower() if char.isalpha() or char == '-' or char == '_')
existing_clinic = self.db.query(Clinics).filter(Clinics.domain == domain).first()
if existing_clinic:
# This will trigger rollback in the exception handler
raise ValidationException("Clinic with same domain already exists")
# Create clinic instance
new_clinic = Clinics(
name=clinic.name,
address=clinic.address,
phone=clinic.phone,
email=clinic.email,
integration=clinic.integration,
pms_id=clinic.pms_id,
practice_name=clinic.practice_name,
logo=clinic.logo,
country=clinic.country,
postal_code=clinic.postal_code,
city=clinic.city,
state=clinic.state,
abn_doc=clinic.abn_doc,
abn_number=clinic.abn_number,
contract_doc=clinic.contract_doc,
clinic_phone=clinic.clinic_phone,
is_clinic_phone_enabled=clinic.is_clinic_phone_enabled,
other_info=clinic.other_info,
greeting_msg=clinic.greeting_msg,
voice_model=clinic.voice_model,
voice_model_provider=clinic.voice_model_provider,
voice_model_gender=clinic.voice_model_gender,
scenarios=clinic.scenarios,
general_info=clinic.general_info,
status=ClinicStatus.UNDER_REVIEW,
domain=domain,
)
# Add clinic to database
self.db.add(new_clinic)
# Now commit both user and clinic in a single transaction
self.db.commit()
self.db.refresh(new_user)
user_dict = new_user.__dict__.copy()
user_response = UserResponse(**user_dict).model_dump()
return user_response
return
except Exception as e:
logger.error("Error creating user", e)
logger.error(f"Error creating user: {str(e)}")
# Rollback the transaction if any error occurs
self.db.rollback()
if e.__class__ == ValidationException:
if isinstance(e, ValidationException):
raise ValidationException(e.message)
if e.__class__ == ResourceNotFoundException:
if isinstance(e, ResourceNotFoundException):
raise ResourceNotFoundException(e.message)
raise e