Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
149 lines
4.6 KiB
Python
149 lines
4.6 KiB
Python
"""
|
|
Drops API - Zone File Analysis Endpoints
|
|
=========================================
|
|
API endpoints for accessing freshly dropped .ch and .li domains.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_db
|
|
from app.api.deps import get_current_user
|
|
from app.services.zone_file import (
|
|
ZoneFileService,
|
|
get_dropped_domains,
|
|
get_zone_stats,
|
|
)
|
|
|
|
router = APIRouter(prefix="/drops", tags=["drops"])
|
|
|
|
|
|
# ============================================================================
|
|
# PUBLIC ENDPOINTS (for stats)
|
|
# ============================================================================
|
|
|
|
@router.get("/stats")
|
|
async def api_get_zone_stats(
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Get zone file statistics.
|
|
Returns domain counts and last sync times for .ch and .li.
|
|
"""
|
|
try:
|
|
stats = await get_zone_stats(db)
|
|
return stats
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
# ============================================================================
|
|
# AUTHENTICATED ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.get("")
|
|
async def api_get_drops(
|
|
tld: Optional[str] = Query(None, description="Filter by TLD (ch or li)"),
|
|
days: int = Query(7, ge=1, le=30, description="Days to look back"),
|
|
min_length: Optional[int] = Query(None, ge=1, le=63, description="Minimum domain length"),
|
|
max_length: Optional[int] = Query(None, ge=1, le=63, description="Maximum domain length"),
|
|
exclude_numeric: bool = Query(False, description="Exclude numeric-only domains"),
|
|
exclude_hyphen: bool = Query(False, description="Exclude domains with hyphens"),
|
|
keyword: Optional[str] = Query(None, description="Search keyword"),
|
|
limit: int = Query(50, ge=1, le=200, description="Results per page"),
|
|
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Get recently dropped domains from .ch and .li zone files.
|
|
|
|
Domains are detected by comparing daily zone file snapshots.
|
|
Only available for authenticated users.
|
|
"""
|
|
if tld and tld not in ["ch", "li"]:
|
|
raise HTTPException(status_code=400, detail="TLD must be 'ch' or 'li'")
|
|
|
|
try:
|
|
result = await get_dropped_domains(
|
|
db=db,
|
|
tld=tld,
|
|
days=days,
|
|
min_length=min_length,
|
|
max_length=max_length,
|
|
exclude_numeric=exclude_numeric,
|
|
exclude_hyphen=exclude_hyphen,
|
|
keyword=keyword,
|
|
limit=limit,
|
|
offset=offset
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/sync/{tld}")
|
|
async def api_trigger_sync(
|
|
tld: str,
|
|
background_tasks: BackgroundTasks,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Trigger a manual zone file sync for a specific TLD.
|
|
Only available for admin users.
|
|
|
|
This is normally run automatically by the scheduler.
|
|
"""
|
|
# Check if user is admin
|
|
if not getattr(current_user, 'is_admin', False):
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
|
|
if tld not in ["ch", "li"]:
|
|
raise HTTPException(status_code=400, detail="TLD must be 'ch' or 'li'")
|
|
|
|
# Run sync in background
|
|
service = ZoneFileService()
|
|
|
|
async def run_sync():
|
|
async for session in get_db():
|
|
try:
|
|
await service.run_daily_sync(session, tld)
|
|
except Exception as e:
|
|
print(f"Zone sync failed for .{tld}: {e}")
|
|
break
|
|
|
|
background_tasks.add_task(run_sync)
|
|
|
|
return {"status": "sync_started", "tld": tld}
|
|
|
|
|
|
# ============================================================================
|
|
# HELPER ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.get("/tlds")
|
|
async def api_get_supported_tlds():
|
|
"""
|
|
Get list of supported TLDs for zone file analysis.
|
|
"""
|
|
return {
|
|
"tlds": [
|
|
{
|
|
"tld": "ch",
|
|
"name": "Switzerland",
|
|
"flag": "🇨🇭",
|
|
"registry": "Switch"
|
|
},
|
|
{
|
|
"tld": "li",
|
|
"name": "Liechtenstein",
|
|
"flag": "🇱🇮",
|
|
"registry": "Switch"
|
|
}
|
|
]
|
|
}
|