yves.gugger dc77b2110a feat: Complete Watchlist monitoring, Portfolio tracking & Listings marketplace
## 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
2025-12-11 16:57:28 +01:00

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"
}
}