"event_type": e.event_type, "detail": (e.detail or "")[:300], "message_id": e.message_id, "created_at": e.created_at.isoformat() if e.created_at else None, }) return {"events": out, "total": total, "page": page, "page_size": page_size} @router.get("/audit") async def list_audit(action: str = Query(""), page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): """Audit log (own actions only). Secrets are redacted at write time.""" q = select(AuditLog).where(AuditLog.user_id == user.id) if action: q = q.where(AuditLog.action == action) total = (await db.execute( select(func.count()).select_from(q.subquery()))).scalar() or 0 rows = (await db.execute( q.order_by(AuditLog.id.desc()).offset((page - 1) * page_size).limit(page_size) )).scalars().all() out = [] for r in rows: out.append({ "id": r.id, "action": r.action, "entity": r.entity, "entity_id": r.entity_id, "detail": r.detail if isinstance(r.detail, dict) else {}, "ip_address": r.ip_address, "created_at": r.created_at.isoformat() if r.created_at else None, }) return {"audit": out, "total": total, "page": page, "page_size": page_size} === all actions written === attachment.clear_all attachment.convert_pdf attachment.delete attachment.upload campaign.cancel campaign.clear_all campaign.create campaign.delete campaign.patch campaign.pause campaign.resume campaign.start gmail.authorized gmail.clear_all gmail.delete gmail.disconnect gmail.test oauth_config.imported phone.add phone.clear_all phone.delete phone.import phone.patch recipients.import recipients.manual_add settings.reset settings.reset_all settings.save settings.save_batch suppression.add suppression.bulk suppression.clear_all suppression.delete suppression.import test_send.error test_send.failed test_send.success user.register