@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") async def clear_all_suppression(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): rows = (await db.execute( select(SuppressionEntry).where(SuppressionEntry.user_id == user.id))).scalars().all() for s in rows: await db.delete(s) await db.commit() await log_audit(db, user.id, "suppression.clear_all", "suppression", None, ===ATT-DEL=== @router.post("/{att_id}/delete") async def delete_attachment(att_id: int, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): att = await _get_own(db, user, att_id) att_name = att.name if att.stored_path: await asyncio.get_event_loop().run_in_executor( None, att_svc.delete_attachment_file, att) await db.delete(att) await db.commit() await log_audit(db, user.id, "attachment.delete", "attachment", att_id, {"name": att_name}) return {"deleted": att_id} @router.post("/clear-all") async def clear_all_attachments(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): """DELETE ALL attachments for the user. Requires CSRF; UI shows explicit confirm dialog.""" atts = (await db.execute( select(Attachment).where(Attachment.user_id == user.id))).scalars().all() loop = asyncio.get_event_loop() count = 0 for a in atts: if a.stored_path: await loop.run_in_executor(None, att_svc.delete_attachment_file, a) await db.delete(a) count += 1 await db.commit() await log_audit(db, user.id, "attachment.clear_all", "attachment", None, {"deleted": count}) return {"deleted": count} ===RESET-ALL-RET=== @router.post("/reset-all") async def reset_all_settings(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), _: None = Depends(require_csrf)): rows = (await db.execute( select(ApplicationSettings).where(ApplicationSettings.key.in_(SETTING_DEFS.keys())) )).scalars().all() for r in rows: await db.delete(r) await db.commit() await log_audit(db, user.id, "settings.reset_all", "application_setting", None, {"reset": len(rows)})