## Watchlist & Monitoring - ✅ Automatic domain monitoring based on subscription tier - ✅ Email alerts when domains become available - ✅ Health checks (DNS/HTTP/SSL) with caching - ✅ Expiry warnings for domains <30 days - ✅ Weekly digest emails - ✅ Instant alert toggle (optimistic UI updates) - ✅ Redesigned health check overlays with full details - 🔒 'Not public' display for .ch/.de domains without public expiry ## Portfolio Management (NEW) - ✅ Track owned domains with purchase price & date - ✅ ROI calculation (unrealized & realized) - ✅ Domain valuation with auto-refresh - ✅ Renewal date tracking - ✅ Sale recording with profit calculation - ✅ List domains for sale directly from portfolio - ✅ Full portfolio summary dashboard ## Listings / For Sale - ✅ Renamed from 'Portfolio' to 'For Sale' - ✅ Fixed listing limits: Scout=0, Trader=5, Tycoon=50 - ✅ Featured badge for Tycoon listings - ✅ Inquiries modal for sellers - ✅ Email notifications when buyer inquires - ✅ Inquiries column in listings table ## Scrapers & Data - ✅ Added 4 new registrar scrapers (Namecheap, Cloudflare, GoDaddy, Dynadot) - ✅ Increased scraping frequency to 2x daily (03:00 & 15:00 UTC) - ✅ Real historical data from database - ✅ Fixed RDAP/WHOIS for .ch/.de domains - ✅ Enhanced SSL certificate parsing ## Scheduler Jobs - ✅ Tiered domain checks (Scout=daily, Trader=hourly, Tycoon=10min) - ✅ Daily health checks (06:00 UTC) - ✅ Weekly expiry warnings (Mon 08:00 UTC) - ✅ Weekly digest emails (Sun 10:00 UTC) - ✅ Auction cleanup every 15 minutes ## UI/UX Improvements - ✅ Removed 'Back' buttons from Intel pages - ✅ Redesigned Radar page to match Market/Intel design - ✅ Less prominent check frequency footer - ✅ Consistent StatCard components across all pages - ✅ Ambient background glows - ✅ Better error handling ## Documentation - ✅ Updated README with monitoring section - ✅ Added env.example with all required variables - ✅ Updated Memory Bank (activeContext.md) - ✅ SMTP configuration requirements documented
103 lines
2.6 KiB
Python
103 lines
2.6 KiB
Python
"""Domain schemas."""
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
from app.models.domain import DomainStatus
|
|
|
|
|
|
class DomainCreate(BaseModel):
|
|
"""Schema for adding a domain to monitoring list."""
|
|
name: str = Field(..., min_length=4, max_length=255)
|
|
notify_on_available: bool = True
|
|
|
|
@field_validator('name')
|
|
@classmethod
|
|
def validate_domain_name(cls, v: str) -> str:
|
|
"""Validate and normalize domain name."""
|
|
v = v.lower().strip()
|
|
if v.startswith('http://'):
|
|
v = v[7:]
|
|
elif v.startswith('https://'):
|
|
v = v[8:]
|
|
if v.startswith('www.'):
|
|
v = v[4:]
|
|
v = v.split('/')[0]
|
|
|
|
if '.' not in v:
|
|
raise ValueError('Domain must include TLD (e.g., .com)')
|
|
|
|
return v
|
|
|
|
|
|
class DomainResponse(BaseModel):
|
|
"""Schema for domain response."""
|
|
id: int
|
|
name: str
|
|
status: DomainStatus
|
|
is_available: bool
|
|
registrar: Optional[str]
|
|
expiration_date: Optional[datetime]
|
|
notify_on_available: bool
|
|
created_at: datetime
|
|
last_checked: Optional[datetime]
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class DomainCheckRequest(BaseModel):
|
|
"""Schema for quick domain availability check."""
|
|
domain: str = Field(..., min_length=4, max_length=255)
|
|
quick: bool = False # If True, only DNS check (faster)
|
|
|
|
@field_validator('domain')
|
|
@classmethod
|
|
def validate_domain(cls, v: str) -> str:
|
|
"""Validate and normalize domain."""
|
|
v = v.lower().strip()
|
|
if v.startswith('http://'):
|
|
v = v[7:]
|
|
elif v.startswith('https://'):
|
|
v = v[8:]
|
|
if v.startswith('www.'):
|
|
v = v[4:]
|
|
v = v.split('/')[0]
|
|
return v
|
|
|
|
|
|
class DomainCheckResponse(BaseModel):
|
|
"""Schema for domain check response."""
|
|
domain: str
|
|
status: str
|
|
is_available: bool
|
|
registrar: Optional[str] = None
|
|
expiration_date: Optional[datetime] = None
|
|
creation_date: Optional[datetime] = None
|
|
name_servers: Optional[List[str]] = None
|
|
error_message: Optional[str] = None
|
|
checked_at: datetime
|
|
|
|
|
|
class DomainListResponse(BaseModel):
|
|
"""Schema for paginated domain list."""
|
|
domains: List[DomainResponse]
|
|
total: int
|
|
page: int
|
|
per_page: int
|
|
pages: int
|
|
|
|
|
|
class ExpiryUpdate(BaseModel):
|
|
"""Schema for manually setting domain expiration date."""
|
|
expiration_date: Optional[datetime] = None
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"expiration_date": "2025-12-31T00:00:00Z"
|
|
}
|
|
}
|
|
|