@router.get("/list") async def list_suppression(search: str = Query(""), reason: str = Query(""), page: int = Query(1, ge=1), page_size: int = Query(100, ge=1, le=500), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): q = select(SuppressionEntry).where(SuppressionEntry.user_id == user.id) if search: q = q.where(SuppressionEntry.email.ilike(f"%{search}%")) if reason: q = q.where(SuppressionEntry.reason == reason) total = (await db.execute( select(func.count()).select_from(q.subquery()))).scalar() or 0 rows = (await db.execute( q.order_by(SuppressionEntry.id.desc()).offset((page - 1) * page_size).limit(page_size) )).scalars().all() return {"entries": [_serialize(s) for s in rows], "total": total, "page": page, "page_size": page_size} @router.post("/add") async def add_suppression(payload: SuppressionIn, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): email = payload.email.strip().lower() payload.reason = _norm_reason(payload.reason) existing = await db.execute( select(SuppressionEntry).where(SuppressionEntry.email == email, SuppressionEntry.user_id == user.id)) if existing.first(): raise HTTPException(status_code=409, detail="Already suppressed") s = SuppressionEntry(user_id=user.id, email=email, reason=payload.reason, source=payload.source.strip()[:200]) db.add(s) await db.commit() await log_audit(db, user.id, "suppression.add", "suppression", s.id, {"reason": s.reason}) return _serialize(s) @router.post("/import") @router.post("/bulk") async def bulk_add_suppression(payload: BulkEmails, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): payload.reason = _norm_reason(payload.reason) existing = set((await db.execute( select(SuppressionEntry.email).where(SuppressionEntry.user_id == user.id))).scalars()) added = 0 for e in payload.emails: email = e.strip().lower() if email in existing: continue db.add(SuppressionEntry(user_id=user.id, email=email, reason=payload.reason)) existing.add(email) added += 1 await db.commit() await log_audit(db, user.id, "suppression.bulk", "suppression", None, {"added": added}) return {"added": added, "duplicates": len(payload.emails) - added} @router.post("/check") async def check_suppression(payload: CheckEmails, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): """Return which of the given emails are currently suppressed.""" emails = [e.strip().lower() for e in payload.emails] rows = (await db.execute( select(SuppressionEntry.email).where(SuppressionEntry.user_id == user.id, SuppressionEntry.email.in_(emails)))).scalars().all() suppressed = set(rows) return {"suppressed": {e: (e in suppressed) for e in emails}} @router.post("/{sid}/delete") async def delete_suppression(sid: int, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): s = await db.get(SuppressionEntry, sid) if not s or s.user_id != user.id: raise HTTPException(status_code=404, detail="Entry not found") email = s.email await db.delete(s) await db.commit() await log_audit(db, user.id, "suppression.delete", "suppression", sid, {"email": email}) return {"deleted": sid} @router.post("/clear-all")