feat: endpoint for caddy
This commit is contained in:
parent
165385358f
commit
db20c498c2
|
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, Depends, Security
|
||||
from fastapi import APIRouter, Depends
|
||||
from middleware.auth_dependency import auth_required
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ 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
|
||||
|
||||
api_router = APIRouter()
|
||||
# api_router.include_router(twilio.router, prefix="/twilio")
|
||||
|
||||
api_router.include_router(clinics.router, prefix="/clinics", tags=["clinics"], dependencies=[Depends(auth_required)])
|
||||
|
||||
api_router.include_router(doctors.router, prefix="/doctors", tags=["doctors"])
|
||||
|
|
@ -24,9 +24,9 @@ api_router.include_router(sns.router, prefix="/sns", tags=["sns"], include_in_sc
|
|||
api_router.include_router(stripe.router, prefix="/stripe", tags=["stripe"])
|
||||
|
||||
api_router.include_router(
|
||||
admin.router,
|
||||
prefix="/admin",
|
||||
dependencies=[Depends(auth_required)],
|
||||
admin.router,
|
||||
prefix="/admin",
|
||||
dependencies=[Depends(auth_required)],
|
||||
tags=["admin"])
|
||||
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from fastapi import APIRouter, BackgroundTasks
|
||||
from fastapi import APIRouter, BackgroundTasks, Request, status
|
||||
from services.authService import AuthService
|
||||
from schemas.CreateSchemas import UserCreate
|
||||
from schemas.ApiResponse import ApiResponse
|
||||
|
|
@ -58,4 +58,10 @@ async def verify_otp(data: AuthOTP):
|
|||
return ApiResponse(
|
||||
data="OK",
|
||||
message="OTP verified successfully"
|
||||
)
|
||||
)
|
||||
|
||||
@router.get("/is-valid-domain")
|
||||
async def is_valid_domain(req:Request):
|
||||
host = req.headers.get("host")
|
||||
is_valid = await ClinicServices().is_valid_domain(host)
|
||||
return status.HTTP_200_OK if is_valid else status.HTTP_404_NOT_FOUND
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ stripe_service = StripeServices()
|
|||
# async def create_checkout_session(user_id: int):
|
||||
# return await stripe_service.create_checkout_session(1)
|
||||
|
||||
# @router.post("/create-subscription-checkout")
|
||||
# async def create_subscription_checkout():
|
||||
# return await stripe_service.create_subscription_checkout(
|
||||
# fees_to_be={
|
||||
# "per_call_charges": 10,
|
||||
# "setup_fees": 100,
|
||||
# "subscription_fees": 100,
|
||||
# "total": 210
|
||||
# },
|
||||
# clinic_id=1,
|
||||
# account_id="acct_1RT1UFPTNqn2kWQ8",
|
||||
# customer_id="cus_SNn49FDltUcSLP"
|
||||
# )
|
||||
@router.post("/create-subscription-checkout")
|
||||
async def create_subscription_checkout():
|
||||
return await stripe_service.create_subscription_checkout(
|
||||
fees_to_be={
|
||||
"per_call_charges": 10,
|
||||
"setup_fees": 100,
|
||||
"subscription_fees": 100,
|
||||
"total": 210
|
||||
},
|
||||
clinic_id=1,
|
||||
account_id="acct_1RT1UFPTNqn2kWQ8",
|
||||
customer_id="cus_SNn49FDltUcSLP"
|
||||
)
|
||||
|
||||
@router.get("/create-stripe-account-link", dependencies=[Depends(auth_required)])
|
||||
async def create_stripe_account_link(req:Request):
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
# import asyncio
|
||||
# import json
|
||||
# import logging
|
||||
# import os
|
||||
# from fastapi import APIRouter, Request, WebSocket, status
|
||||
# from twilio.twiml.voice_response import VoiceResponse, Connect
|
||||
# from twilio.rest import Client
|
||||
# from fastapi import WebSocket, Request, Response
|
||||
# from enum import Enum
|
||||
# from typing import Optional
|
||||
|
||||
# from services.bot import run_bot
|
||||
# from services.call_state import CallState
|
||||
|
||||
# logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# router = APIRouter()
|
||||
|
||||
# BASE_WS_URL = "wss://13.229.247.61:8000/api/twilio"
|
||||
# BASE_URL = "http://13.229.247.61:8000/api/twilio"
|
||||
|
||||
# DTMF_SWITCH_KEY = "*" # Key to switch between models
|
||||
|
||||
# LOG_EVENT_TYPES = [
|
||||
# "error",
|
||||
# "response.content.done",
|
||||
# "rate_limits.updated",
|
||||
# "response.done",
|
||||
# "input_audio_buffer.committed",
|
||||
# "input_audio_buffer.speech_stopped",
|
||||
# "input_audio_buffer.speech_started",
|
||||
# "session.created",
|
||||
# ]
|
||||
|
||||
# SHOW_TIMING_MATH = False
|
||||
|
||||
# MENU_OPTIONS = """
|
||||
# Press 1 for Model 1.
|
||||
# Press 2 for Model 2.
|
||||
# Press 3 for Model 3.
|
||||
# Press 4 for Model 4.
|
||||
# Press 5 for Model 5.
|
||||
# Press 0 to repeat the options.
|
||||
# """
|
||||
|
||||
# call_state = CallState
|
||||
|
||||
|
||||
# # Define model options as enum for type safety
|
||||
# class ModelOption(int, Enum):
|
||||
# MODEL_1 = 1
|
||||
# MODEL_2 = 2
|
||||
# MODEL_3 = 3
|
||||
# MODEL_4 = 4
|
||||
# MODEL_5 = 5
|
||||
|
||||
|
||||
# @router.post("/call")
|
||||
# async def get_call():
|
||||
|
||||
# SID = os.getenv("TWILIO_SID")
|
||||
# AUTH_TOKEN = os.getenv("TWILIO_AUTH")
|
||||
|
||||
# client = Client(SID, AUTH_TOKEN)
|
||||
|
||||
# call = client.calls.create(
|
||||
# from_="+14149466486",
|
||||
# to="+917984372159",
|
||||
# record=True,
|
||||
# url=f"{BASE_URL}/receive",
|
||||
# )
|
||||
|
||||
# return status.HTTP_200_OK
|
||||
|
||||
|
||||
# # @router.websocket("/media-stream/{id}")
|
||||
# # async def handle_media_stream(websocket: WebSocket, id: int):
|
||||
# # """Handle WebSocket connections between Twilio and OpenAI."""
|
||||
# # print(f"Client connected with id: {id}")
|
||||
# # await websocket.accept()
|
||||
# # start_data = websocket.iter_text()
|
||||
# # await start_data.__anext__()
|
||||
# # call_data = json.loads(await start_data.__anext__())
|
||||
# # print(call_data, flush=True)
|
||||
# # stream_sid = call_data["start"]["streamSid"]
|
||||
# # print("WebSocket connection accepted")
|
||||
# # await run_bot(websocket, stream_sid, False, option=id)
|
||||
|
||||
|
||||
# @router.websocket("/media-stream/{id}")
|
||||
# async def handle_media_stream(websocket: WebSocket, id: int):
|
||||
# """Handle WebSocket connections between Twilio and OpenAI."""
|
||||
# logger.info(f"Client connected with id: {id}")
|
||||
# print(f"Client connected with id: {id}")
|
||||
# await websocket.accept()
|
||||
# start_data = websocket.iter_text()
|
||||
# await start_data.__anext__()
|
||||
# call_data = json.loads(await start_data.__anext__())
|
||||
# print(call_data, flush=True)
|
||||
# logger.info(call_data)
|
||||
# stream_sid = call_data["start"]["streamSid"]
|
||||
# print("WebSocket connection accepted")
|
||||
# logger.info("WebSocket connection accepted")
|
||||
# await run_bot(websocket, stream_sid, False, option=id)
|
||||
|
||||
|
||||
# # @router.post("/receive")
|
||||
# # async def receive_call(req: Request):
|
||||
# # print("----------------- received call -----------------")
|
||||
|
||||
# # resp = VoiceResponse()
|
||||
# # connect = Connect()
|
||||
# # connect.stream(
|
||||
# # url=f"wss://allegedly-known-wasp.ngrok-free.app/api/twilio/media-stream"
|
||||
# # )
|
||||
# # resp.append(connect)
|
||||
# # return Response(content=str(resp), media_type="application/xml")
|
||||
|
||||
|
||||
# @router.post("/receive")
|
||||
# async def receive_call(req: Request):
|
||||
# print("----------------- received call -----------------")
|
||||
# resp = VoiceResponse()
|
||||
|
||||
# # Gather DTMF input
|
||||
# gather = resp.gather(
|
||||
# num_digits=1, action="/api/twilio/handle-key", method="POST", timeout=10
|
||||
# )
|
||||
|
||||
# # Initial greeting and menu options
|
||||
# gather.say(MENU_OPTIONS)
|
||||
|
||||
# # If no input received, redirect back to the main menu
|
||||
# resp.redirect("/api/twilio/receive")
|
||||
|
||||
# return Response(content=str(resp), media_type="application/xml")
|
||||
|
||||
|
||||
# @router.post("/handle-key")
|
||||
# async def handle_key_press(req: Request):
|
||||
# """Process the key pressed by the caller and connect to the appropriate model."""
|
||||
# try:
|
||||
# form_data = await req.form()
|
||||
# digits_pressed = form_data.get("Digits", "")
|
||||
# print(f"User pressed: {digits_pressed}")
|
||||
|
||||
# resp = VoiceResponse()
|
||||
|
||||
# if digits_pressed == "0":
|
||||
# # Repeat options
|
||||
# resp.redirect("/api/twilio/receive")
|
||||
# elif digits_pressed in "12345":
|
||||
# # Valid model selection
|
||||
# model_id = int(digits_pressed)
|
||||
# resp.say(f"You have selected model {model_id}.")
|
||||
|
||||
# # Connect to WebSocket
|
||||
# connect = Connect()
|
||||
# connect.stream(url=f"{BASE_WS_URL}/media-stream/{model_id}")
|
||||
# resp.append(connect)
|
||||
# else:
|
||||
# # Invalid selection
|
||||
# resp.say("Invalid selection. Please try again.")
|
||||
# resp.redirect("/api/twilio/receive")
|
||||
|
||||
# return Response(content=str(resp), media_type="application/xml")
|
||||
|
||||
# except Exception as e:
|
||||
# print(f"Error handling key press: {str(e)}")
|
||||
# resp = VoiceResponse()
|
||||
# resp.say(
|
||||
# "We encountered a problem processing your request. Please try again later."
|
||||
# )
|
||||
# return Response(content=str(resp), media_type="application/xml")
|
||||
|
||||
|
||||
# @router.post("/error")
|
||||
# async def read_item(req: Request):
|
||||
# print(await req.body())
|
||||
# print(await req.form())
|
||||
# logger.error(await req.body())
|
||||
# return status.HTTP_200_OK
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
from database import get_db
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from schemas.UpdateSchemas import ClinicStatusUpdate, ClinicUpdate
|
||||
from schemas.UpdateSchemas import ClinicUpdate
|
||||
from schemas.ResponseSchemas import Clinic, ClinicOfferResponse
|
||||
from typing import List, Literal, Optional, Union
|
||||
from typing import Literal, Optional, Union
|
||||
from exceptions import ResourceNotFoundException, ValidationException
|
||||
from enums.enums import ClinicStatus, UserType
|
||||
from exceptions.unauthorized_exception import UnauthorizedException
|
||||
from interface.common_response import CommonResponse
|
||||
from interface.common_response import CommonResponse
|
||||
from sqlalchemy import or_,not_
|
||||
from sqlalchemy import text
|
||||
|
||||
|
|
@ -18,6 +18,7 @@ from loguru import logger
|
|||
from sqlalchemy import func
|
||||
|
||||
from exceptions.db_exceptions import DBExceptionHandler
|
||||
|
||||
class ClinicServices:
|
||||
def __init__(self):
|
||||
self.db: Session = next(get_db())
|
||||
|
|
@ -48,17 +49,17 @@ class ClinicServices:
|
|||
)
|
||||
|
||||
clinics = clinics_query.offset(offset).limit(limit).all()
|
||||
|
||||
|
||||
count_query = text("""
|
||||
SELECT
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'ACTIVE' OR status = 'INACTIVE' OR status ='SUBSCRIPTION_ENDED' THEN 1 END) as active,
|
||||
COUNT(CASE WHEN status != 'ACTIVE' AND status != 'INACTIVE' AND status != 'SUBSCRIPTION_ENDED' THEN 1 END) as rejected
|
||||
FROM clinics
|
||||
""")
|
||||
|
||||
|
||||
result = self.db.execute(count_query).first()
|
||||
|
||||
|
||||
totalClinics = result.total or 0
|
||||
totalRegisteredClinics = result.active or 0
|
||||
totalRejectedClinics = result.rejected or 0
|
||||
|
|
@ -100,7 +101,7 @@ class ClinicServices:
|
|||
raise ResourceNotFoundException("Clinic not found")
|
||||
|
||||
clinic_response = Clinic(**clinic.__dict__.copy())
|
||||
|
||||
|
||||
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.contract_doc = await get_signed_url(clinic_response.contract_doc) if clinic_response.contract_doc else None
|
||||
|
|
@ -127,7 +128,7 @@ class ClinicServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
|
||||
|
||||
|
||||
async def get_clinic_files(self, clinic_id: int):
|
||||
try:
|
||||
|
|
@ -162,7 +163,7 @@ class ClinicServices:
|
|||
clinic_data.logo = get_file_key(clinic_data.logo)
|
||||
|
||||
update_data = clinic_data.model_dump(exclude_unset=True)
|
||||
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(clinic, key, value)
|
||||
|
||||
|
|
@ -171,10 +172,10 @@ class ClinicServices:
|
|||
self.db.refresh(clinic)
|
||||
|
||||
clinic_response = Clinic(**clinic.__dict__.copy())
|
||||
|
||||
|
||||
# update clinic files
|
||||
clinic_files = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
|
||||
|
||||
|
||||
if clinic_data.abn_doc:
|
||||
clinic_files.abn_doc_is_verified = None
|
||||
if clinic_data.contract_doc:
|
||||
|
|
@ -207,10 +208,10 @@ class ClinicServices:
|
|||
try:
|
||||
# Get total count
|
||||
totalClinics = self.db.query(Clinics).count()
|
||||
|
||||
|
||||
# Get counts for specific statuses in a single query
|
||||
status_counts = self.db.query(
|
||||
Clinics.status,
|
||||
Clinics.status,
|
||||
func.count(Clinics.id).label('count')
|
||||
).filter(
|
||||
Clinics.status.in_([
|
||||
|
|
@ -222,7 +223,7 @@ class ClinicServices:
|
|||
ClinicStatus.SUBSCRIPTION_ENDED
|
||||
])
|
||||
).group_by(Clinics.status).all()
|
||||
|
||||
|
||||
# Initialize counts with 0
|
||||
counts = {
|
||||
"totalClinics": totalClinics,
|
||||
|
|
@ -231,7 +232,7 @@ class ClinicServices:
|
|||
"totalRejectedClinics": 0,
|
||||
"totalPaymentDueClinics": 0,
|
||||
}
|
||||
|
||||
|
||||
# Map status values to their respective count keys
|
||||
status_to_key = {
|
||||
ClinicStatus.ACTIVE: "totalActiveClinics",
|
||||
|
|
@ -239,7 +240,7 @@ class ClinicServices:
|
|||
ClinicStatus.PAYMENT_DUE: "totalPaymentDueClinics",
|
||||
ClinicStatus.REJECTED: "totalRejectedClinics"
|
||||
}
|
||||
|
||||
|
||||
# Update counts with actual values from the query
|
||||
for status, count in status_counts:
|
||||
key = status_to_key.get(status)
|
||||
|
|
@ -247,14 +248,14 @@ class ClinicServices:
|
|||
counts[key] += count
|
||||
else:
|
||||
counts["totalActiveClinics"] += count
|
||||
|
||||
|
||||
return counts
|
||||
except Exception as e:
|
||||
DBExceptionHandler.handle_exception(e, context="getting clinic count")
|
||||
finally:
|
||||
self.db.close()
|
||||
|
||||
|
||||
|
||||
|
||||
async def update_clinic_status(self, user, clinic_id: int, status: ClinicStatus, documentStatus: Optional[dict] = None, rejection_reason: Optional[str] = None):
|
||||
try:
|
||||
if user["userType"] != UserType.SUPER_ADMIN:
|
||||
|
|
@ -324,7 +325,7 @@ class ClinicServices:
|
|||
# self.db.commit()
|
||||
pass
|
||||
|
||||
return
|
||||
return
|
||||
except Exception as e:
|
||||
DBExceptionHandler.handle_exception(e, context="updating clinic status")
|
||||
finally:
|
||||
|
|
@ -338,7 +339,7 @@ class ClinicServices:
|
|||
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 = ""):
|
||||
try:
|
||||
if user["userType"] != UserType.SUPER_ADMIN:
|
||||
|
|
@ -423,4 +424,19 @@ class ClinicServices:
|
|||
except Exception as e:
|
||||
DBExceptionHandler.handle_exception(e, context="deleting clinic offer")
|
||||
finally:
|
||||
self.db.close()
|
||||
self.db.close()
|
||||
|
||||
async def is_valid_domain(self, domain:str):
|
||||
|
||||
subdomain = domain.split(".")[0]
|
||||
|
||||
# allow main domain, backend and admin domains
|
||||
if(subdomain in ["toBeDomain", "backend", "admin"]):
|
||||
return True
|
||||
|
||||
|
||||
# check if any clinic has domain
|
||||
clinic_domain = self.db.query(Clinic).filter(Clinic.domain == subdomain).first()
|
||||
if clinic_domain:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ class StripeServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
# NOTE: in case when checkout session expired or not created
|
||||
async def create_payment_session(self, user):
|
||||
try:
|
||||
if user["userType"] != UserType.CLINIC_ADMIN:
|
||||
|
|
@ -252,46 +253,34 @@ class StripeServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
async def create_checkout_session(self, user_id: int):
|
||||
try:
|
||||
checkout_session = await stripe.checkout.Session.create_async(
|
||||
payment_method_types=["card"],
|
||||
line_items=[
|
||||
{
|
||||
"price_data": {
|
||||
"currency": "aud",
|
||||
"product_data": {
|
||||
"name": "Willio Voice Subscription",
|
||||
},
|
||||
"unit_amount": 5000,
|
||||
},
|
||||
"quantity": 1,
|
||||
}
|
||||
],
|
||||
expand=["payment_intent"],
|
||||
mode="payment",
|
||||
payment_intent_data={"metadata": {"order_id": "1"}},
|
||||
success_url=f"{self.redirect_url}auth/waiting",
|
||||
cancel_url=f"{self.redirect_url}auth/waiting",
|
||||
metadata={"user_id": user_id},
|
||||
)
|
||||
return checkout_session
|
||||
except stripe.error.StripeError as e:
|
||||
self.logger.error(f"Error creating checkout session: {e}")
|
||||
raise
|
||||
|
||||
async def create_setup_fees(self, customer_id: str, amount: int):
|
||||
try:
|
||||
setup_intent = await stripe.InvoiceItem.create_async(
|
||||
customer=customer_id,
|
||||
amount=amount,
|
||||
currency="aud",
|
||||
description="Setup Fees",
|
||||
)
|
||||
return setup_intent
|
||||
except stripe.error.StripeError as e:
|
||||
self.logger.error(f"Error creating setup intent: {e}")
|
||||
raise
|
||||
# NOTE: This is not used
|
||||
# async def create_checkout_session(self, user_id: int):
|
||||
# try:
|
||||
# checkout_session = await stripe.checkout.Session.create_async(
|
||||
# payment_method_types=["card"],
|
||||
# line_items=[
|
||||
# {
|
||||
# "price_data": {
|
||||
# "currency": "aud",
|
||||
# "product_data": {
|
||||
# "name": "Willio Voice Subscription",
|
||||
# },
|
||||
# "unit_amount": 5000,
|
||||
# },
|
||||
# "quantity": 1,
|
||||
# }
|
||||
# ],
|
||||
# expand=["payment_intent"],
|
||||
# mode="payment",
|
||||
# payment_intent_data={"metadata": {"order_id": "1"}},
|
||||
# success_url=f"{self.redirect_url}auth/waiting",
|
||||
# cancel_url=f"{self.redirect_url}auth/waiting",
|
||||
# metadata={"user_id": user_id},
|
||||
# )
|
||||
# return checkout_session
|
||||
# except stripe.error.StripeError as e:
|
||||
# self.logger.error(f"Error creating checkout session: {e}")
|
||||
# raise
|
||||
|
||||
async def create_subscription_checkout(
|
||||
self, fees_to_be: dict, clinic_id: int, account_id: str, customer_id: str
|
||||
|
|
@ -313,6 +302,7 @@ class StripeServices:
|
|||
), # Convert to cents
|
||||
"recurring": {
|
||||
"interval": "year",
|
||||
"interval_count": 3, #NOTE: max 3 years supported by stripe
|
||||
},
|
||||
},
|
||||
"quantity": 1,
|
||||
|
|
|
|||
Loading…
Reference in New Issue