urmi-ai/backend/app/routes_messaging.py
2026-10-02 11:22:40 +05:30

211 lines
6.8 KiB
Python

from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import Session, joinedload
from .auth import current_user
from .database import get_db
from .message_send_service import SendMessageError, send_template_messages
from .models import SenderConfig, TemplateConfig, UserAccount
from .sender_validators import (
validate_dlt_entity_id,
validate_purpose,
validate_sender_id,
validate_sender_type,
)
from .template_validators import (
validate_dlt_template_id,
validate_template_name,
validate_template_text,
)
router = APIRouter(prefix="/api/v1/messaging", tags=["messaging"])
class CreateSenderRequest(BaseModel):
sender_id: str = Field(min_length=3, max_length=11)
sender_type: str
purpose: str = Field(min_length=10, max_length=500)
dlt_entity_id: str = Field(min_length=12, max_length=25)
class CreateTemplateRequest(BaseModel):
sender_config_id: str
template_name: str = Field(min_length=3, max_length=40)
template_text: str = Field(min_length=10, max_length=1000)
dlt_template_id: str = Field(min_length=12, max_length=25)
def serialize_sender(sender: SenderConfig) -> dict:
return {
"id": str(sender.id),
"sender_id": sender.sender_id,
"sender_type": sender.sender_type,
"purpose": sender.purpose,
"dlt_entity_id": sender.dlt_entity_id,
"status": sender.status,
"review_note": sender.review_note,
"created_at": sender.created_at,
"reviewed_at": sender.reviewed_at,
}
def serialize_template(template: TemplateConfig) -> dict:
return {
"id": str(template.id),
"sender_config_id": str(template.sender_config_id),
"sender_id": template.sender_config.sender_id,
"template_name": template.template_name,
"template_text": template.template_text,
"dlt_template_id": template.dlt_template_id,
"status": template.status,
"review_note": template.review_note,
"created_at": template.created_at,
"reviewed_at": template.reviewed_at,
}
@router.get("/senders")
def list_senders(
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> list[dict]:
senders = db.scalars(
select(SenderConfig)
.where(SenderConfig.user_id == user.id)
.order_by(SenderConfig.created_at.desc())
).all()
return [serialize_sender(sender) for sender in senders]
@router.post("/senders", status_code=status.HTTP_201_CREATED)
def create_sender(
payload: CreateSenderRequest,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
try:
sender_id = validate_sender_id(payload.sender_id)
sender_type = validate_sender_type(payload.sender_type)
purpose = validate_purpose(payload.purpose)
dlt_entity_id = validate_dlt_entity_id(payload.dlt_entity_id)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
existing = db.scalar(
select(SenderConfig).where(
SenderConfig.user_id == user.id,
SenderConfig.sender_id == sender_id,
)
)
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Sender ID already exists")
sender = SenderConfig(
user_id=user.id,
sender_id=sender_id,
sender_type=sender_type,
purpose=purpose,
dlt_entity_id=dlt_entity_id,
status="pending",
)
db.add(sender)
db.commit()
db.refresh(sender)
return serialize_sender(sender)
@router.get("/templates")
def list_templates(
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> list[dict]:
templates = db.scalars(
select(TemplateConfig)
.options(joinedload(TemplateConfig.sender_config))
.where(TemplateConfig.user_id == user.id)
.order_by(TemplateConfig.created_at.desc())
).unique().all()
return [serialize_template(template) for template in templates]
@router.post("/templates", status_code=status.HTTP_201_CREATED)
def create_template(
payload: CreateTemplateRequest,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
try:
template_name = validate_template_name(payload.template_name)
template_text = validate_template_text(payload.template_text)
dlt_template_id = validate_dlt_template_id(payload.dlt_template_id)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
sender = db.scalar(
select(SenderConfig).where(
SenderConfig.id == payload.sender_config_id,
SenderConfig.user_id == user.id,
)
)
if not sender:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sender ID not found")
if sender.status != "approved":
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Template can only be linked to an approved sender ID",
)
existing = db.scalar(
select(TemplateConfig).where(
TemplateConfig.user_id == user.id,
TemplateConfig.template_name == template_name,
)
)
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Template name already exists")
template = TemplateConfig(
user_id=user.id,
sender_config_id=sender.id,
template_name=template_name,
template_text=template_text,
dlt_template_id=dlt_template_id,
status="pending",
)
db.add(template)
db.commit()
db.refresh(template)
db.refresh(sender)
template.sender_config = sender
return serialize_template(template)
class ComposeSendRequest(BaseModel):
sender_config_id: UUID
template_id: UUID
numbers: str = Field(min_length=10, max_length=5000)
message: str = Field(min_length=1, max_length=1000)
campaign_name: str | None = Field(default=None, max_length=120)
@router.post("/send")
def compose_send_message(
payload: ComposeSendRequest,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
try:
return send_template_messages(
db,
user,
sender_config_id=payload.sender_config_id,
template_id=payload.template_id,
message=payload.message,
recipients=[part.strip() for part in payload.numbers.split(",") if part.strip()],
campaign_name=payload.campaign_name,
)
except SendMessageError as exc:
raise HTTPException(status_code=exc.status_code, detail=exc.detail) from exc