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