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 fastapi import APIRouter, Depends
|
||||||
from middleware.auth_dependency import auth_required
|
from middleware.auth_dependency import auth_required
|
||||||
|
from middleware.auth_secret import verify_secret
|
||||||
from fastapi.security import HTTPBearer
|
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, 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()
|
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(call_transcripts.router, prefix="/call-transcripts", tags=["call-transcripts"])
|
||||||
|
|
||||||
api_router.include_router(notifications.router, prefix="/notifications", tags=["notifications"], dependencies=[Depends(auth_required)])
|
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")
|
@router.get("/is-valid-domain")
|
||||||
async def is_valid_domain(req:Request):
|
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)
|
is_valid = await ClinicServices().is_valid_domain(host)
|
||||||
return status.HTTP_200_OK if is_valid else status.HTTP_404_NOT_FOUND
|
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:
|
if page < 1:
|
||||||
page = 1
|
page = 1
|
||||||
offset = (page - 1) * limit
|
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")
|
return ApiResponse(data=clinic_doctors, message="Clinic doctors retrieved successfully")
|
||||||
|
|
||||||
@router.post("/")
|
@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
|
# 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
|
# exclude appointmentTypes from clinic_doctor
|
||||||
clinic_doctor_db = ClinicDoctors(
|
clinic_doctor_db = ClinicDoctors(
|
||||||
|
|
@ -129,7 +131,6 @@ class ClinicDoctorsServices:
|
||||||
if clinic_doctor is None:
|
if clinic_doctor is None:
|
||||||
raise ResourceNotFoundException("Clinic doctor not found")
|
raise ResourceNotFoundException("Clinic doctor not found")
|
||||||
|
|
||||||
|
|
||||||
# Update the existing object with new values
|
# Update the existing object with new values
|
||||||
update_data = clinic_doctor_data.model_dump(exclude_unset=True)
|
update_data = clinic_doctor_data.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
|
@ -198,14 +199,42 @@ class ClinicDoctorsServices:
|
||||||
finally:
|
finally:
|
||||||
self.db.close()
|
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:
|
try:
|
||||||
clinic_doctors_query = (
|
clinic_doctors_query = (
|
||||||
self.db.query(ClinicDoctors)
|
self.db.query(ClinicDoctors)
|
||||||
.filter(ClinicDoctors.clinic_id == user["created_clinics"][0]["id"])
|
.filter(ClinicDoctors.clinic_id == clinic_id)
|
||||||
.options(
|
.options(
|
||||||
selectinload(ClinicDoctors.appointmentRelations)
|
selectinload(ClinicDoctors.appointmentRelations).selectinload(
|
||||||
.selectinload(AppointmentRelations.masterAppointmentTypes)
|
AppointmentRelations.masterAppointmentTypes
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.order_by(
|
.order_by(
|
||||||
getattr(ClinicDoctors, sort_by).desc()
|
getattr(ClinicDoctors, sort_by).desc()
|
||||||
|
|
@ -225,12 +254,15 @@ class ClinicDoctorsServices:
|
||||||
AppointmentRelations.masterAppointmentTypes.has(
|
AppointmentRelations.masterAppointmentTypes.has(
|
||||||
MasterAppointmentTypes.type.ilike(f"%{search}%")
|
MasterAppointmentTypes.type.ilike(f"%{search}%")
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
total = clinic_doctors_query.count()
|
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
|
# Build response data manually to include appointment types
|
||||||
response_data = []
|
response_data = []
|
||||||
|
|
@ -244,7 +276,7 @@ class ClinicDoctorsServices:
|
||||||
id=relation.masterAppointmentTypes.id,
|
id=relation.masterAppointmentTypes.id,
|
||||||
type=relation.masterAppointmentTypes.type,
|
type=relation.masterAppointmentTypes.type,
|
||||||
create_time=relation.masterAppointmentTypes.create_time,
|
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,
|
status=clinic_doctor.status,
|
||||||
create_time=clinic_doctor.create_time,
|
create_time=clinic_doctor.create_time,
|
||||||
update_time=clinic_doctor.update_time,
|
update_time=clinic_doctor.update_time,
|
||||||
appointmentTypes=appointment_types
|
appointmentTypes=appointment_types,
|
||||||
)
|
)
|
||||||
response_data.append(clinic_doctor_data)
|
response_data.append(clinic_doctor_data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,23 @@ class ClinicServices:
|
||||||
self.db.close()
|
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):
|
async def get_clinic_files(self, clinic_id: int):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue