@router.post("/import-preview") async def import_preview(file: UploadFile = File(...), user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): """Parse + validate an upload WITHOUT saving. Shows the full validation report.""" content = await file.read() if len(content) > settings.MAX_UPLOAD_MB * 1024 * 1024: raise HTTPException(413, f"File too large (max {settings.MAX_UPLOAD_MB} MB)") fname = (file.filename or "").lower() if fname.endswith(".json"): raise HTTPException(400, "JSON recipient files are not accepted. Use CSV or TXT.") if not (fname.endswith(".csv") or fname.endswith(".txt")): raise HTTPException(400, "Only .csv and .txt files are accepted for recipients") if fname.endswith(".txt"): lines = recipient_service.parse_txt(content.decode("utf-8", errors="replace")) records = [] for ln in lines: parts = [p.strip() for p in ln.split(",")] rec = {"email": parts[0]} if len(parts) > 1: rec["first_name"] = parts[1] if len(parts) > 2: rec["last_name"] = parts[2] records.append(rec) headers = ["email", "first_name", "last_name"] parse_errors = [] else: records, headers, parse_errors = recipient_service.parse_csv( content.decode("utf-8", errors="replace")) sup, bounced = await _suppression_and_bounces(db) existing = set((await db.execute(select(Recipient.email))).scalars().all()) rpt = recipient_service.validate_rows(records, sup, bounced, existing, settings.MAX_RECIPIENTS_PER_CAMPAIGN) return {"filename": file.filename, "headers": headers, "summary": rpt.summary(), "valid_sample": rpt.valid[:25], "invalid_sample": rpt.invalid[:25], "duplicates_sample": rpt.duplicates[:10], "suppressed_sample": rpt.suppressed[:10], "parse_errors": parse_errors[:50], "parsed_records": len(records)} @router.post("/import") async def import_commit(list_id: int = Form(...), file: UploadFile = File(...), user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), request: Request = None): """Commit an import into the given list (same validation as preview).""" rl = await db.get(RecipientList, list_id) if not rl or rl.user_id != user.id: raise HTTPException(404, "Recipient list not found") content = await file.read() if len(content) > settings.MAX_UPLOAD_MB * 1024 * 1024: raise HTTPException(413, "File too large") fname = (file.filename or "").lower() if fname.endswith(".json"): raise HTTPException(400, "JSON recipient files are not accepted. Use CSV or TXT.") if not (fname.endswith(".csv") or fname.endswith(".txt")): raise HTTPException(400, "Only .csv and .txt files are accepted") if fname.endswith(".txt"): lines = recipient_service.parse_txt(content.decode("utf-8", errors="replace")) records = [] 53:@router.get("/lists") 62:@router.post("/lists") 71:@router.post("/import-preview") 116:@router.post("/import") 167:@router.post("/manual") 202:@router.get("/list") 216:@router.delete("/{recipient_id}") 231:@router.delete("/lists/{list_id}") 243:@router.post("/lists/{list_id}/clear")