"""Campaign logs and audit log endpoints: send attempts, events, errors with filters, CSV export, per-recipient history. Combined logs view for the Logs page.""" import csv import io from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import StreamingResponse from sqlalchemy import func, or_, select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user, get_db from app.models import (AuditLog, Campaign, CampaignRecipient, EmailEvent, SendAttempt, SendJob, User) router = APIRouter(prefix="/logs", tags=["logs"]) def _ser_attempt(a: SendAttempt, cr_id: int = None) -> dict: return { "id": a.id, "campaign_id": a.campaign_id, "campaign_recipient_id": cr_id if cr_id is not None else (a.send_job.campaign_recipient_id if a.send_job else None), "account_id": a.gmail_account_id, "attempt_number": (a.retry_no or 0) + 1, "outcome": a.outcome, "error_class": a.error_class, "error_message": (a.failure_reason or "")[:300], "message_id": a.message_id, "thread_id": getattr(a, "thread_id", "") or "", "method": a.method, "subject": (a.subject or "")[:160], "template_name": (a.template_name or "")[:160], "provider_response": (a.provider_response or "")[:300], "created_at": a.created_at.isoformat() if a.created_at else None, } @router.get("/list") async def list_logs(campaign_id: int = Query(0), === any other log_audit writers anywhere ===