mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
177 lines
8 KiB
Python
177 lines
8 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from loguru import logger
|
|
|
|
from api.db import db_client
|
|
from api.db.models import UserModel
|
|
from api.enums import OrganizationConfigurationKey
|
|
from typing import Optional, Union
|
|
from api.schemas.telephony_config import (
|
|
TelephonyConfigurationResponse,
|
|
TwilioConfigurationRequest,
|
|
TwilioConfigurationResponse,
|
|
VonageConfigurationRequest,
|
|
VonageConfigurationResponse,
|
|
)
|
|
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"])
|
|
|
|
|
|
# TODO: Make endpoints provider-agnostic
|
|
@router.get("/telephony-config", response_model=TelephonyConfigurationResponse)
|
|
async def get_telephony_configuration(
|
|
user: UserModel = Depends(get_user),
|
|
provider: Optional[str] = None # Query param to filter by provider
|
|
):
|
|
"""Get telephony configuration for the user's organization with masked sensitive fields.
|
|
|
|
Args:
|
|
provider: Optional provider filter ('twilio' or 'vonage').
|
|
If specified, only returns config if it matches the stored provider.
|
|
"""
|
|
if not user.selected_organization_id:
|
|
raise HTTPException(status_code=400, detail="No organization selected")
|
|
|
|
# Try new key first, fallback to old for backward compatibility
|
|
config = await db_client.get_configuration(
|
|
user.selected_organization_id,
|
|
OrganizationConfigurationKey.TELEPHONY_CONFIGURATION.value,
|
|
)
|
|
|
|
# TODO: Remove after telephony provider db migration is complete
|
|
if not config:
|
|
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, vonage=None)
|
|
|
|
# Simple single-provider format
|
|
stored_provider = config.value.get("provider", "twilio")
|
|
|
|
# If a specific provider is requested, only return config if it matches
|
|
if provider and provider != stored_provider:
|
|
# User is requesting a different provider than what's stored
|
|
return TelephonyConfigurationResponse(twilio=None, vonage=None)
|
|
|
|
if stored_provider == "twilio":
|
|
# 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", []),
|
|
),
|
|
vonage=None
|
|
)
|
|
elif stored_provider == "vonage":
|
|
# Mask sensitive fields for Vonage
|
|
application_id = config.value.get("application_id", "")
|
|
private_key = config.value.get("private_key", "")
|
|
api_key = config.value.get("api_key", "")
|
|
api_secret = config.value.get("api_secret", "")
|
|
|
|
return TelephonyConfigurationResponse(
|
|
twilio=None,
|
|
vonage=VonageConfigurationResponse(
|
|
provider="vonage",
|
|
application_id=application_id, # Not masked, not sensitive
|
|
private_key=mask_key(private_key) if private_key else "",
|
|
api_key=mask_key(api_key) if api_key else None,
|
|
api_secret=mask_key(api_secret) if api_secret else None,
|
|
from_numbers=config.value.get("from_numbers", []),
|
|
)
|
|
)
|
|
else:
|
|
return TelephonyConfigurationResponse(twilio=None, vonage=None)
|
|
|
|
|
|
@router.post("/telephony-config")
|
|
async def save_telephony_configuration(
|
|
request: Union[TwilioConfigurationRequest, VonageConfigurationRequest],
|
|
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.TELEPHONY_CONFIGURATION.value,
|
|
)
|
|
if not existing_config:
|
|
# Check old key for backward compatibility
|
|
existing_config = await db_client.get_configuration(
|
|
user.selected_organization_id,
|
|
OrganizationConfigurationKey.TWILIO_CONFIGURATION.value,
|
|
)
|
|
|
|
# Build simple single-provider configuration
|
|
if request.provider == "twilio":
|
|
config_value = {
|
|
"provider": "twilio",
|
|
"account_sid": request.account_sid,
|
|
"auth_token": request.auth_token,
|
|
"from_numbers": request.from_numbers,
|
|
}
|
|
elif request.provider == "vonage":
|
|
config_value = {
|
|
"provider": "vonage",
|
|
"application_id": request.application_id,
|
|
"private_key": request.private_key,
|
|
"api_key": getattr(request, 'api_key', None),
|
|
"api_secret": getattr(request, 'api_secret', None),
|
|
"from_numbers": request.from_numbers,
|
|
}
|
|
else:
|
|
raise HTTPException(status_code=400, detail=f"Unsupported provider: {request.provider}")
|
|
|
|
# Handle masked values - only if same provider
|
|
if existing_config and existing_config.value:
|
|
existing_provider = existing_config.value.get("provider")
|
|
|
|
# Only preserve masked values if it's the same provider
|
|
if existing_provider == request.provider:
|
|
if request.provider == "twilio":
|
|
# Check if account_sid is unchanged (masked value matches)
|
|
if hasattr(request, 'account_sid') and is_mask_of(request.account_sid, existing_config.value.get("account_sid", "")):
|
|
config_value["account_sid"] = existing_config.value["account_sid"] # Keep original
|
|
|
|
# Check if auth_token is unchanged (masked value matches)
|
|
if hasattr(request, 'auth_token') and is_mask_of(request.auth_token, existing_config.value.get("auth_token", "")):
|
|
config_value["auth_token"] = existing_config.value["auth_token"] # Keep original
|
|
|
|
elif request.provider == "vonage":
|
|
# Check if private_key is unchanged (masked value matches)
|
|
if hasattr(request, 'private_key') and is_mask_of(request.private_key, existing_config.value.get("private_key", "")):
|
|
config_value["private_key"] = existing_config.value["private_key"] # Keep original
|
|
|
|
# Check if api_key is unchanged (masked value matches)
|
|
if hasattr(request, 'api_key') and request.api_key and is_mask_of(request.api_key, existing_config.value.get("api_key", "")):
|
|
config_value["api_key"] = existing_config.value["api_key"] # Keep original
|
|
|
|
# Check if api_secret is unchanged (masked value matches)
|
|
if hasattr(request, 'api_secret') and request.api_secret and is_mask_of(request.api_secret, existing_config.value.get("api_secret", "")):
|
|
config_value["api_secret"] = existing_config.value["api_secret"] # Keep original
|
|
|
|
# Always save to new TELEPHONY_CONFIGURATION key
|
|
await db_client.upsert_configuration(
|
|
user.selected_organization_id,
|
|
OrganizationConfigurationKey.TELEPHONY_CONFIGURATION.value,
|
|
config_value,
|
|
)
|
|
|
|
# If old TWILIO_CONFIGURATION exists, delete it to avoid confusion
|
|
if existing_config and existing_config.key == OrganizationConfigurationKey.TWILIO_CONFIGURATION.value:
|
|
# Note: We're migrating from old to new key
|
|
logger.info(f"Migrated telephony config from TWILIO_CONFIGURATION to TELEPHONY_CONFIGURATION for org {user.selected_organization_id}")
|
|
|
|
return {"message": "Telephony configuration saved successfully"}
|