first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
90
backend/app/otp_service.py
Normal file
90
backend/app/otp_service.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
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,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue