"""Portfolio model for tracking owned domains.""" from datetime import datetime from typing import Optional from sqlalchemy import String, DateTime, Float, Integer, Text, ForeignKey, Boolean from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class PortfolioDomain(Base): """ Portfolio Domain model for tracking domains that users own. Allows users to track their domain investments, values, and ROI. """ __tablename__ = "portfolio_domains" id: Mapped[int] = mapped_column(primary_key=True, index=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True, nullable=False) # Domain info domain: Mapped[str] = mapped_column(String(255), nullable=False) # Purchase info purchase_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) purchase_price: Mapped[Optional[float]] = mapped_column(Float, nullable=True) purchase_registrar: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # Current status registrar: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) renewal_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) renewal_cost: Mapped[Optional[float]] = mapped_column(Float, nullable=True) auto_renew: Mapped[bool] = mapped_column(Boolean, default=True) # Valuation estimated_value: Mapped[Optional[float]] = mapped_column(Float, nullable=True) value_updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) # Sale info (if sold) is_sold: Mapped[bool] = mapped_column(Boolean, default=False) sale_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) sale_price: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # Status status: Mapped[str] = mapped_column(String(50), default="active") # active, expired, sold, parked # DNS Verification (required for Yield and For Sale) is_dns_verified: Mapped[bool] = mapped_column(Boolean, default=False) verification_status: Mapped[str] = mapped_column(String(50), default="unverified") # unverified, pending, verified, failed verification_code: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) verification_started_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) verified_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) # Notes notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True) tags: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) # Comma-separated # Timestamps created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships user: Mapped["User"] = relationship("User", back_populates="portfolio_domains") valuations: Mapped[list["DomainValuation"]] = relationship( "DomainValuation", back_populates="portfolio_domain", cascade="all, delete-orphan" ) def __repr__(self) -> str: return f"" @property def roi(self) -> Optional[float]: """Calculate ROI percentage.""" if not self.purchase_price or self.purchase_price == 0: return None if self.is_sold and self.sale_price: return ((self.sale_price - self.purchase_price) / self.purchase_price) * 100 elif self.estimated_value: return ((self.estimated_value - self.purchase_price) / self.purchase_price) * 100 return None @property def total_cost(self) -> float: """Calculate total cost including renewals.""" cost = self.purchase_price or 0 # Add renewal costs if we had them tracked return cost class DomainValuation(Base): """ Domain valuation history. Stores historical valuations for domains to track value changes over time. """ __tablename__ = "domain_valuations" id: Mapped[int] = mapped_column(primary_key=True, index=True) domain: Mapped[str] = mapped_column(String(255), index=True, nullable=False) portfolio_domain_id: Mapped[Optional[int]] = mapped_column( ForeignKey("portfolio_domains.id"), nullable=True ) # Valuation breakdown estimated_value: Mapped[float] = mapped_column(Float, nullable=False) # Factors length_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 tld_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 keyword_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 brandability_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 # SEO metrics moz_da: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) moz_pa: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) backlinks: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # Source source: Mapped[str] = mapped_column(String(50), default="internal") # internal, estibot, godaddy # Timestamp created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) # Relationship portfolio_domain: Mapped[Optional["PortfolioDomain"]] = relationship( "PortfolioDomain", back_populates="valuations" ) def __repr__(self) -> str: return f""