from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Boolean, ForeignKey from sqlalchemy.sql import func from pydantic import BaseModel from typing import Optional, Any from datetime import datetime from database import Base # ── User model ────────────────────────────────────────────────────────────── class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True, nullable=False) full_name = Column(String, nullable=True) email = Column(String, unique=True, nullable=True) hashed_password = Column(String, nullable=False) is_admin = Column(Boolean, default=False) is_active = Column(Boolean, default=True) created_at = Column(DateTime, server_default=func.now()) class UserResponse(BaseModel): id: int username: str full_name: Optional[str] = None email: Optional[str] = None is_admin: bool is_active: bool created_at: Optional[datetime] = None class Config: from_attributes = True class UserCreate(BaseModel): username: str full_name: Optional[str] = None email: Optional[str] = None password: str is_admin: bool = False class UserUpdate(BaseModel): full_name: Optional[str] = None email: Optional[str] = None is_admin: Optional[bool] = None is_active: Optional[bool] = None class PasswordReset(BaseModel): new_password: str # ── Project model ──────────────────────────────────────────────────────────── class Project(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False, index=True) description = Column(Text, nullable=True) color = Column(String, default="#0073ea") created_at = Column(DateTime, server_default=func.now()) class ProjectCreate(BaseModel): name: str description: Optional[str] = None color: Optional[str] = "#0073ea" class ProjectUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None color: Optional[str] = None class ProjectResponse(BaseModel): id: int name: str description: Optional[str] = None color: str created_at: Optional[datetime] = None class Config: from_attributes = True # SQLAlchemy ORM Model class Lead(Base): __tablename__ = "leads" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True) company_name = Column(String, index=True, nullable=True) contact_name = Column(String, nullable=True) email = Column(String, index=True, nullable=True) phone = Column(String, nullable=True) website = Column(String, nullable=True) industry = Column(String, nullable=True) company_size = Column(String, nullable=True) location = Column(String, nullable=True) linkedin_url = Column(String, nullable=True) source = Column(String, nullable=True) # csv, excel, linkedin, web_scrape, manual status = Column(String, default="new") # new, contacted, qualified, converted, archived notes = Column(Text, nullable=True) custom_fields = Column(JSON, nullable=True) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class ScrapingJob(Base): __tablename__ = "scraping_jobs" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id", ondelete="SET NULL"), nullable=True, index=True) url = Column(String) status = Column(String, default="pending") # pending, running, completed, failed leads_found = Column(Integer, default=0) error = Column(Text, nullable=True) config = Column(JSON, nullable=True) created_at = Column(DateTime, server_default=func.now()) completed_at = Column(DateTime, nullable=True) # Pydantic Schemas class LeadBase(BaseModel): project_id: Optional[int] = None company_name: Optional[str] = None contact_name: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None website: Optional[str] = None industry: Optional[str] = None company_size: Optional[str] = None location: Optional[str] = None linkedin_url: Optional[str] = None source: Optional[str] = None status: Optional[str] = "new" notes: Optional[str] = None custom_fields: Optional[dict] = None class LeadCreate(LeadBase): pass class LeadUpdate(LeadBase): pass class LeadResponse(LeadBase): id: int project_id: Optional[int] = None created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: from_attributes = True class LeadListResponse(BaseModel): total: int page: int page_size: int leads: list[LeadResponse] class ScrapingConfig(BaseModel): url: str extract_emails: bool = True extract_phones: bool = True extract_company_name: bool = True custom_selectors: Optional[dict] = None follow_links: bool = False max_pages: int = 1 class ScrapingJobResponse(BaseModel): id: int url: str status: str leads_found: int error: Optional[str] = None created_at: Optional[datetime] = None completed_at: Optional[datetime] = None class Config: from_attributes = True class AnalyticsResponse(BaseModel): total_leads: int leads_by_status: dict[str, int] leads_by_source: dict[str, int] leads_by_industry: dict[str, int] recent_leads: list[LeadResponse] conversion_rate: float