mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
feat: Enable telephony for OSS (#21)
* fix: fix tooltip bug * feat: add Twilio with CloudFlare configuration * chore: update Tella Video
This commit is contained in:
parent
d39a8111a6
commit
8e2e5c9327
21 changed files with 891 additions and 191 deletions
|
|
@ -168,10 +168,10 @@ async def start_campaign(
|
|||
user: UserModel = Depends(get_user),
|
||||
) -> CampaignResponse:
|
||||
"""Start campaign execution"""
|
||||
# Check if organization has TWILIO_PHONE_NUMBERS configured
|
||||
# Check if organization has TWILIO_CONFIGURATION configured
|
||||
twilio_config = await db_client.get_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_PHONE_NUMBERS.value,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
)
|
||||
|
||||
if (
|
||||
|
|
@ -280,10 +280,10 @@ async def resume_campaign(
|
|||
user: UserModel = Depends(get_user),
|
||||
) -> CampaignResponse:
|
||||
"""Resume a paused campaign"""
|
||||
# Check if organization has TWILIO_PHONE_NUMBERS configured
|
||||
# Check if organization has TWILIO_CONFIGURATION configured
|
||||
twilio_config = await db_client.get_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_PHONE_NUMBERS.value,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
)
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from loguru import logger
|
|||
from api.routes.campaign import router as campaign_router
|
||||
from api.routes.integration import router as integration_router
|
||||
from api.routes.looptalk import router as looptalk_router
|
||||
from api.routes.organization import router as organization_router
|
||||
from api.routes.organization_usage import router as organization_usage_router
|
||||
from api.routes.reports import router as reports_router
|
||||
from api.routes.rtc_offer import router as rtc_offer_router
|
||||
|
|
@ -27,6 +28,7 @@ router.include_router(workflow_router)
|
|||
router.include_router(user_router)
|
||||
router.include_router(campaign_router)
|
||||
router.include_router(integration_router)
|
||||
router.include_router(organization_router)
|
||||
router.include_router(s3_router)
|
||||
router.include_router(service_keys_router)
|
||||
router.include_router(looptalk_router)
|
||||
|
|
|
|||
85
api/routes/organization.py
Normal file
85
api/routes/organization.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from api.db import db_client
|
||||
from api.db.models import UserModel
|
||||
from api.enums import OrganizationConfigurationKey
|
||||
from api.schemas.telephony_config import (
|
||||
TelephonyConfigurationResponse,
|
||||
TwilioConfigurationRequest,
|
||||
TwilioConfigurationResponse,
|
||||
)
|
||||
from api.services.auth.depends import get_user
|
||||
from api.services.configuration.masking import is_mask_of, mask_key
|
||||
|
||||
router = APIRouter(prefix="/organizations", tags=["organizations"])
|
||||
|
||||
|
||||
@router.get("/telephony-config", response_model=TelephonyConfigurationResponse)
|
||||
async def get_telephony_configuration(user: UserModel = Depends(get_user)):
|
||||
"""Get telephony configuration for the user's organization with masked sensitive fields."""
|
||||
if not user.selected_organization_id:
|
||||
raise HTTPException(status_code=400, detail="No organization selected")
|
||||
|
||||
config = await db_client.get_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
)
|
||||
|
||||
if not config or not config.value:
|
||||
return TelephonyConfigurationResponse(twilio=None)
|
||||
|
||||
# Mask sensitive fields (account_sid and auth_token) before returning
|
||||
account_sid = config.value.get("account_sid", "")
|
||||
auth_token = config.value.get("auth_token", "")
|
||||
|
||||
return TelephonyConfigurationResponse(
|
||||
twilio=TwilioConfigurationResponse(
|
||||
provider="twilio",
|
||||
account_sid=mask_key(account_sid) if account_sid else "",
|
||||
auth_token=mask_key(auth_token) if auth_token else "",
|
||||
from_numbers=config.value.get("from_numbers", []),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/telephony-config")
|
||||
async def save_telephony_configuration(
|
||||
request: TwilioConfigurationRequest, user: UserModel = Depends(get_user)
|
||||
):
|
||||
"""Save telephony configuration for the user's organization."""
|
||||
if not user.selected_organization_id:
|
||||
raise HTTPException(status_code=400, detail="No organization selected")
|
||||
|
||||
# Fetch existing configuration to handle masked values
|
||||
existing_config = await db_client.get_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
)
|
||||
|
||||
# Build new configuration
|
||||
config_value = {
|
||||
"provider": request.provider,
|
||||
"account_sid": request.account_sid,
|
||||
"auth_token": request.auth_token,
|
||||
"from_numbers": request.from_numbers,
|
||||
}
|
||||
|
||||
# If incoming values are masked (same as stored masked value), keep the original
|
||||
if existing_config and existing_config.value:
|
||||
# Check if account_sid is unchanged (masked value matches)
|
||||
stored_account_sid = existing_config.value.get("account_sid", "")
|
||||
if stored_account_sid and is_mask_of(request.account_sid, stored_account_sid):
|
||||
config_value["account_sid"] = stored_account_sid # Keep original
|
||||
|
||||
# Check if auth_token is unchanged (masked value matches)
|
||||
stored_auth_token = existing_config.value.get("auth_token", "")
|
||||
if stored_auth_token and is_mask_of(request.auth_token, stored_auth_token):
|
||||
config_value["auth_token"] = stored_auth_token # Keep original
|
||||
|
||||
await db_client.upsert_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
config_value,
|
||||
)
|
||||
|
||||
return {"message": "Telephony configuration saved successfully"}
|
||||
|
|
@ -5,7 +5,6 @@ from typing import Annotated, Optional
|
|||
|
||||
from fastapi import APIRouter, Depends, Form, Header, HTTPException, Request, WebSocket
|
||||
from loguru import logger
|
||||
from pipecat.utils.context import set_current_run_id
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
|
|
@ -19,6 +18,7 @@ from api.services.campaign.campaign_event_publisher import (
|
|||
)
|
||||
from api.services.pipecat.run_pipeline import run_pipeline_twilio
|
||||
from api.services.telephony.twilio import TwilioService
|
||||
from pipecat.utils.context import set_current_run_id
|
||||
|
||||
router = APIRouter(prefix="/twilio")
|
||||
|
||||
|
|
@ -45,20 +45,16 @@ class TwilioStatusCallbackRequest(BaseModel):
|
|||
async def initiate_call(
|
||||
request: InitiateCallRequest, user: UserModel = Depends(get_user)
|
||||
):
|
||||
# Check if organization has TWILIO_PHONE_NUMBERS configured
|
||||
# Check if organization has TWILIO_CONFIGURATION configured
|
||||
twilio_config = await db_client.get_configuration(
|
||||
user.selected_organization_id,
|
||||
OrganizationConfigurationKey.TWILIO_PHONE_NUMBERS.value,
|
||||
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
||||
)
|
||||
|
||||
if (
|
||||
not twilio_config
|
||||
or not twilio_config.value
|
||||
or not twilio_config.value.get("value")
|
||||
):
|
||||
if not twilio_config or not twilio_config.value:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Your organisation is not allowed to make phone call. Contact founders@dograh.com for further support.",
|
||||
status_code=400,
|
||||
detail="telephony_not_configured", # Special error code
|
||||
)
|
||||
|
||||
user_configuration = await db_client.get_user_configurations(user.id)
|
||||
|
|
@ -84,15 +80,16 @@ async def initiate_call(
|
|||
workflow_run_name = workflow_run.name
|
||||
|
||||
if user_configuration.test_phone_number:
|
||||
await TwilioService().initiate_call(
|
||||
twilio_service = TwilioService(user.selected_organization_id)
|
||||
await twilio_service.initiate_call(
|
||||
to_number=user_configuration.test_phone_number,
|
||||
url_args={
|
||||
"workflow_id": request.workflow_id,
|
||||
"user_id": user.id,
|
||||
"workflow_run_id": workflow_run_id,
|
||||
"organization_id": user.selected_organization_id,
|
||||
},
|
||||
workflow_run_id=workflow_run_id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
return {
|
||||
"message": f"Call initiated successfully with run name {workflow_run_name}"
|
||||
|
|
@ -102,8 +99,10 @@ async def initiate_call(
|
|||
|
||||
|
||||
@router.post("/twiml", include_in_schema=False)
|
||||
async def start_call(workflow_id: int, user_id: int, workflow_run_id: int):
|
||||
twiml_content = await TwilioService().get_start_call_twiml(
|
||||
async def start_call(
|
||||
workflow_id: int, user_id: int, workflow_run_id: int, organization_id: int
|
||||
):
|
||||
twiml_content = await TwilioService(organization_id).get_start_call_twiml(
|
||||
workflow_id, user_id, workflow_run_id
|
||||
)
|
||||
return HTMLResponse(content=twiml_content, media_type="application/xml")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue