""" Contact catalog (port authorities + aid owners) + alert report log. """ from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy.orm import Session from typing import Optional import uuid from database import get_db from models.contact import Contact, AlertReport from models.aid import Aid from models.user import User from routers.auth import get_current_user, require_admin from services import notifier router = APIRouter(tags=["contacts"]) class ContactIn(BaseModel): name: str role: str # PORT_AUTHORITY | OWNER email: Optional[str] = None phone: Optional[str] = None whatsapp: Optional[str] = None port_name: Optional[str] = None company_name: Optional[str] = None preferred_channel: str = "EMAIL" notes: Optional[str] = None def _row_to_dict(c: Contact) -> dict: return {col.name: getattr(c, col.name) for col in c.__table__.columns} # ── Contacts CRUD (SUPERADMIN only) ─────────────────────────────────────── @router.get("/contacts/") def list_contacts(db: Session = Depends(get_db)): return [_row_to_dict(c) for c in db.query(Contact).order_by(Contact.role, Contact.name).all()] @router.post("/contacts/") def create_contact(data: ContactIn, db: Session = Depends(get_db), current_user: User = Depends(require_admin)): if data.role not in ("PORT_AUTHORITY", "OWNER"): raise HTTPException(400, "role must be PORT_AUTHORITY or OWNER") if data.preferred_channel not in ("EMAIL", "SMS", "WHATSAPP"): raise HTTPException(400, "preferred_channel must be EMAIL, SMS or WHATSAPP") c = Contact(id=str(uuid.uuid4()), **data.model_dump()) db.add(c); db.commit(); db.refresh(c) return _row_to_dict(c) @router.put("/contacts/{contact_id}") def update_contact(contact_id: str, data: ContactIn, db: Session = Depends(get_db), current_user: User = Depends(require_admin)): c = db.query(Contact).filter(Contact.id == contact_id).first() if not c: raise HTTPException(404, "Contact not found") for k, v in data.model_dump().items(): setattr(c, k, v) db.commit(); db.refresh(c) return _row_to_dict(c) @router.delete("/contacts/{contact_id}") def delete_contact(contact_id: str, db: Session = Depends(get_db), current_user: User = Depends(require_admin)): db.query(Contact).filter(Contact.id == contact_id).delete() db.commit() return {"deleted": contact_id} # ── Resolve recipients for a given aid ──────────────────────────────────── @router.get("/alerts/resolve-recipients") def resolve_recipients(aid_id: Optional[str] = None, mmsi: Optional[str] = None, db: Session = Depends(get_db)): """For a given aid (by id or mmsi), return port authority + owner contacts.""" aid = None if aid_id: aid = db.query(Aid).filter(Aid.id == aid_id).first() if aid is None and mmsi: aid = db.query(Aid).filter(Aid.mmsi == mmsi).first() if aid is None: return {"aid": None, "recipients": []} recipients = [] if aid.puerto_responsable: for c in db.query(Contact).filter( Contact.role == "PORT_AUTHORITY", Contact.port_name == aid.puerto_responsable ).all(): recipients.append(_row_to_dict(c)) if aid.empresa_responsable: for c in db.query(Contact).filter( Contact.role == "OWNER", Contact.company_name == aid.empresa_responsable ).all(): recipients.append(_row_to_dict(c)) return { "aid": { "id": aid.id, "nombre": aid.nombre, "mmsi": aid.mmsi, "puerto_responsable": aid.puerto_responsable, "empresa_responsable": aid.empresa_responsable, }, "recipients": recipients, } # ── Log a report (audit trail) ──────────────────────────────────────────── class ReportIn(BaseModel): alert_id: Optional[str] = None alert_tipo: Optional[str] = None alert_subtipo: Optional[str] = None aid_id: Optional[str] = None aid_nombre: Optional[str] = None mmsi: Optional[str] = None contact_id: Optional[str] = None contact_name: Optional[str] = None channel: str # EMAIL | SMS | WHATSAPP recipient: str message: Optional[str] = None @router.post("/alerts/report") def log_report(data: ReportIn, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): """Log a report row AND, when channel=EMAIL and SMTP is configured, actually send the email through the operator's SMTP account. The frontend can then skip opening mailto:.""" smtp_sent = False smtp_detail = None if data.channel == "EMAIL" and notifier.smtp_configured() and data.recipient: subject = f"[AidsMonitoring] {data.alert_tipo or 'Alert'} — {data.aid_nombre or data.aid_id or ''}" smtp_sent, smtp_detail = notifier.send_email(data.recipient, subject, data.message or "") rep = AlertReport( id=str(uuid.uuid4()), reported_by=current_user.username, **data.model_dump(), ) db.add(rep); db.commit() return { "ok": True, "id": rep.id, "smtp_sent": smtp_sent, "smtp_detail": smtp_detail, "smtp_configured": notifier.smtp_configured(), } @router.get("/alerts/reports") def list_reports(alert_id: Optional[str] = Query(None), limit: int = 200, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): q = db.query(AlertReport) if alert_id: q = q.filter(AlertReport.alert_id == alert_id) rows = q.order_by(AlertReport.reported_at.desc()).limit(limit).all() return [{col.name: getattr(r, col.name) for col in r.__table__.columns} for r in rows]