Deploy: 2025-12-19 09:35
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:
2025-12-19 09:35:11 +01:00
parent 8dc6f85fb8
commit 93a18820c2
4 changed files with 230 additions and 154 deletions

View File

@ -193,12 +193,11 @@ async def api_check_drop_status(
Returns: Returns:
- available: Domain can be registered NOW - available: Domain can be registered NOW
- pending_delete: Domain is in deletion phase (monitor it!) - dropping_soon: Domain is in deletion phase (track it!)
- redemption: Domain is in redemption period (owner can recover)
- taken: Domain was re-registered - taken: Domain was re-registered
- unknown: Could not determine status - unknown: Could not determine status
""" """
from app.services.domain_checker import domain_checker from app.services.drop_status_checker import check_drop_status
# Get the drop from DB # Get the drop from DB
result = await db.execute( result = await db.execute(
@ -212,36 +211,16 @@ async def api_check_drop_status(
full_domain = f"{drop.domain}.{drop.tld}" full_domain = f"{drop.domain}.{drop.tld}"
try: try:
# Check with RDAP (not quick mode) # Check with dedicated drop status checker
check_result = await domain_checker.check_domain(full_domain, quick=False) status_result = await check_drop_status(full_domain)
# Determine availability status
if check_result.is_available:
availability_status = "available"
rdap_status = check_result.raw_data.get("rdap_status", []) if check_result.raw_data else []
# Check if it's pending delete (available but not immediately)
if rdap_status and any("pending" in str(s).lower() for s in rdap_status):
availability_status = "pending_delete"
else:
# Domain exists - check specific status
rdap_status = check_result.raw_data.get("rdap_status", []) if check_result.raw_data else []
rdap_str = str(rdap_status).lower()
if "pending delete" in rdap_str or "pendingdelete" in rdap_str:
availability_status = "pending_delete"
elif "redemption" in rdap_str:
availability_status = "redemption"
else:
availability_status = "taken"
# Update the drop in DB # Update the drop in DB
await db.execute( await db.execute(
update(DroppedDomain) update(DroppedDomain)
.where(DroppedDomain.id == drop_id) .where(DroppedDomain.id == drop_id)
.values( .values(
availability_status=availability_status, availability_status=status_result.status,
rdap_status=str(rdap_status) if rdap_status else None, rdap_status=str(status_result.rdap_status) if status_result.rdap_status else None,
last_status_check=datetime.utcnow() last_status_check=datetime.utcnow()
) )
) )
@ -250,11 +229,11 @@ async def api_check_drop_status(
return { return {
"id": drop_id, "id": drop_id,
"domain": full_domain, "domain": full_domain,
"availability_status": availability_status, "status": status_result.status,
"rdap_status": rdap_status, "rdap_status": status_result.rdap_status,
"is_available": check_result.is_available, "can_register_now": status_result.can_register_now,
"can_register_now": availability_status == "available", "should_track": status_result.should_monitor,
"can_monitor": availability_status in ("pending_delete", "redemption"), "message": status_result.message,
} }
except Exception as e: except Exception as e:
@ -262,17 +241,19 @@ async def api_check_drop_status(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.post("/monitor/{drop_id}") @router.post("/track/{drop_id}")
async def api_monitor_drop( async def api_track_drop(
drop_id: int, drop_id: int,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user) current_user = Depends(get_current_user)
): ):
""" """
Add a dropped domain to the Sniper monitoring list. Add a dropped domain to the user's Watchlist.
Will send notification when domain becomes available. Will send notification when domain becomes available.
This is the same as adding to watchlist, but optimized for drops.
""" """
from app.models.sniper_alert import SniperAlert from app.models.domain import Domain, DomainStatus
# Get the drop # Get the drop
result = await db.execute( result = await db.execute(
@ -285,49 +266,29 @@ async def api_monitor_drop(
full_domain = f"{drop.domain}.{drop.tld}" full_domain = f"{drop.domain}.{drop.tld}"
# Check if already monitoring # Check if already in watchlist
existing = await db.execute( existing = await db.execute(
select(SniperAlert).where( select(Domain).where(
SniperAlert.user_id == current_user.id, Domain.user_id == current_user.id,
SniperAlert.domain == full_domain, Domain.name == full_domain
SniperAlert.is_active == True
) )
) )
if existing.scalar_one_or_none(): if existing.scalar_one_or_none():
return {"status": "already_monitoring", "domain": full_domain} return {"status": "already_tracking", "domain": full_domain}
# Check user limits # Add to watchlist with notification enabled
from app.api.sniper_alerts import get_user_alert_limit domain = Domain(
limit = get_user_alert_limit(current_user)
count_result = await db.execute(
select(SniperAlert).where(
SniperAlert.user_id == current_user.id,
SniperAlert.is_active == True
)
)
current_count = len(count_result.scalars().all())
if current_count >= limit:
raise HTTPException(
status_code=400,
detail=f"Monitor limit reached ({limit}). Upgrade to add more."
)
# Create sniper alert for this drop
alert = SniperAlert(
user_id=current_user.id, user_id=current_user.id,
domain=full_domain, name=full_domain,
alert_type="drop", status=DomainStatus.AVAILABLE if drop.availability_status == 'available' else DomainStatus.UNKNOWN,
is_active=True, is_available=drop.availability_status == 'available',
notify_email=True, notify_on_available=True, # Enable notification!
notify_push=True,
) )
db.add(alert) db.add(domain)
await db.commit() await db.commit()
return { return {
"status": "monitoring", "status": "tracking",
"domain": full_domain, "domain": full_domain,
"message": f"You'll be notified when {full_domain} becomes available!" "message": f"Added {full_domain} to your Watchlist. You'll be notified when available!"
} }

View File

@ -0,0 +1,153 @@
"""
Drop Status Checker
====================
Dedicated RDAP checker for dropped domains.
Correctly identifies pending_delete, redemption, and available status.
"""
import httpx
import logging
from dataclasses import dataclass
from typing import Optional
logger = logging.getLogger(__name__)
# RDAP endpoints for different TLDs
RDAP_ENDPOINTS = {
# ccTLDs
'ch': 'https://rdap.nic.ch/domain/',
'li': 'https://rdap.nic.ch/domain/',
'de': 'https://rdap.denic.de/domain/',
# gTLDs via CentralNic
'online': 'https://rdap.centralnic.com/online/domain/',
'xyz': 'https://rdap.centralnic.com/xyz/domain/',
'club': 'https://rdap.nic.club/domain/',
# gTLDs via Afilias/Donuts
'info': 'https://rdap.afilias.net/rdap/info/domain/',
'biz': 'https://rdap.afilias.net/rdap/biz/domain/',
'org': 'https://rdap.publicinterestregistry.org/rdap/org/domain/',
# Google TLDs
'dev': 'https://rdap.nic.google/domain/',
'app': 'https://rdap.nic.google/domain/',
}
@dataclass
class DropStatus:
"""Status of a dropped domain."""
domain: str
status: str # 'available', 'dropping_soon', 'taken', 'unknown'
rdap_status: list[str]
can_register_now: bool
should_monitor: bool
message: str
async def check_drop_status(domain: str) -> DropStatus:
"""
Check the real status of a dropped domain via RDAP.
Returns:
DropStatus with one of:
- 'available': Domain can be registered NOW
- 'dropping_soon': Domain is in pending delete/redemption (monitor it!)
- 'taken': Domain was re-registered
- 'unknown': Could not determine status
"""
tld = domain.split('.')[-1].lower()
endpoint = RDAP_ENDPOINTS.get(tld)
if not endpoint:
# Try generic lookup
logger.warning(f"No RDAP endpoint for .{tld}, returning unknown")
return DropStatus(
domain=domain,
status='unknown',
rdap_status=[],
can_register_now=False,
should_monitor=False,
message=f"No RDAP endpoint for .{tld}"
)
url = f"{endpoint}{domain}"
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(url)
# 404 = Domain not found = AVAILABLE!
if resp.status_code == 404:
return DropStatus(
domain=domain,
status='available',
rdap_status=[],
can_register_now=True,
should_monitor=False,
message="Domain is available for registration!"
)
# 200 = Domain exists in registry
if resp.status_code == 200:
data = resp.json()
rdap_status = data.get('status', [])
status_lower = ' '.join(str(s).lower() for s in rdap_status)
# Check for pending delete / redemption status
is_pending = any(x in status_lower for x in [
'pending delete', 'pendingdelete',
'pending purge', 'pendingpurge',
'redemption period', 'redemptionperiod',
'pending restore', 'pendingrestore',
])
if is_pending:
return DropStatus(
domain=domain,
status='dropping_soon',
rdap_status=rdap_status,
can_register_now=False,
should_monitor=True,
message="Domain is being deleted. Track it to get notified when available!"
)
# Domain is actively registered
return DropStatus(
domain=domain,
status='taken',
rdap_status=rdap_status,
can_register_now=False,
should_monitor=False,
message="Domain was re-registered"
)
# Other status code
logger.warning(f"RDAP returned {resp.status_code} for {domain}")
return DropStatus(
domain=domain,
status='unknown',
rdap_status=[],
can_register_now=False,
should_monitor=False,
message=f"RDAP returned HTTP {resp.status_code}"
)
except httpx.TimeoutException:
logger.warning(f"RDAP timeout for {domain}")
return DropStatus(
domain=domain,
status='unknown',
rdap_status=[],
can_register_now=False,
should_monitor=False,
message="RDAP timeout"
)
except Exception as e:
logger.warning(f"RDAP error for {domain}: {e}")
return DropStatus(
domain=domain,
status='unknown',
rdap_status=[],
can_register_now=False,
should_monitor=False,
message=str(e)
)

View File

@ -3,7 +3,6 @@
import { useState, useEffect, useCallback, useMemo } from 'react' import { useState, useEffect, useCallback, useMemo } from 'react'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { useAnalyzePanelStore } from '@/lib/analyze-store' import { useAnalyzePanelStore } from '@/lib/analyze-store'
import { useStore } from '@/lib/store'
import { import {
Globe, Globe,
Loader2, Loader2,
@ -31,7 +30,7 @@ import clsx from 'clsx'
// TYPES // TYPES
// ============================================================================ // ============================================================================
type AvailabilityStatus = 'available' | 'pending_delete' | 'redemption' | 'taken' | 'unknown' type AvailabilityStatus = 'available' | 'dropping_soon' | 'taken' | 'unknown'
interface DroppedDomain { interface DroppedDomain {
id: number id: number
@ -74,7 +73,6 @@ interface DropsTabProps {
export function DropsTab({ showToast }: DropsTabProps) { export function DropsTab({ showToast }: DropsTabProps) {
const openAnalyze = useAnalyzePanelStore((s) => s.open) const openAnalyze = useAnalyzePanelStore((s) => s.open)
const addDomain = useStore((s) => s.addDomain)
// Data State // Data State
const [items, setItems] = useState<DroppedDomain[]>([]) const [items, setItems] = useState<DroppedDomain[]>([])
@ -101,12 +99,9 @@ export function DropsTab({ showToast }: DropsTabProps) {
const [sortField, setSortField] = useState<'domain' | 'length' | 'date'>('length') const [sortField, setSortField] = useState<'domain' | 'length' | 'date'>('length')
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc') const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
// Tracking
const [tracking, setTracking] = useState<string | null>(null)
// Status Checking // Status Checking
const [checkingStatus, setCheckingStatus] = useState<number | null>(null) const [checkingStatus, setCheckingStatus] = useState<number | null>(null)
const [monitoringDrop, setMonitoringDrop] = useState<number | null>(null) const [trackingDrop, setTrackingDrop] = useState<number | null>(null)
// Load Stats // Load Stats
const loadStats = useCallback(async () => { const loadStats = useCallback(async () => {
@ -169,19 +164,6 @@ export function DropsTab({ showToast }: DropsTabProps) {
await loadStats() await loadStats()
}, [loadDrops, loadStats, page]) }, [loadDrops, loadStats, page])
const track = useCallback(async (domain: string) => {
if (tracking) return
setTracking(domain)
try {
await addDomain(domain)
showToast(`Added: ${domain}`, 'success')
} catch (e) {
showToast(e instanceof Error ? e.message : 'Failed', 'error')
} finally {
setTracking(null)
}
}, [addDomain, showToast, tracking])
// Check real-time status of a drop // Check real-time status of a drop
const checkStatus = useCallback(async (dropId: number, domain: string) => { const checkStatus = useCallback(async (dropId: number, domain: string) => {
if (checkingStatus) return if (checkingStatus) return
@ -191,17 +173,11 @@ export function DropsTab({ showToast }: DropsTabProps) {
// Update the item in our list // Update the item in our list
setItems(prev => prev.map(item => setItems(prev => prev.map(item =>
item.id === dropId item.id === dropId
? { ...item, availability_status: result.availability_status, last_status_check: new Date().toISOString() } ? { ...item, availability_status: result.status, last_status_check: new Date().toISOString() }
: item : item
)) ))
if (result.can_register_now) { showToast(result.message, result.can_register_now ? 'success' : 'info')
showToast(`${domain} is available NOW!`, 'success')
} else if (result.can_monitor) {
showToast(`${domain} is pending deletion. Add to monitor!`, 'info')
} else {
showToast(`${domain} is taken`, 'error')
}
} catch (e) { } catch (e) {
showToast(e instanceof Error ? e.message : 'Status check failed', 'error') showToast(e instanceof Error ? e.message : 'Status check failed', 'error')
} finally { } finally {
@ -209,23 +185,23 @@ export function DropsTab({ showToast }: DropsTabProps) {
} }
}, [checkingStatus, showToast]) }, [checkingStatus, showToast])
// Monitor a pending drop // Track a drop (add to watchlist)
const monitorDrop = useCallback(async (dropId: number, domain: string) => { const trackDrop = useCallback(async (dropId: number, domain: string) => {
if (monitoringDrop) return if (trackingDrop) return
setMonitoringDrop(dropId) setTrackingDrop(dropId)
try { try {
const result = await api.monitorDrop(dropId) const result = await api.trackDrop(dropId)
if (result.status === 'already_monitoring') { if (result.status === 'already_tracking') {
showToast(`Already monitoring ${domain}`, 'info') showToast(`${domain} is already in your Watchlist`, 'info')
} else { } else {
showToast(`🎯 Monitoring ${domain} - you'll be notified!`, 'success') showToast(result.message, 'success')
} }
} catch (e) { } catch (e) {
showToast(e instanceof Error ? e.message : 'Failed to monitor', 'error') showToast(e instanceof Error ? e.message : 'Failed to track', 'error')
} finally { } finally {
setMonitoringDrop(null) setTrackingDrop(null)
} }
}, [monitoringDrop, showToast]) }, [trackingDrop, showToast])
// Sorted Items // Sorted Items
const sortedItems = useMemo(() => { const sortedItems = useMemo(() => {
@ -480,7 +456,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
<> <>
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden"> <div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
{/* Table Header */} {/* Table Header */}
<div className="hidden lg:grid grid-cols-[1fr_80px_110px_100px_200px] gap-4 px-6 py-4 text-[10px] font-mono text-white/40 uppercase tracking-[0.15em] border-b border-white/[0.08] bg-white/[0.02]"> <div className="hidden lg:grid grid-cols-[1fr_80px_130px_100px_180px] gap-4 px-6 py-4 text-[10px] font-mono text-white/40 uppercase tracking-[0.15em] border-b border-white/[0.08] bg-white/[0.02]">
<button <button
onClick={() => handleSort('domain')} onClick={() => handleSort('domain')}
className="flex items-center gap-2 hover:text-white transition-colors text-left" className="flex items-center gap-2 hover:text-white transition-colors text-left"
@ -510,18 +486,16 @@ export function DropsTab({ showToast }: DropsTabProps) {
<div className="divide-y divide-white/[0.04]"> <div className="divide-y divide-white/[0.04]">
{sortedItems.map((item) => { {sortedItems.map((item) => {
const fullDomain = `${item.domain}.${item.tld}` const fullDomain = `${item.domain}.${item.tld}`
const isTracking = tracking === fullDomain
const isChecking = checkingStatus === item.id const isChecking = checkingStatus === item.id
const isMonitoring = monitoringDrop === item.id const isTrackingThis = trackingDrop === item.id
const status = item.availability_status || 'unknown' const status = item.availability_status || 'unknown'
// 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 },
pending_delete: { label: 'Pending', color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', icon: Clock }, dropping_soon: { label: 'Dropping Soon', color: 'text-amber-400', bg: 'bg-amber-400/10', border: 'border-amber-400/30', icon: Clock },
redemption: { label: 'Redemption', color: 'text-orange-400', bg: 'bg-orange-400/10', border: 'border-orange-400/30', icon: AlertCircle },
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: 'Check', color: 'text-white/40', bg: 'bg-white/5', border: 'border-white/10', icon: Search }, unknown: { label: 'Check', color: 'text-white/50', bg: 'bg-white/5', border: 'border-white/20', icon: Search },
}[status] }[status]
const StatusIcon = statusConfig.icon const StatusIcon = statusConfig.icon
@ -573,24 +547,20 @@ export function DropsTab({ showToast }: DropsTabProps) {
<Zap className="w-4 h-4" /> <Zap className="w-4 h-4" />
Buy Now Buy Now
</a> </a>
) : status === 'pending_delete' || status === 'redemption' ? ( ) : status === 'dropping_soon' ? (
<button <button
onClick={() => monitorDrop(item.id, fullDomain)} onClick={() => trackDrop(item.id, fullDomain)}
disabled={isMonitoring} disabled={isTrackingThis}
className="flex-1 h-12 bg-amber-500 text-black text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 hover:bg-amber-400 active:scale-[0.98] transition-all" className="flex-1 h-12 bg-amber-500 text-black text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 hover:bg-amber-400 active:scale-[0.98] transition-all"
> >
{isMonitoring ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />} {isTrackingThis ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Monitor Track & Notify
</button> </button>
) : status === 'taken' ? ( ) : status === 'taken' ? (
<button <span className="flex-1 h-12 border border-rose-400/20 text-rose-400/60 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2 bg-rose-400/5">
onClick={() => checkStatus(item.id, fullDomain)} <Ban className="w-4 h-4" />
disabled={isChecking} Re-registered
className="flex-1 h-12 border border-white/10 text-white/40 text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-2" </span>
>
{isChecking ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
Recheck
</button>
) : ( ) : (
<button <button
onClick={() => checkStatus(item.id, fullDomain)} onClick={() => checkStatus(item.id, fullDomain)}
@ -611,7 +581,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
</div> </div>
{/* Desktop Row */} {/* Desktop Row */}
<div className="hidden lg:grid grid-cols-[1fr_80px_110px_100px_200px] gap-4 items-center px-6 py-3"> <div className="hidden lg:grid grid-cols-[1fr_80px_130px_100px_180px] gap-4 items-center px-6 py-3">
{/* Domain */} {/* Domain */}
<div className="min-w-0"> <div className="min-w-0">
<button <button
@ -634,7 +604,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
</span> </span>
</div> </div>
{/* Status */} {/* Status - clickable to refresh */}
<div className="text-center"> <div className="text-center">
<button <button
onClick={() => checkStatus(item.id, fullDomain)} onClick={() => checkStatus(item.id, fullDomain)}
@ -657,16 +627,8 @@ export function DropsTab({ showToast }: DropsTabProps) {
</span> </span>
</div> </div>
{/* Actions */} {/* Actions - simplified */}
<div className="flex items-center justify-end gap-2 opacity-60 group-hover:opacity-100 transition-all"> <div className="flex items-center justify-end gap-2 opacity-60 group-hover:opacity-100 transition-all">
<button
onClick={() => track(fullDomain)}
disabled={isTracking}
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 transition-all"
title="Add to Watchlist"
>
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
</button>
<button <button
onClick={() => openAnalyze(fullDomain)} onClick={() => openAnalyze(fullDomain)}
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all" className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
@ -687,18 +649,18 @@ export function DropsTab({ showToast }: DropsTabProps) {
<Zap className="w-3 h-3" /> <Zap className="w-3 h-3" />
Buy Now Buy Now
</a> </a>
) : status === 'pending_delete' || status === 'redemption' ? ( ) : status === 'dropping_soon' ? (
<button <button
onClick={() => monitorDrop(item.id, fullDomain)} onClick={() => trackDrop(item.id, fullDomain)}
disabled={isMonitoring} disabled={isTrackingThis}
className="h-9 px-4 bg-amber-500 text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-amber-400 transition-all" className="h-9 px-4 bg-amber-500 text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-amber-400 transition-all"
title="Get notified when available!" title="Add to Watchlist & get notified!"
> >
{isMonitoring ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />} {isTrackingThis ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
Monitor Track
</button> </button>
) : status === 'taken' ? ( ) : status === 'taken' ? (
<span className="h-9 px-4 text-rose-400/60 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-rose-400/20 bg-rose-400/5"> <span className="h-9 px-4 text-rose-400/50 text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 border border-rose-400/20 bg-rose-400/5">
<Ban className="w-3 h-3" /> <Ban className="w-3 h-3" />
Taken Taken
</span> </span>

View File

@ -2050,7 +2050,7 @@ class AdminApiClient extends ApiClient {
length: number length: number
is_numeric: boolean is_numeric: boolean
has_hyphen: boolean has_hyphen: boolean
availability_status: 'available' | 'pending_delete' | 'redemption' | 'taken' | 'unknown' availability_status: 'available' | 'dropping_soon' | 'taken' | 'unknown'
last_status_check: string | null last_status_check: string | null
}> }>
}>(`/drops?${query}`) }>(`/drops?${query}`)
@ -2071,20 +2071,20 @@ class AdminApiClient extends ApiClient {
return this.request<{ return this.request<{
id: number id: number
domain: string domain: string
availability_status: 'available' | 'pending_delete' | 'redemption' | 'taken' | 'unknown' status: 'available' | 'dropping_soon' | 'taken' | 'unknown'
rdap_status: string[] rdap_status: string[]
is_available: boolean
can_register_now: boolean can_register_now: boolean
can_monitor: boolean should_track: boolean
message: string
}>(`/drops/check-status/${dropId}`, { method: 'POST' }) }>(`/drops/check-status/${dropId}`, { method: 'POST' })
} }
async monitorDrop(dropId: number) { async trackDrop(dropId: number) {
return this.request<{ return this.request<{
status: string status: string
domain: string domain: string
message?: string message: string
}>(`/drops/monitor/${dropId}`, { method: 'POST' }) }>(`/drops/track/${dropId}`, { method: 'POST' })
} }
} }