Deploy: 2025-12-19 12:34
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
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
This commit is contained in:
@ -735,6 +735,15 @@ def setup_scheduler():
|
|||||||
replace_existing=True,
|
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(
|
logger.info(
|
||||||
f"Scheduler configured:"
|
f"Scheduler configured:"
|
||||||
f"\n - Scout domain check at {settings.check_hour:02d}:{settings.check_minute:02d} (daily)"
|
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}")
|
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():
|
async def sync_zone_files():
|
||||||
"""Sync zone files from Switch.ch (.ch, .li) and ICANN CZDS (gTLDs)."""
|
"""Sync zone files from Switch.ch (.ch, .li) and ICANN CZDS (gTLDs)."""
|
||||||
logger.info("Starting zone file sync...")
|
logger.info("Starting zone file sync...")
|
||||||
|
|||||||
@ -156,29 +156,6 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
loadDrops(1)
|
loadDrops(1)
|
||||||
}, [loadDrops])
|
}, [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) => {
|
const handlePageChange = useCallback((newPage: number) => {
|
||||||
setPage(newPage)
|
setPage(newPage)
|
||||||
loadDrops(newPage)
|
loadDrops(newPage)
|
||||||
@ -187,8 +164,6 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
const handleRefresh = useCallback(async () => {
|
const handleRefresh = useCallback(async () => {
|
||||||
await loadDrops(page, true)
|
await loadDrops(page, true)
|
||||||
await loadStats()
|
await loadStats()
|
||||||
// Also trigger batch check
|
|
||||||
api.batchCheckDrops(50).catch(() => {})
|
|
||||||
}, [loadDrops, loadStats, page])
|
}, [loadDrops, loadStats, page])
|
||||||
|
|
||||||
// Check real-time status of a drop
|
// Check real-time status of a drop
|
||||||
@ -523,12 +498,6 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
<span>{availableCount} available now</span>
|
<span>{availableCount} available now</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{batchChecking && (
|
|
||||||
<div className="flex items-center gap-2 text-amber-400">
|
|
||||||
<Loader2 className="w-3 h-3 animate-spin" />
|
|
||||||
<span>Checking status...</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{totalPages > 1 && !showOnlyAvailable && (
|
{totalPages > 1 && !showOnlyAvailable && (
|
||||||
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
||||||
@ -590,9 +559,9 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
|||||||
// Simplified status display config
|
// Simplified status display config
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
available: { label: 'Available', color: 'text-accent', bg: 'bg-accent/10', border: 'border-accent/30', icon: CheckCircle2 },
|
available: { label: 'Available', color: 'text-accent', bg: 'bg-accent/10', border: 'border-accent/30', icon: CheckCircle2 },
|
||||||
dropping_soon: { label: countdown || 'Soon', color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', icon: Clock },
|
dropping_soon: { label: countdown || 'Dropping', color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', icon: Clock },
|
||||||
taken: { label: 'Taken', color: 'text-rose-400', bg: 'bg-rose-400/10', border: 'border-rose-400/30', icon: Ban },
|
taken: { label: 'Taken', color: 'text-rose-400', bg: 'bg-rose-400/10', border: 'border-rose-400/30', icon: Ban },
|
||||||
unknown: { label: batchChecking ? '...' : 'Check', color: 'text-white/50', bg: 'bg-white/5', border: 'border-white/20', icon: batchChecking ? Loader2 : Search },
|
unknown: { label: 'Pending', color: 'text-white/50', bg: 'bg-white/5', border: 'border-white/20', icon: Clock },
|
||||||
}[status]
|
}[status]
|
||||||
|
|
||||||
const StatusIcon = statusConfig.icon
|
const StatusIcon = statusConfig.icon
|
||||||
|
|||||||
Reference in New Issue
Block a user