OTP send
This commit is contained in:
parent
e98036cd31
commit
614cf1e7f5
4 changed files with 398 additions and 15 deletions
|
|
@ -6,7 +6,8 @@ from sqlalchemy.orm import Session
|
|||
from .log_service import record_otp_log
|
||||
from .message_send_service import SendMessageError, normalize_recipients
|
||||
from .models import UrmiOtpProject, UserAccount
|
||||
from .urmiotp_catalog import OTP_TEMPLATES
|
||||
from .ultronsms_client import UltronsmsClient
|
||||
from .urmiotp_catalog import OTP_TEMPLATES, URMI_OTP_SMS_CONFIG, build_dlt_otp_message
|
||||
|
||||
TEMPLATE_TYPES = {template["type"] for template in OTP_TEMPLATES}
|
||||
|
||||
|
|
@ -18,11 +19,7 @@ class GenerateOtpPayload(BaseModel):
|
|||
variables: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
def _build_otp_message(template_type: str, message_label: str, variables: dict[str, str]) -> str:
|
||||
template = next((item for item in OTP_TEMPLATES if item["type"] == template_type), None)
|
||||
if not template:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Unknown template type")
|
||||
|
||||
def _require_otp_variables(variables: dict[str, str]) -> None:
|
||||
otp_value = variables.get("otp", "").strip()
|
||||
validity = variables.get("validity_minutes", "").strip()
|
||||
if not otp_value or not validity:
|
||||
|
|
@ -31,11 +28,6 @@ def _build_otp_message(template_type: str, message_label: str, variables: dict[s
|
|||
detail="variables.otp and variables.validity_minutes are required",
|
||||
)
|
||||
|
||||
content = template["content"].replace("{message_label}", message_label)
|
||||
for value in (otp_value, validity):
|
||||
content = content.replace("{#var#}", value, 1)
|
||||
return content
|
||||
|
||||
|
||||
def generate_otp_for_user(
|
||||
db: Session,
|
||||
|
|
@ -68,7 +60,52 @@ def generate_otp_for_user(
|
|||
raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc
|
||||
|
||||
recipient = recipients[0]
|
||||
message = _build_otp_message(payload.template_type, project.message_label, payload.variables)
|
||||
_require_otp_variables(payload.variables)
|
||||
try:
|
||||
message = build_dlt_otp_message(payload.variables, project.message_label)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
|
||||
|
||||
client = UltronsmsClient()
|
||||
try:
|
||||
send_result = client.send_template_sms(
|
||||
mobile=recipient,
|
||||
message=message,
|
||||
sender_id=URMI_OTP_SMS_CONFIG["sender_id"],
|
||||
pe_id=URMI_OTP_SMS_CONFIG["pe_id"],
|
||||
dlt_template_id=URMI_OTP_SMS_CONFIG["dlt_template_id"],
|
||||
sender_type="transactional",
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
entry = record_otp_log(
|
||||
db,
|
||||
user,
|
||||
project_name=project.project_name,
|
||||
template_type=payload.template_type,
|
||||
to_number=recipient,
|
||||
status="failed",
|
||||
error_message=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
|
||||
if not send_result.success:
|
||||
error_message = send_result.raw.get("ErrorMessage") or "SMS gateway rejected the request"
|
||||
entry = record_otp_log(
|
||||
db,
|
||||
user,
|
||||
project_name=project.project_name,
|
||||
template_type=payload.template_type,
|
||||
to_number=recipient,
|
||||
status="failed",
|
||||
error_message=error_message,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=error_message,
|
||||
)
|
||||
|
||||
entry = record_otp_log(
|
||||
db,
|
||||
|
|
@ -76,7 +113,9 @@ def generate_otp_for_user(
|
|||
project_name=project.project_name,
|
||||
template_type=payload.template_type,
|
||||
to_number=recipient,
|
||||
status="accepted",
|
||||
status="sent",
|
||||
job_id=send_result.job_id,
|
||||
message_id=send_result.message_id,
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -84,7 +123,9 @@ def generate_otp_for_user(
|
|||
"log_id": str(entry.id),
|
||||
"project_name": project.project_name,
|
||||
"template_type": payload.template_type,
|
||||
"to": recipient,
|
||||
"to": send_result.number or recipient,
|
||||
"message_preview": message,
|
||||
"status": entry.status,
|
||||
"job_id": send_result.job_id,
|
||||
"message_id": send_result.message_id,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,35 @@ def render_templates_for_project(message_label: str) -> list[dict]:
|
|||
return rendered
|
||||
|
||||
|
||||
# DLT-approved SMS body for Ultronsms (template ID 1777178797989033323).
|
||||
# All UrmiOTP template_type values are delivered using this registered text.
|
||||
URMI_OTP_SMS_CONFIG = {
|
||||
"sender_id": "URMIAI",
|
||||
"pe_id": "1701178780498045011",
|
||||
"dlt_template_id": "1777178797989033323",
|
||||
"message_template": (
|
||||
"Your URMI app verification code is {otp} for {message_label}. "
|
||||
"It is valid for {validity_minutes} minutes. "
|
||||
"Do not share it to anyone - URMI AI"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def build_dlt_otp_message(variables: dict[str, str], message_label: str) -> str:
|
||||
otp_value = variables.get("otp", "").strip()
|
||||
validity = variables.get("validity_minutes", "").strip()
|
||||
label = message_label.strip()
|
||||
if not otp_value or not validity:
|
||||
raise ValueError("variables.otp and variables.validity_minutes are required")
|
||||
if not label:
|
||||
raise ValueError("message_label is required")
|
||||
return URMI_OTP_SMS_CONFIG["message_template"].format(
|
||||
otp=otp_value,
|
||||
message_label=label,
|
||||
validity_minutes=validity,
|
||||
)
|
||||
|
||||
|
||||
OTP_API_DOCUMENTATION = {
|
||||
"method": "POST",
|
||||
"path": "/api/v1/otp/generate",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue