from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from sqlalchemy.orm import Session from typing import List import pandas as pd import io from app.db.base import get_db from app.core.deps import get_current_user from app.models.user import User from app.models.contact import Contact, DNDList from app.schemas.imports import ImportSummary from app.utils.phone import normalize_phone router = APIRouter() def process_import_file( df: pd.DataFrame, user_id: int, source: str, db: Session ) -> ImportSummary: """Process imported contacts dataframe""" summary = ImportSummary( total=len(df), created=0, updated=0, skipped=0, invalid=0, errors=[] ) # Normalize column names df.columns = [col.lower().strip() for col in df.columns] # Check required columns if 'phone' not in df.columns: summary.errors.append("Missing required column: phone") summary.invalid = len(df) return summary for idx, row in df.iterrows(): try: phone = str(row.get('phone', '')).strip() if not phone: summary.skipped += 1 continue # Normalize phone normalized_phone = normalize_phone(phone) if not normalized_phone: summary.invalid += 1 summary.errors.append(f"Row {idx+1}: Invalid phone number {phone}") continue # Check DND list dnd_entry = db.query(DNDList).filter( DNDList.user_id == user_id, DNDList.phone_e164 == normalized_phone ).first() if dnd_entry: summary.skipped += 1 continue # Check if exists existing = db.query(Contact).filter( Contact.user_id == user_id, Contact.phone_e164 == normalized_phone ).first() first_name = str(row.get('first_name', '')).strip() or None last_name = str(row.get('last_name', '')).strip() or None email = str(row.get('email', '')).strip() or None opted_in_raw = str(row.get('opted_in', 'false')).lower() opted_in = opted_in_raw in ['true', '1', 'yes', 'y'] if existing: # Update existing if first_name: existing.first_name = first_name if last_name: existing.last_name = last_name if email: existing.email = email existing.opted_in = opted_in summary.updated += 1 else: # Create new contact = Contact( user_id=user_id, phone_e164=normalized_phone, first_name=first_name, last_name=last_name, email=email, opted_in=opted_in, source=source ) db.add(contact) summary.created += 1 except Exception as e: summary.invalid += 1 summary.errors.append(f"Row {idx+1}: {str(e)}") db.commit() return summary @router.post("/excel", response_model=ImportSummary) async def import_excel( file: UploadFile = File(...), current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): if not file.filename.endswith(('.xlsx', '.xls')): raise HTTPException(status_code=400, detail="File must be Excel format (.xlsx or .xls)") try: contents = await file.read() # Read phone column as string to preserve '+' sign df = pd.read_excel(io.BytesIO(contents), dtype={'phone': str}) return process_import_file(df, current_user.id, "excel", db) except Exception as e: raise HTTPException(status_code=400, detail=f"Error processing file: {str(e)}") @router.post("/csv", response_model=ImportSummary) async def import_csv( file: UploadFile = File(...), current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): if not file.filename.endswith('.csv'): raise HTTPException(status_code=400, detail="File must be CSV format") try: contents = await file.read() # Read phone column as string to preserve '+' sign df = pd.read_csv(io.StringIO(contents.decode('utf-8')), dtype={'phone': str}) return process_import_file(df, current_user.id, "csv", db) except Exception as e: raise HTTPException(status_code=400, detail=f"Error processing file: {str(e)}")