yves.gugger 88eca582e5 feat: Remove ALL mock data - real scraped data only
MOCK DATA REMOVED:
- Removed ALL hardcoded auction data from auctions.py
- Now uses real-time scraping from ExpiredDomains.net
- Database stores scraped auctions (domain_auctions table)
- Scraping runs hourly via scheduler (:30 each hour)

AUCTION SCRAPER SERVICE:
- Web scraping from ExpiredDomains.net (aggregator)
- Rate limiting per platform (10 req/min)
- Database caching to minimize requests
- Cleanup of ended auctions (auto-deactivate)
- Scrape logging for monitoring

STRIPE INTEGRATION:
- Full payment flow: Checkout → Webhook → Subscription update
- Customer Portal for managing subscriptions
- Price IDs configurable via env vars
- Handles: checkout.completed, subscription.updated/deleted, payment.failed

EMAIL SERVICE (SMTP):
- Beautiful HTML email templates with pounce branding
- Domain available alerts
- Price change notifications
- Subscription confirmations
- Weekly digest emails
- Configurable via SMTP_* env vars

NEW SUBSCRIPTION TIERS:
- Scout (Free): 5 domains, daily checks
- Trader (€19/mo): 50 domains, hourly, portfolio, valuation
- Tycoon (€49/mo): 500+ domains, realtime, API, bulk tools

DATABASE CHANGES:
- domain_auctions table for scraped data
- auction_scrape_logs for monitoring
- stripe_customer_id on users
- stripe_subscription_id on subscriptions
- portfolio_domain relationships fixed

ENV VARS ADDED:
- STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
- STRIPE_PRICE_TRADER, STRIPE_PRICE_TYCOON
- SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD
- SMTP_FROM_EMAIL, SMTP_FROM_NAME
2025-12-08 14:08:52 +01:00

49 lines
1.7 KiB
Python

"""User model."""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Boolean, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class User(Base):
"""User model for authentication and domain tracking."""
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
# Profile
name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
# Stripe
stripe_customer_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
# Status
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_verified: Mapped[bool] = mapped_column(Boolean, default=False)
# 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
domains: Mapped[List["Domain"]] = relationship(
"Domain", back_populates="user", cascade="all, delete-orphan"
)
subscription: Mapped["Subscription"] = relationship(
"Subscription", back_populates="user", uselist=False, cascade="all, delete-orphan"
)
portfolio_domains: Mapped[List["PortfolioDomain"]] = relationship(
"PortfolioDomain", back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"<User {self.email}>"