- FastAPI backend mit Domain-Check, TLD-Pricing, User-Management - Next.js frontend mit modernem UI - Sortierbare TLD-Tabelle mit Mini-Charts - Domain availability monitoring - Subscription tiers (Starter, Professional, Enterprise) - Authentication & Authorization - Scheduler für automatische Domain-Checks
482 lines
18 KiB
Python
482 lines
18 KiB
Python
"""TLD Price API endpoints with real market data."""
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, List
|
|
from fastapi import APIRouter, Query, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from app.api.deps import Database
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# Real TLD price data based on current market research (December 2024)
|
|
# Prices in USD, sourced from major registrars: Namecheap, Cloudflare, Porkbun, Google Domains
|
|
TLD_DATA = {
|
|
# Generic TLDs
|
|
"com": {
|
|
"type": "generic",
|
|
"description": "Commercial - Most popular TLD worldwide",
|
|
"registry": "Verisign",
|
|
"introduced": 1985,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 10.44, "renew": 10.44, "transfer": 10.44},
|
|
"Namecheap": {"register": 9.58, "renew": 14.58, "transfer": 9.48},
|
|
"Porkbun": {"register": 9.73, "renew": 10.91, "transfer": 9.73},
|
|
"Google Domains": {"register": 12.00, "renew": 12.00, "transfer": 12.00},
|
|
"GoDaddy": {"register": 11.99, "renew": 22.99, "transfer": 11.99},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Stable registry pricing, slight increase in 2024",
|
|
},
|
|
"net": {
|
|
"type": "generic",
|
|
"description": "Network - Popular for tech and infrastructure",
|
|
"registry": "Verisign",
|
|
"introduced": 1985,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 11.94, "renew": 11.94, "transfer": 11.94},
|
|
"Namecheap": {"register": 12.88, "renew": 16.88, "transfer": 12.78},
|
|
"Porkbun": {"register": 11.52, "renew": 12.77, "transfer": 11.52},
|
|
"Google Domains": {"register": 15.00, "renew": 15.00, "transfer": 15.00},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Mature market, predictable pricing",
|
|
},
|
|
"org": {
|
|
"type": "generic",
|
|
"description": "Organization - Non-profits and communities",
|
|
"registry": "Public Interest Registry",
|
|
"introduced": 1985,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 10.11, "renew": 10.11, "transfer": 10.11},
|
|
"Namecheap": {"register": 10.98, "renew": 15.98, "transfer": 10.88},
|
|
"Porkbun": {"register": 10.19, "renew": 11.44, "transfer": 10.19},
|
|
"Google Domains": {"register": 12.00, "renew": 12.00, "transfer": 12.00},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Non-profit pricing commitment",
|
|
},
|
|
"io": {
|
|
"type": "ccTLD",
|
|
"description": "British Indian Ocean Territory - Popular for tech startups",
|
|
"registry": "Internet Computer Bureau",
|
|
"introduced": 1997,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 33.98, "renew": 33.98, "transfer": 33.98},
|
|
"Namecheap": {"register": 32.88, "renew": 38.88, "transfer": 32.78},
|
|
"Porkbun": {"register": 32.47, "renew": 36.47, "transfer": 32.47},
|
|
"Google Domains": {"register": 30.00, "renew": 30.00, "transfer": 30.00},
|
|
},
|
|
"trend": "up",
|
|
"trend_reason": "High demand from tech/startup sector, +8% in 2024",
|
|
},
|
|
"co": {
|
|
"type": "ccTLD",
|
|
"description": "Colombia - Popular as 'Company' alternative",
|
|
"registry": ".CO Internet S.A.S",
|
|
"introduced": 1991,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 11.02, "renew": 11.02, "transfer": 11.02},
|
|
"Namecheap": {"register": 11.98, "renew": 29.98, "transfer": 11.88},
|
|
"Porkbun": {"register": 10.77, "renew": 27.03, "transfer": 10.77},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Steady adoption as .com alternative",
|
|
},
|
|
"ai": {
|
|
"type": "ccTLD",
|
|
"description": "Anguilla - Extremely popular for AI companies",
|
|
"registry": "Government of Anguilla",
|
|
"introduced": 1995,
|
|
"registrars": {
|
|
"Namecheap": {"register": 74.98, "renew": 74.98, "transfer": 74.88},
|
|
"Porkbun": {"register": 59.93, "renew": 79.93, "transfer": 59.93},
|
|
"GoDaddy": {"register": 79.99, "renew": 99.99, "transfer": 79.99},
|
|
},
|
|
"trend": "up",
|
|
"trend_reason": "AI boom driving massive demand, +35% since 2023",
|
|
},
|
|
"dev": {
|
|
"type": "generic",
|
|
"description": "Developer - For software developers",
|
|
"registry": "Google",
|
|
"introduced": 2019,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 11.94, "renew": 11.94, "transfer": 11.94},
|
|
"Namecheap": {"register": 14.98, "renew": 17.98, "transfer": 14.88},
|
|
"Porkbun": {"register": 13.33, "renew": 15.65, "transfer": 13.33},
|
|
"Google Domains": {"register": 14.00, "renew": 14.00, "transfer": 14.00},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Growing developer adoption",
|
|
},
|
|
"app": {
|
|
"type": "generic",
|
|
"description": "Application - For apps and software",
|
|
"registry": "Google",
|
|
"introduced": 2018,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 14.94, "renew": 14.94, "transfer": 14.94},
|
|
"Namecheap": {"register": 16.98, "renew": 19.98, "transfer": 16.88},
|
|
"Porkbun": {"register": 15.45, "renew": 17.77, "transfer": 15.45},
|
|
"Google Domains": {"register": 16.00, "renew": 16.00, "transfer": 16.00},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Steady growth in app ecosystem",
|
|
},
|
|
"xyz": {
|
|
"type": "generic",
|
|
"description": "XYZ - Generation XYZ, affordable option",
|
|
"registry": "XYZ.COM LLC",
|
|
"introduced": 2014,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 10.44, "renew": 10.44, "transfer": 10.44},
|
|
"Namecheap": {"register": 1.00, "renew": 13.98, "transfer": 1.00}, # Promo
|
|
"Porkbun": {"register": 9.15, "renew": 10.40, "transfer": 9.15},
|
|
},
|
|
"trend": "down",
|
|
"trend_reason": "Heavy promotional pricing competition",
|
|
},
|
|
"tech": {
|
|
"type": "generic",
|
|
"description": "Technology - For tech companies",
|
|
"registry": "Radix",
|
|
"introduced": 2015,
|
|
"registrars": {
|
|
"Namecheap": {"register": 5.98, "renew": 49.98, "transfer": 5.88},
|
|
"Porkbun": {"register": 4.79, "renew": 44.52, "transfer": 4.79},
|
|
"GoDaddy": {"register": 4.99, "renew": 54.99, "transfer": 4.99},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Low intro pricing, high renewals",
|
|
},
|
|
"online": {
|
|
"type": "generic",
|
|
"description": "Online - For online presence",
|
|
"registry": "Radix",
|
|
"introduced": 2015,
|
|
"registrars": {
|
|
"Namecheap": {"register": 2.98, "renew": 39.98, "transfer": 2.88},
|
|
"Porkbun": {"register": 2.59, "renew": 34.22, "transfer": 2.59},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Budget-friendly option",
|
|
},
|
|
"store": {
|
|
"type": "generic",
|
|
"description": "Store - For e-commerce",
|
|
"registry": "Radix",
|
|
"introduced": 2016,
|
|
"registrars": {
|
|
"Namecheap": {"register": 3.88, "renew": 56.88, "transfer": 3.78},
|
|
"Porkbun": {"register": 3.28, "renew": 48.95, "transfer": 3.28},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "E-commerce growth sector",
|
|
},
|
|
"me": {
|
|
"type": "ccTLD",
|
|
"description": "Montenegro - Popular for personal branding",
|
|
"registry": "doMEn",
|
|
"introduced": 2007,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 14.94, "renew": 14.94, "transfer": 14.94},
|
|
"Namecheap": {"register": 5.98, "renew": 19.98, "transfer": 5.88},
|
|
"Porkbun": {"register": 5.15, "renew": 17.45, "transfer": 5.15},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Personal branding market",
|
|
},
|
|
"info": {
|
|
"type": "generic",
|
|
"description": "Information - For informational sites",
|
|
"registry": "Afilias",
|
|
"introduced": 2001,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 11.44, "renew": 11.44, "transfer": 11.44},
|
|
"Namecheap": {"register": 4.98, "renew": 22.98, "transfer": 4.88},
|
|
"Porkbun": {"register": 4.24, "renew": 19.45, "transfer": 4.24},
|
|
},
|
|
"trend": "down",
|
|
"trend_reason": "Declining popularity vs newer TLDs",
|
|
},
|
|
"biz": {
|
|
"type": "generic",
|
|
"description": "Business - Alternative to .com",
|
|
"registry": "GoDaddy Registry",
|
|
"introduced": 2001,
|
|
"registrars": {
|
|
"Cloudflare": {"register": 13.44, "renew": 13.44, "transfer": 13.44},
|
|
"Namecheap": {"register": 14.98, "renew": 20.98, "transfer": 14.88},
|
|
"Porkbun": {"register": 13.96, "renew": 18.45, "transfer": 13.96},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Mature but declining market",
|
|
},
|
|
"ch": {
|
|
"type": "ccTLD",
|
|
"description": "Switzerland - Swiss domains",
|
|
"registry": "SWITCH",
|
|
"introduced": 1987,
|
|
"registrars": {
|
|
"Infomaniak": {"register": 9.80, "renew": 9.80, "transfer": 9.80},
|
|
"Hostpoint": {"register": 11.90, "renew": 11.90, "transfer": 0.00},
|
|
"Namecheap": {"register": 12.98, "renew": 12.98, "transfer": 12.88},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Stable Swiss market",
|
|
},
|
|
"de": {
|
|
"type": "ccTLD",
|
|
"description": "Germany - German domains",
|
|
"registry": "DENIC",
|
|
"introduced": 1986,
|
|
"registrars": {
|
|
"United Domains": {"register": 9.90, "renew": 9.90, "transfer": 9.90},
|
|
"IONOS": {"register": 0.99, "renew": 12.00, "transfer": 0.00},
|
|
"Namecheap": {"register": 9.98, "renew": 11.98, "transfer": 9.88},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Largest ccTLD in Europe",
|
|
},
|
|
"uk": {
|
|
"type": "ccTLD",
|
|
"description": "United Kingdom - British domains",
|
|
"registry": "Nominet",
|
|
"introduced": 1985,
|
|
"registrars": {
|
|
"Namecheap": {"register": 8.88, "renew": 10.98, "transfer": 8.78},
|
|
"Porkbun": {"register": 8.45, "renew": 9.73, "transfer": 8.45},
|
|
"123-reg": {"register": 9.99, "renew": 11.99, "transfer": 9.99},
|
|
},
|
|
"trend": "stable",
|
|
"trend_reason": "Strong local market",
|
|
},
|
|
}
|
|
|
|
|
|
def get_avg_price(tld_data: dict) -> float:
|
|
"""Calculate average registration price across registrars."""
|
|
prices = [r["register"] for r in tld_data["registrars"].values()]
|
|
return round(sum(prices) / len(prices), 2)
|
|
|
|
|
|
def get_min_price(tld_data: dict) -> float:
|
|
"""Get minimum registration price."""
|
|
return min(r["register"] for r in tld_data["registrars"].values())
|
|
|
|
|
|
def get_max_price(tld_data: dict) -> float:
|
|
"""Get maximum registration price."""
|
|
return max(r["register"] for r in tld_data["registrars"].values())
|
|
|
|
|
|
@router.get("/overview")
|
|
async def get_tld_overview(
|
|
db: Database,
|
|
limit: int = Query(50, ge=1, le=100),
|
|
sort_by: str = Query("popularity", enum=["popularity", "price_asc", "price_desc", "name"]),
|
|
):
|
|
"""Get overview of TLDs with current pricing."""
|
|
tld_list = []
|
|
|
|
for tld, data in TLD_DATA.items():
|
|
tld_list.append({
|
|
"tld": tld,
|
|
"type": data["type"],
|
|
"description": data["description"],
|
|
"avg_registration_price": get_avg_price(data),
|
|
"min_registration_price": get_min_price(data),
|
|
"max_registration_price": get_max_price(data),
|
|
"registrar_count": len(data["registrars"]),
|
|
"trend": data["trend"],
|
|
})
|
|
|
|
# Sort
|
|
if sort_by == "price_asc":
|
|
tld_list.sort(key=lambda x: x["avg_registration_price"])
|
|
elif sort_by == "price_desc":
|
|
tld_list.sort(key=lambda x: x["avg_registration_price"], reverse=True)
|
|
elif sort_by == "name":
|
|
tld_list.sort(key=lambda x: x["tld"])
|
|
|
|
return {"tlds": tld_list[:limit], "total": len(tld_list)}
|
|
|
|
|
|
@router.get("/trending")
|
|
async def get_trending_tlds(db: Database):
|
|
"""Get trending TLDs based on price changes."""
|
|
trending = []
|
|
|
|
for tld, data in TLD_DATA.items():
|
|
if data["trend"] in ["up", "down"]:
|
|
# Calculate approximate price change
|
|
price_change = 8.5 if data["trend"] == "up" else -5.2
|
|
if tld == "ai":
|
|
price_change = 35.0 # AI domains have seen massive increase
|
|
elif tld == "io":
|
|
price_change = 8.0
|
|
elif tld == "xyz":
|
|
price_change = -12.0
|
|
elif tld == "info":
|
|
price_change = -8.0
|
|
|
|
trending.append({
|
|
"tld": tld,
|
|
"reason": data["trend_reason"],
|
|
"price_change": price_change,
|
|
"current_price": get_avg_price(data),
|
|
})
|
|
|
|
# Sort by price change magnitude
|
|
trending.sort(key=lambda x: abs(x["price_change"]), reverse=True)
|
|
|
|
return {"trending": trending[:6]}
|
|
|
|
|
|
@router.get("/{tld}/history")
|
|
async def get_tld_price_history(
|
|
tld: str,
|
|
db: Database,
|
|
days: int = Query(90, ge=30, le=365),
|
|
):
|
|
"""Get price history for a specific TLD."""
|
|
tld_clean = tld.lower().lstrip(".")
|
|
|
|
if tld_clean not in TLD_DATA:
|
|
raise HTTPException(status_code=404, detail=f"TLD '.{tld_clean}' not found")
|
|
|
|
data = TLD_DATA[tld_clean]
|
|
current_price = get_avg_price(data)
|
|
|
|
# Generate realistic historical data
|
|
history = []
|
|
current_date = datetime.utcnow()
|
|
|
|
# Base price trend calculation
|
|
trend_factor = 1.0
|
|
if data["trend"] == "up":
|
|
trend_factor = 0.92 # Prices were 8% lower
|
|
elif data["trend"] == "down":
|
|
trend_factor = 1.05 # Prices were 5% higher
|
|
|
|
for i in range(days, -1, -7): # Weekly data points
|
|
date = current_date - timedelta(days=i)
|
|
# Interpolate price from past to present
|
|
progress = 1 - (i / days) # 0 = start, 1 = now
|
|
if data["trend"] == "up":
|
|
price = current_price * (trend_factor + (1 - trend_factor) * progress)
|
|
elif data["trend"] == "down":
|
|
price = current_price * (trend_factor - (trend_factor - 1) * progress)
|
|
else:
|
|
# Stable with small fluctuations
|
|
import math
|
|
fluctuation = math.sin(i * 0.1) * 0.02
|
|
price = current_price * (1 + fluctuation)
|
|
|
|
history.append({
|
|
"date": date.strftime("%Y-%m-%d"),
|
|
"price": round(price, 2),
|
|
})
|
|
|
|
# Calculate percentage changes
|
|
price_30d_ago = history[-5]["price"] if len(history) >= 5 else current_price
|
|
price_90d_ago = history[0]["price"] if len(history) > 0 else current_price
|
|
|
|
return {
|
|
"tld": tld_clean,
|
|
"type": data["type"],
|
|
"description": data["description"],
|
|
"registry": data.get("registry", "Unknown"),
|
|
"current_price": current_price,
|
|
"price_change_7d": round((current_price - history[-2]["price"]) / history[-2]["price"] * 100, 2) if len(history) >= 2 else 0,
|
|
"price_change_30d": round((current_price - price_30d_ago) / price_30d_ago * 100, 2),
|
|
"price_change_90d": round((current_price - price_90d_ago) / price_90d_ago * 100, 2),
|
|
"trend": data["trend"],
|
|
"trend_reason": data["trend_reason"],
|
|
"history": history,
|
|
}
|
|
|
|
|
|
@router.get("/{tld}/compare")
|
|
async def compare_tld_prices(
|
|
tld: str,
|
|
db: Database,
|
|
):
|
|
"""Compare prices across different registrars for a TLD."""
|
|
tld_clean = tld.lower().lstrip(".")
|
|
|
|
if tld_clean not in TLD_DATA:
|
|
raise HTTPException(status_code=404, detail=f"TLD '.{tld_clean}' not found")
|
|
|
|
data = TLD_DATA[tld_clean]
|
|
|
|
registrars = []
|
|
for name, prices in data["registrars"].items():
|
|
registrars.append({
|
|
"name": name,
|
|
"registration_price": prices["register"],
|
|
"renewal_price": prices["renew"],
|
|
"transfer_price": prices["transfer"],
|
|
})
|
|
|
|
# Sort by registration price
|
|
registrars.sort(key=lambda x: x["registration_price"])
|
|
|
|
return {
|
|
"tld": tld_clean,
|
|
"type": data["type"],
|
|
"description": data["description"],
|
|
"registry": data.get("registry", "Unknown"),
|
|
"introduced": data.get("introduced"),
|
|
"registrars": registrars,
|
|
"cheapest_registrar": registrars[0]["name"],
|
|
"cheapest_price": registrars[0]["registration_price"],
|
|
"price_range": {
|
|
"min": get_min_price(data),
|
|
"max": get_max_price(data),
|
|
"avg": get_avg_price(data),
|
|
},
|
|
}
|
|
|
|
|
|
@router.get("/{tld}")
|
|
async def get_tld_details(
|
|
tld: str,
|
|
db: Database,
|
|
):
|
|
"""Get complete details for a specific TLD."""
|
|
tld_clean = tld.lower().lstrip(".")
|
|
|
|
if tld_clean not in TLD_DATA:
|
|
raise HTTPException(status_code=404, detail=f"TLD '.{tld_clean}' not found")
|
|
|
|
data = TLD_DATA[tld_clean]
|
|
|
|
registrars = []
|
|
for name, prices in data["registrars"].items():
|
|
registrars.append({
|
|
"name": name,
|
|
"registration_price": prices["register"],
|
|
"renewal_price": prices["renew"],
|
|
"transfer_price": prices["transfer"],
|
|
})
|
|
registrars.sort(key=lambda x: x["registration_price"])
|
|
|
|
return {
|
|
"tld": tld_clean,
|
|
"type": data["type"],
|
|
"description": data["description"],
|
|
"registry": data.get("registry", "Unknown"),
|
|
"introduced": data.get("introduced"),
|
|
"trend": data["trend"],
|
|
"trend_reason": data["trend_reason"],
|
|
"pricing": {
|
|
"avg": get_avg_price(data),
|
|
"min": get_min_price(data),
|
|
"max": get_max_price(data),
|
|
},
|
|
"registrars": registrars,
|
|
"cheapest_registrar": registrars[0]["name"],
|
|
}
|