feat: new endpoints for agent
feat: new auth middleware for agent
This commit is contained in:
parent
db20c498c2
commit
6ce3e4acce
|
|
@ -1,11 +1,12 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from middleware.auth_dependency import auth_required
|
||||
from middleware.auth_secret import verify_secret
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
# Import the security scheme
|
||||
bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication")
|
||||
|
||||
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts, notifications,sns, stripe
|
||||
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts, notifications,sns, stripe, agent
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
|
|
@ -42,3 +43,7 @@ api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboar
|
|||
api_router.include_router(call_transcripts.router, prefix="/call-transcripts", tags=["call-transcripts"])
|
||||
|
||||
api_router.include_router(notifications.router, prefix="/notifications", tags=["notifications"], dependencies=[Depends(auth_required)])
|
||||
|
||||
|
||||
# agent (bot) routes
|
||||
api_router.include_router(agent.router, prefix="/agent", tags=["agent"], dependencies=[Depends(verify_secret)], include_in_schema=False)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
'''
|
||||
this route is for agent (bot)
|
||||
'''
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from services.agentServices import AgentServices
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/clinic/docs/{clinic_id}")
|
||||
async def get_clinic_doctors_with_appointments(clinic_id: int):
|
||||
return await AgentServices().get_clinic_doctors_with_appointments(clinic_id)
|
||||
|
||||
@router.get("/clinic/{phone}")
|
||||
async def get_clinic_by_phone(phone: str):
|
||||
return await AgentServices().get_clinic_by_phone(phone)
|
||||
|
||||
|
|
@ -62,6 +62,7 @@ async def verify_otp(data: AuthOTP):
|
|||
|
||||
@router.get("/is-valid-domain")
|
||||
async def is_valid_domain(req:Request):
|
||||
host = req.headers.get("host")
|
||||
host = req.client.host
|
||||
print(host)
|
||||
is_valid = await ClinicServices().is_valid_domain(host)
|
||||
return status.HTTP_200_OK if is_valid else status.HTTP_404_NOT_FOUND
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ async def get_clinic_doctors(
|
|||
if page < 1:
|
||||
page = 1
|
||||
offset = (page - 1) * limit
|
||||
clinic_doctors = await ClinicDoctorsServices().get_clinic_doctors(req.state.user, limit, offset, search, sort_by, sort_order)
|
||||
user = req.state.user
|
||||
clinic_id = user["created_clinics"][0]["id"]
|
||||
clinic_doctors = await ClinicDoctorsServices().get_clinic_doctors(clinic_id, limit, offset, search, sort_by, sort_order)
|
||||
return ApiResponse(data=clinic_doctors, message="Clinic doctors retrieved successfully")
|
||||
|
||||
@router.post("/")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
"""
|
||||
Authentication middleware and dependency for agent (bot) requests.
|
||||
Validates the presence and correctness of the X-Agent-Secret header.
|
||||
"""
|
||||
import os
|
||||
from fastapi import HTTPException, status, Header
|
||||
from typing import Optional
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Get the secret key from environment variables
|
||||
AGENT_SECRET_KEY = os.getenv("AGENT_SECRET_KEY")
|
||||
if not AGENT_SECRET_KEY:
|
||||
raise ValueError("AGENT_SECRET_KEY environment variable not set")
|
||||
|
||||
async def verify_secret(x_agent_secret: Optional[str] = Header(None, alias="X-Agent-Secret")):
|
||||
"""
|
||||
Dependency function to verify the X-Agent-Secret header.
|
||||
Can be used with Depends() in FastAPI route dependencies.
|
||||
"""
|
||||
if not x_agent_secret or x_agent_secret != AGENT_SECRET_KEY:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or missing X-Agent-Secret header",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return True
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from services.clinicServices import ClinicServices
|
||||
from services.clinicDoctorsServices import ClinicDoctorsServices
|
||||
|
||||
|
||||
class AgentServices:
|
||||
def __init__(self):
|
||||
self.clinicServices = ClinicServices()
|
||||
self.clinicDoctorService = ClinicDoctorsServices()
|
||||
|
||||
async def get_clinic_by_phone(self, phone: str):
|
||||
return await self.clinicServices.get_clinic_by_phone(phone)
|
||||
|
||||
|
||||
async def get_clinic_doctors_with_appointments(self, clinic_id: int):
|
||||
return await self.clinicDoctorService.get_clinic_doctors(clinic_id)
|
||||
|
|
@ -53,7 +53,9 @@ class ClinicDoctorsServices:
|
|||
)
|
||||
|
||||
# check if clinic exists
|
||||
await self.clinic_services.get_clinic_by_id(user["created_clinics"][0]["id"])
|
||||
await self.clinic_services.get_clinic_by_id(
|
||||
user["created_clinics"][0]["id"]
|
||||
)
|
||||
|
||||
# exclude appointmentTypes from clinic_doctor
|
||||
clinic_doctor_db = ClinicDoctors(
|
||||
|
|
@ -129,7 +131,6 @@ class ClinicDoctorsServices:
|
|||
if clinic_doctor is None:
|
||||
raise ResourceNotFoundException("Clinic doctor not found")
|
||||
|
||||
|
||||
# Update the existing object with new values
|
||||
update_data = clinic_doctor_data.model_dump(exclude_unset=True)
|
||||
|
||||
|
|
@ -173,7 +174,7 @@ class ClinicDoctorsServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
async def get_doctor_status_count(self, clinic_id:int):
|
||||
async def get_doctor_status_count(self, clinic_id: int):
|
||||
try:
|
||||
# Query to count doctors by status
|
||||
status_counts = (
|
||||
|
|
@ -198,14 +199,42 @@ class ClinicDoctorsServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
async def get_clinic_doctors(self,user, limit: int, offset: int, search: str = "", sort_by: str = DEFAULT_ORDER, sort_order: str = DEFAULT_ORDER_BY):
|
||||
async def get_clinic_doctors(
|
||||
self,
|
||||
clinic_id: int,
|
||||
limit: int | None = None,
|
||||
offset: int | None = None,
|
||||
search: str = "",
|
||||
sort_by: str = DEFAULT_ORDER,
|
||||
sort_order: str = DEFAULT_ORDER_BY,
|
||||
):
|
||||
try:
|
||||
response = await self._get_clinic_doctors(
|
||||
clinic_id, limit, offset, search, sort_by, sort_order
|
||||
)
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
self.logger.error(e)
|
||||
raise e
|
||||
|
||||
async def _get_clinic_doctors(
|
||||
self,
|
||||
clinic_id: int,
|
||||
limit: int | None = None,
|
||||
offset: int | None = None,
|
||||
search: str = "",
|
||||
sort_by: str = DEFAULT_ORDER,
|
||||
sort_order: str = DEFAULT_ORDER_BY,
|
||||
):
|
||||
try:
|
||||
clinic_doctors_query = (
|
||||
self.db.query(ClinicDoctors)
|
||||
.filter(ClinicDoctors.clinic_id == user["created_clinics"][0]["id"])
|
||||
.filter(ClinicDoctors.clinic_id == clinic_id)
|
||||
.options(
|
||||
selectinload(ClinicDoctors.appointmentRelations)
|
||||
.selectinload(AppointmentRelations.masterAppointmentTypes)
|
||||
selectinload(ClinicDoctors.appointmentRelations).selectinload(
|
||||
AppointmentRelations.masterAppointmentTypes
|
||||
)
|
||||
)
|
||||
.order_by(
|
||||
getattr(ClinicDoctors, sort_by).desc()
|
||||
|
|
@ -224,13 +253,16 @@ class ClinicDoctorsServices:
|
|||
ClinicDoctors.appointmentRelations.any(
|
||||
AppointmentRelations.masterAppointmentTypes.has(
|
||||
MasterAppointmentTypes.type.ilike(f"%{search}%")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
total = clinic_doctors_query.count()
|
||||
|
||||
clinic_doctors = clinic_doctors_query.limit(limit).offset(offset).all()
|
||||
if limit and offset:
|
||||
clinic_doctors_query = clinic_doctors_query.limit(limit).offset(offset)
|
||||
|
||||
clinic_doctors = clinic_doctors_query.all()
|
||||
|
||||
# Build response data manually to include appointment types
|
||||
response_data = []
|
||||
|
|
@ -244,7 +276,7 @@ class ClinicDoctorsServices:
|
|||
id=relation.masterAppointmentTypes.id,
|
||||
type=relation.masterAppointmentTypes.type,
|
||||
create_time=relation.masterAppointmentTypes.create_time,
|
||||
update_time=relation.masterAppointmentTypes.update_time
|
||||
update_time=relation.masterAppointmentTypes.update_time,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -256,7 +288,7 @@ class ClinicDoctorsServices:
|
|||
status=clinic_doctor.status,
|
||||
create_time=clinic_doctor.create_time,
|
||||
update_time=clinic_doctor.update_time,
|
||||
appointmentTypes=appointment_types
|
||||
appointmentTypes=appointment_types,
|
||||
)
|
||||
response_data.append(clinic_doctor_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,23 @@ class ClinicServices:
|
|||
self.db.close()
|
||||
|
||||
|
||||
async def get_clinic_by_phone(self, phone: str):
|
||||
try:
|
||||
clinic = self.db.query(Clinics).filter(Clinics.phone == phone).first()
|
||||
|
||||
if clinic is None:
|
||||
raise ResourceNotFoundException("Clinic not found")
|
||||
|
||||
clinic_response = Clinic.model_validate(clinic)
|
||||
|
||||
return clinic_response.model_dump(
|
||||
exclude={"creator", "abn_doc", "contract_doc", "logo"}
|
||||
)
|
||||
except Exception as e:
|
||||
DBExceptionHandler.handle_exception(e, context="getting clinic by phone")
|
||||
finally:
|
||||
self.db.close()
|
||||
|
||||
|
||||
async def get_clinic_files(self, clinic_id: int):
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue