from fastapi import HTTPException, status from pydantic import BaseModel, Field from sqlalchemy import select 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 TEMPLATE_TYPES = {template["type"] for template in OTP_TEMPLATES} class GenerateOtpPayload(BaseModel): project_name: str = Field(min_length=3, max_length=30) template_type: str = Field(min_length=3, max_length=40) to: str = Field(min_length=10, max_length=15) 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") otp_value = variables.get("otp", "").strip() validity = variables.get("validity_minutes", "").strip() if not otp_value or not validity: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 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, user: UserAccount, payload: GenerateOtpPayload, ) -> dict: if payload.template_type not in TEMPLATE_TYPES: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"template_type must be one of: {', '.join(sorted(TEMPLATE_TYPES))}", ) project = db.scalar( select(UrmiOtpProject).where( UrmiOtpProject.user_id == user.id, UrmiOtpProject.project_name == payload.project_name.strip(), ) ) if not project: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="UrmiOTP project not found") if project.status != "approved": raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="UrmiOTP project is not approved yet", ) try: recipients = normalize_recipients([payload.to]) except SendMessageError as exc: 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) entry = record_otp_log( db, user, project_name=project.project_name, template_type=payload.template_type, to_number=recipient, status="accepted", ) return { "success": True, "log_id": str(entry.id), "project_name": project.project_name, "template_type": payload.template_type, "to": recipient, "message_preview": message, "status": entry.status, }