Files
AidsMonitoring/backend/routers/contacts.py
T

162 lines
6.1 KiB
Python

"""
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]