feat: full clinic signup
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user