diff --git a/backend/app/scheduler.py b/backend/app/scheduler.py index ba84f47..5da9588 100644 --- a/backend/app/scheduler.py +++ b/backend/app/scheduler.py @@ -735,6 +735,15 @@ def setup_scheduler(): replace_existing=True, ) + # Drops RDAP status update (every 15 minutes - check real status) + scheduler.add_job( + update_drops_status, + CronTrigger(minute='*/15'), # Every 15 minutes + id="drops_status_update", + name="Drops Status Update (15-min)", + replace_existing=True, + ) + logger.info( f"Scheduler configured:" f"\n - Scout domain check at {settings.check_hour:02d}:{settings.check_minute:02d} (daily)" @@ -1033,6 +1042,91 @@ async def verify_drops(): logger.exception(f"Drops verification failed: {e}") +async def update_drops_status(): + """ + Update RDAP status for dropped domains. + + This job runs every 30 minutes to check the real status of drops + (available, dropping_soon, taken) and store it in the database. + This way users see the status instantly without needing to check manually. + """ + logger.info("Starting drops status update...") + + try: + from app.services.drop_status_checker import batch_check_drops + from app.models.zone_file import DroppedDomain + from sqlalchemy import select, update + from datetime import datetime, timedelta + + async with AsyncSessionLocal() as db: + # Get drops that haven't been status-checked in the last 30 minutes + # Or have never been checked + thirty_min_ago = datetime.utcnow() - timedelta(minutes=30) + + query = ( + select(DroppedDomain) + .where( + (DroppedDomain.availability_status == 'unknown') | + (DroppedDomain.last_status_check == None) | + (DroppedDomain.last_status_check < thirty_min_ago) + ) + .order_by(DroppedDomain.length.asc()) # Short domains first + .limit(200) # Process 200 per run + ) + + result = await db.execute(query) + drops = result.scalars().all() + + if not drops: + logger.info("All drops have been status-checked recently") + return + + logger.info(f"Checking status for {len(drops)} drops...") + + # Prepare domain list + domains_to_check = [(d.id, f"{d.domain}.{d.tld}") for d in drops] + + # Batch check with rate limiting + results = await batch_check_drops(domains_to_check) + + # Update database + available_count = 0 + dropping_soon_count = 0 + taken_count = 0 + + for drop_id, status in results: + await db.execute( + update(DroppedDomain) + .where(DroppedDomain.id == drop_id) + .values( + availability_status=status.status, + rdap_status=str(status.rdap_status) if status.rdap_status else None, + last_status_check=datetime.utcnow(), + deletion_date=status.deletion_date, + ) + ) + + if status.status == 'available': + available_count += 1 + elif status.status == 'dropping_soon': + dropping_soon_count += 1 + elif status.status == 'taken': + taken_count += 1 + + await db.commit() + + logger.info( + f"Drops status update complete: " + f"{len(results)} checked, " + f"{available_count} available, " + f"{dropping_soon_count} dropping soon, " + f"{taken_count} taken" + ) + + except Exception as e: + logger.exception(f"Drops status update failed: {e}") + + async def sync_zone_files(): """Sync zone files from Switch.ch (.ch, .li) and ICANN CZDS (gTLDs).""" logger.info("Starting zone file sync...") diff --git a/frontend/src/components/hunt/DropsTab.tsx b/frontend/src/components/hunt/DropsTab.tsx index 5a2acf9..37e8168 100644 --- a/frontend/src/components/hunt/DropsTab.tsx +++ b/frontend/src/components/hunt/DropsTab.tsx @@ -156,29 +156,6 @@ export function DropsTab({ showToast }: DropsTabProps) { loadDrops(1) }, [loadDrops]) - // Auto batch-check status when drops are loaded - const [batchChecking, setBatchChecking] = useState(false) - - useEffect(() => { - // Check if we have unknown status drops - const unknownCount = items.filter(i => i.availability_status === 'unknown').length - - if (unknownCount > 0 && !batchChecking && items.length > 0) { - setBatchChecking(true) - - // Trigger batch check in background - api.batchCheckDrops(50).then(() => { - // Reload drops after a short delay to get updated status - setTimeout(() => { - loadDrops(page, false) - setBatchChecking(false) - }, 3000) - }).catch(() => { - setBatchChecking(false) - }) - } - }, [items, batchChecking, loadDrops, page]) - const handlePageChange = useCallback((newPage: number) => { setPage(newPage) loadDrops(newPage) @@ -187,8 +164,6 @@ export function DropsTab({ showToast }: DropsTabProps) { const handleRefresh = useCallback(async () => { await loadDrops(page, true) await loadStats() - // Also trigger batch check - api.batchCheckDrops(50).catch(() => {}) }, [loadDrops, loadStats, page]) // Check real-time status of a drop @@ -523,12 +498,6 @@ export function DropsTab({ showToast }: DropsTabProps) { {availableCount} available now )} - {batchChecking && ( -