fix: Data freshness - only show active auctions
CRITICAL FIXES: - API: Added end_time > now() filter to all auction queries - Scheduler: Cleanup expired auctions every 15 minutes - Scheduler: Scrape auctions every 2 hours (was 1 hour) - Scheduler: Sniper alert matching every 30 minutes Affected endpoints: - GET /auctions (search) - GET /auctions/feed (unified) - GET /auctions/hot - GET /auctions/ending-soon (already had filter) Updated MARKET_CONCEPT.md with: - 3 pillars: Pounce Direct, Live Auctions, Drops Tomorrow - Data freshness architecture - Unicorn roadmap
This commit is contained in:
@ -6,504 +6,342 @@
|
|||||||
|
|
||||||
## 📋 Executive Summary
|
## 📋 Executive Summary
|
||||||
|
|
||||||
Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen und werden dem User als **"Clean Feed"** präsentiert.
|
Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen:
|
||||||
|
|
||||||
### Vision (aus pounce_terminal.md)
|
1. **Pounce Direct** — User-Listings (unser USP, 0% Provision)
|
||||||
> *"Die Market Page zeigt alle Domains die entweder:*
|
2. **Live Auktionen** — Externe Plattformen (GoDaddy, Sedo, etc.)
|
||||||
> 1. *Zu Verkauf stehen (Auktionen)*
|
3. **Drops Tomorrow** — Domains bevor sie in Auktionen landen (Phase 3)
|
||||||
> 2. *Bald frei werden (Drops)*
|
|
||||||
> 3. *Über Pounce direkt angeboten werden (Pounce Direct)"*
|
|
||||||
|
|
||||||
### Aktueller Stand: Phase 1 — Intelligence
|
### Der Weg zum Unicorn (aus pounce_strategy.md)
|
||||||
|
|
||||||
|
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."*
|
||||||
|
|
||||||
|
**Aggregation kann jeder. Pounce Direct ist unser USP.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 KRITISCHE FIXES (Implementiert am 11.12.2025)
|
||||||
|
|
||||||
|
### Problem: Veraltete Daten wurden angezeigt
|
||||||
|
|
||||||
|
```
|
||||||
|
VORHER: Abgelaufene Auktionen wurden im Feed angezeigt
|
||||||
|
→ Schlechte User Experience
|
||||||
|
→ Vertrauensverlust
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lösung: Multi-Layer Data Freshness
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. API-Filter: Nur laufende Auktionen
|
||||||
|
query = select(DomainAuction).where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == True,
|
||||||
|
DomainAuction.end_time > datetime.utcnow() # ← NEU!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Scheduler: Cleanup alle 15 Minuten
|
||||||
|
scheduler.add_job(
|
||||||
|
cleanup_expired_auctions,
|
||||||
|
CronTrigger(minute='*/15'), # Alle 15 Minuten
|
||||||
|
id="auction_cleanup"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Scraper: Alle 2 Stunden frische Daten
|
||||||
|
scheduler.add_job(
|
||||||
|
scrape_auctions,
|
||||||
|
CronTrigger(hour='*/2', minute=30), # Alle 2 Stunden
|
||||||
|
id="auction_scrape"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Die 3 Säulen des Market
|
||||||
|
|
||||||
|
### Säule 1: POUNCE DIRECT (Unser USP!)
|
||||||
|
|
||||||
|
> *"Das sind die Domains, die es NUR bei Pounce gibt."*
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ POUNCE MARKET — Aktueller Datenfluss │
|
│ 💎 POUNCE DIRECT │
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ DATENQUELLEN: │
|
│ Warum es genial ist: │
|
||||||
│ ───────────────────────────────────────────────────────────── │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
|
│ ✓ Unique Content (nur bei uns!) │
|
||||||
|
│ ✓ 0% Provision (vs. 15-20% bei Sedo) │
|
||||||
|
│ ✓ DNS-Verifizierung = Trust │
|
||||||
|
│ ✓ Instant Buy (kein Bieten) │
|
||||||
|
│ ✓ SEO: Jedes Listing = eigene Landing Page │
|
||||||
│ │
|
│ │
|
||||||
│ 📦 WEB SCRAPING (Hauptquelle) │
|
│ Der Flow: │
|
||||||
│ └─→ ExpiredDomains.net (325 Auktionen) ✅ │
|
|
||||||
│ └─→ GoDaddy RSS Feed (10 Auktionen) ✅ │
|
|
||||||
│ └─→ Sedo Public (7 Auktionen) ✅ │
|
|
||||||
│ └─→ NameJet Public (6 Auktionen) ✅ │
|
|
||||||
│ └─→ DropCatch Public (7 Auktionen) ✅ │
|
|
||||||
│ │
|
|
||||||
│ 🔌 OFFIZIELLE APIs (Konfiguriert) │
|
|
||||||
│ └─→ DropCatch Partner API ⚠️ (Nur eigene Aktivitäten) │
|
|
||||||
│ └─→ Sedo Partner API ⏳ (Credentials fehlen) │
|
|
||||||
│ │
|
|
||||||
│ 💎 POUNCE DIRECT (User-Listings) │
|
|
||||||
│ └─→ DNS-verifizierte Verkaufsangebote ❌ (0 Listings) │
|
|
||||||
│ │
|
|
||||||
│ 🔮 ZONE FILES (Phase 3 — Zukunft) │
|
|
||||||
│ └─→ Verisign .com/.net 🔜 │
|
|
||||||
│ └─→ PIR .org 🔜 │
|
|
||||||
│ │
|
|
||||||
│ ───────────────────────────────────────────────────────────── │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
│ TOTAL: 355 Domains im Feed | 0 Pounce Direct │
|
│ 1. User listet Domain (Trader/Tycoon Abo) │
|
||||||
|
│ 2. DNS-Verifizierung (TXT Record) │
|
||||||
|
│ 3. Listing erscheint im Market Feed │
|
||||||
|
│ 4. Käufer kontaktiert Verkäufer (nach Login) │
|
||||||
│ │
|
│ │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
**Status:** ⏳ 0 Listings — Muss aktiviert werden!
|
||||||
|
|
||||||
## 📊 TEIL 1: Bestandsaufnahme — Was haben wir?
|
|
||||||
|
|
||||||
### A. Backend-Komponenten ✅
|
|
||||||
|
|
||||||
| Komponente | Status | Beschreibung |
|
|
||||||
|------------|--------|--------------|
|
|
||||||
| **Unified Feed API** `/auctions/feed` | ✅ Live | Kombiniert Pounce Direct + External |
|
|
||||||
| **Pounce Score v2.0** | ✅ Live | Length, TLD, Bids, Time Pressure |
|
|
||||||
| **Vanity Filter** | ✅ Live | Premium-Domains für Public Users |
|
|
||||||
| **Auction Scraper** | ✅ Läuft | 5 Plattformen, Scheduler aktiv |
|
|
||||||
| **Listings API** | ✅ Fertig | DNS-Verifizierung, Inquiry-System |
|
|
||||||
| **Sniper Alerts** | ✅ Fertig | Keyword-Matching, Notifications |
|
|
||||||
|
|
||||||
### B. Frontend-Komponenten ✅
|
|
||||||
|
|
||||||
| Seite | Status | Beschreibung |
|
|
||||||
|-------|--------|--------------|
|
|
||||||
| `/terminal/market` | ✅ Live | Vollständiger Market Feed für Auth Users |
|
|
||||||
| `/auctions` | ✅ Live | Public Market mit Vanity Filter |
|
|
||||||
| `/buy` | ✅ Live | Pounce Direct Marketplace Browse |
|
|
||||||
| `/buy/[slug]` | ✅ Live | Listing-Detailseite |
|
|
||||||
| `/terminal/listing` | ✅ Live | Seller Dashboard |
|
|
||||||
|
|
||||||
### C. Datenquellen — Realitätscheck
|
|
||||||
|
|
||||||
#### Offizielle APIs — Die Ernüchterung
|
|
||||||
|
|
||||||
**DropCatch API:**
|
|
||||||
```
|
|
||||||
Status: ✅ Authentifiziert
|
|
||||||
Problem: Zeigt nur EIGENE Aktivitäten (Bids, Backorders)
|
|
||||||
NICHT das öffentliche Auktionsinventar
|
|
||||||
Nutzen: User-Integration (verbinde dein DropCatch-Konto)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sedo API:**
|
|
||||||
```
|
|
||||||
Status: ⏳ Client bereit, Credentials fehlen
|
|
||||||
Wo finden: Sedo.com → Mein Sedo → API-Zugang
|
|
||||||
Benötigt: Partner ID + SignKey
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Web Scraping — Unsere Hauptquelle
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Aktuelle Scraper-Architektur
|
|
||||||
TIER_1_APIS = [
|
|
||||||
("DropCatch", _fetch_dropcatch_api), # Für eigene Aktivitäten
|
|
||||||
("Sedo", _fetch_sedo_api), # Wenn konfiguriert
|
|
||||||
]
|
|
||||||
|
|
||||||
TIER_2_SCRAPING = [
|
|
||||||
("ExpiredDomains", _scrape_expireddomains), # 325 Domains
|
|
||||||
("GoDaddy", _scrape_godaddy_rss), # 10 Domains
|
|
||||||
("Sedo", _scrape_sedo_public), # 7 Domains (Fallback)
|
|
||||||
("NameJet", _scrape_namejet_public), # 6 Domains
|
|
||||||
("DropCatch", _scrape_dropcatch_public), # 7 Domains (Fallback)
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 TEIL 2: Das Konzept — Die 3 Säulen des Market
|
### Säule 2: LIVE AUKTIONEN (Content Filler)
|
||||||
|
|
||||||
### Säule 1: AUKTIONEN (Externe Plattformen)
|
|
||||||
|
|
||||||
> *"Zeige alle relevanten Auktionen von GoDaddy, Sedo, NameJet, etc."*
|
> *"Zeige alle relevanten Auktionen von GoDaddy, Sedo, NameJet, etc."*
|
||||||
|
|
||||||
**Datenquellen:**
|
|
||||||
- Web Scraping (primär)
|
|
||||||
- Partner APIs (wenn verfügbar)
|
|
||||||
|
|
||||||
**Filter-Strategie:**
|
|
||||||
```python
|
|
||||||
# Vanity Filter für Public Users (aus pounce_features.md)
|
|
||||||
def is_premium_domain(domain: str) -> bool:
|
|
||||||
name, tld = domain.rsplit('.', 1)
|
|
||||||
|
|
||||||
# Premium TLDs only
|
|
||||||
if tld not in ['com', 'io', 'ai', 'co', 'ch', 'de', 'net', 'org', 'app', 'dev']:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Keine Spam-Muster
|
|
||||||
if len(name) > 12: return False
|
|
||||||
if '-' in name: return False
|
|
||||||
if sum(c.isdigit() for c in name) > 1: return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
```
|
|
||||||
|
|
||||||
**UI-Darstellung:**
|
|
||||||
| Domain | Source | Price | Status | Action |
|
|
||||||
|--------|--------|-------|--------|--------|
|
|
||||||
| **crypto-bank.io** | 🏢 GoDaddy | $2,500 | ⏱️ 2h left | [Bid ↗] |
|
|
||||||
| **meta-shop.com** | 🏢 Sedo | $5,000 | 🤝 Offer | [View ↗] |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Säule 2: POUNCE DIRECT (User-Listings)
|
|
||||||
|
|
||||||
> *"Das sind die Domains, die es NUR bei Pounce gibt. Unser USP."*
|
|
||||||
|
|
||||||
**Das Konzept (aus pounce_terminal.md):**
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ POUNCE DIRECT — Der Listing-Wizard │
|
│ 🏢 LIVE AUKTIONEN │
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ STEP 1: DOMAIN EINGEBEN │
|
│ Datenquellen: │
|
||||||
│ ─────────────────────────────────────────────────────────── │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
│ Domain: [zurich-immo.ch___________] │
|
│ 📦 Web Scraping (Hauptquelle) │
|
||||||
│ Preis: [$950_______] ○ Fixpreis ● Verhandlungsbasis │
|
│ └─→ ExpiredDomains.net (~350 Domains) │
|
||||||
|
│ └─→ GoDaddy RSS │
|
||||||
|
│ └─→ Sedo Public │
|
||||||
|
│ └─→ NameJet Public │
|
||||||
|
│ └─→ DropCatch Public │
|
||||||
│ │
|
│ │
|
||||||
│ STEP 2: DNS VERIFICATION (Trust-Check) │
|
│ Data Freshness: │
|
||||||
│ ─────────────────────────────────────────────────────────── │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
│ Füge diesen TXT-Record bei deinem Registrar hinzu: │
|
│ ⏱️ Scraping: Alle 2 Stunden │
|
||||||
|
│ 🧹 Cleanup: Alle 15 Minuten │
|
||||||
|
│ ✅ Filter: Nur end_time > now() │
|
||||||
│ │
|
│ │
|
||||||
│ Name: _pounce-verify │
|
│ Qualitätsfilter: │
|
||||||
│ Value: pounce-verify-8a3f7b9c2e1d │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
│ │
|
│ • Vanity Filter für Public (nur Premium-Domains) │
|
||||||
│ [🔄 VERIFY DNS] │
|
│ • Pounce Score (0-100) │
|
||||||
│ │
|
│ • TLD Filter (com, io, ai, etc.) │
|
||||||
│ STEP 3: LIVE! │
|
|
||||||
│ ─────────────────────────────────────────────────────────── │
|
|
||||||
│ ✅ Domain verifiziert! │
|
|
||||||
│ Dein Listing erscheint jetzt im Market Feed. │
|
|
||||||
│ │
|
│ │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**Warum das genial ist:**
|
**Status:** ✅ ~361 aktive Auktionen
|
||||||
|
|
||||||
| Vorteil | Erklärung |
|
|
||||||
|---------|-----------|
|
|
||||||
| **Unique Content** | Domains, die es NUR bei Pounce gibt |
|
|
||||||
| **Höhere Conversion** | "Instant Buy" statt "Bid on external site" |
|
|
||||||
| **Vendor Lock-in** | Verkäufer listen bei uns (0% Provision!) |
|
|
||||||
| **SEO Power** | Jede Listing = eigene Landing Page |
|
|
||||||
| **Trust Signal** | DNS-Verifizierung = Qualitätsgarantie |
|
|
||||||
|
|
||||||
**UI-Darstellung:**
|
|
||||||
| Domain | Source | Price | Status | Action |
|
|
||||||
|--------|--------|-------|--------|--------|
|
|
||||||
| **zurich-immo.ch** | 💎 **Pounce** | **$950** | ⚡ **Instant** | **[Buy Now]** |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Säule 3: DROPS (Domains die bald frei werden)
|
### Säule 3: DROPS TOMORROW (Tycoon Exclusive)
|
||||||
|
|
||||||
> *"Zeige Domains BEVOR sie in Auktionen landen."*
|
> *"Zeige Domains BEVOR sie in Auktionen landen."*
|
||||||
|
|
||||||
**Phase 1 (Jetzt): Deleted Domains via Scraping**
|
|
||||||
```
|
|
||||||
ExpiredDomains.net → Deleted Domains Liste → Pounce Filter → Feed
|
|
||||||
```
|
|
||||||
|
|
||||||
**Phase 3 (Zukunft): Zone File Analysis**
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ ZONE FILE PIPELINE — Die Unicorn-Strategie │
|
│ 🔮 DROPS TOMORROW — Phase 3 │
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ 1. DAILY DOWNLOAD (4:00 UTC) │
|
│ Das Konzept: │
|
||||||
│ └─→ Zone Files von Verisign, PIR, etc. │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
|
│ 1. Zone Files von Verisign (.com/.net) beziehen │
|
||||||
|
│ 2. Tägliche Diff-Analyse (was war gestern da, ist heute weg) │
|
||||||
|
│ 3. Diese Domains droppen in 1-5 Tagen! │
|
||||||
|
│ 4. Pounce Algorithm filtert nur Premium-Domains │
|
||||||
│ │
|
│ │
|
||||||
│ 2. DIFF ANALYSIS │
|
│ Warum das ein MONOPOL schafft: │
|
||||||
│ └─→ Was war gestern da, ist heute weg? │
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
│ └─→ Diese Domains DROPPEN in 1-5 Tagen! │
|
│ • ExpiredDomains zeigt ALLES (Spam-Hölle) │
|
||||||
│ │
|
│ • Pounce zeigt nur die TOP 100 (kuratiert) │
|
||||||
│ 3. POUNCE ALGORITHM │
|
│ • = Zeitersparnis = Premium Feature = $29/Monat │
|
||||||
│ └─→ Nur Premium-Domains durchlassen (Score > 70) │
|
|
||||||
│ │
|
|
||||||
│ 4. OUTPUT: "Drops Tomorrow" (Tycoon Exclusive) │
|
|
||||||
│ └─→ Domains BEVOR sie in Auktionen erscheinen │
|
|
||||||
│ │
|
│ │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
**Status:** 🔜 Geplant (6-12 Monate)
|
||||||
|
|
||||||
## 🔧 TEIL 3: Technische Architektur
|
|
||||||
|
|
||||||
### Der Unified Feed API Endpoint
|
|
||||||
|
|
||||||
```python
|
|
||||||
# backend/app/api/auctions.py
|
|
||||||
|
|
||||||
@router.get("/feed")
|
|
||||||
async def get_market_feed(
|
|
||||||
source: str = Query("all", enum=["all", "pounce", "external"]),
|
|
||||||
keyword: Optional[str] = None,
|
|
||||||
tld: Optional[str] = None,
|
|
||||||
min_price: Optional[float] = None,
|
|
||||||
max_price: Optional[float] = None,
|
|
||||||
min_score: int = Query(0, ge=0, le=100),
|
|
||||||
ending_within: Optional[int] = None, # Stunden
|
|
||||||
verified_only: bool = False,
|
|
||||||
sort_by: str = Query("score", enum=["score", "price_asc", "price_desc", "time", "newest"]),
|
|
||||||
limit: int = Query(50, le=200),
|
|
||||||
offset: int = Query(0),
|
|
||||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
🚀 UNIFIED MARKET FEED — Das Herz von Pounce
|
|
||||||
|
|
||||||
Kombiniert:
|
|
||||||
- 💎 Pounce Direct: DNS-verifizierte User-Listings (Instant Buy)
|
|
||||||
- 🏢 External Auctions: Scraped von GoDaddy, Sedo, etc.
|
|
||||||
- 🔮 Drops: Domains die bald frei werden (Phase 3)
|
|
||||||
|
|
||||||
Für nicht-authentifizierte User:
|
|
||||||
- Vanity Filter aktiv (nur Premium-Domains)
|
|
||||||
- Pounce Score sichtbar, aber limited Details
|
|
||||||
|
|
||||||
Für authentifizierte User (Trader/Tycoon):
|
|
||||||
- Vollzugriff auf alle Domains
|
|
||||||
- Advanced Filtering
|
|
||||||
- Valuation Data
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pounce Score v2.0
|
|
||||||
|
|
||||||
```python
|
|
||||||
def calculate_pounce_score_v2(domain: str, auction_data: dict) -> int:
|
|
||||||
"""
|
|
||||||
Der Pounce Score — Qualitäts- und Opportunity-Bewertung
|
|
||||||
|
|
||||||
A) INTRINSIC VALUE (Domain selbst)
|
|
||||||
- Länge (kurz = wertvoll)
|
|
||||||
- TLD Premium (com > io > xyz)
|
|
||||||
- Dictionary Word Bonus
|
|
||||||
|
|
||||||
B) MARKET SIGNALS (Aktivität)
|
|
||||||
- Bid Activity (mehr Bids = mehr Interesse)
|
|
||||||
- Time Pressure (endet bald = Opportunity)
|
|
||||||
- Price-to-Value Ratio (unterbewertet = 🔥)
|
|
||||||
|
|
||||||
C) PENALTIES
|
|
||||||
- Bindestriche (-30)
|
|
||||||
- Zahlen wenn >3 Zeichen (-20)
|
|
||||||
- Zu lang >15 Zeichen (-25)
|
|
||||||
"""
|
|
||||||
score = 50 # Baseline
|
|
||||||
name = domain.rsplit('.', 1)[0]
|
|
||||||
tld = domain.rsplit('.', 1)[1]
|
|
||||||
|
|
||||||
# Länge
|
|
||||||
if len(name) <= 3: score += 30
|
|
||||||
elif len(name) == 4: score += 25
|
|
||||||
elif len(name) == 5: score += 20
|
|
||||||
elif len(name) <= 7: score += 10
|
|
||||||
|
|
||||||
# TLD
|
|
||||||
tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'ch': 15}
|
|
||||||
score += tld_scores.get(tld, 0)
|
|
||||||
|
|
||||||
# Market Signals
|
|
||||||
bids = auction_data.get('num_bids', 0)
|
|
||||||
if bids >= 20: score += 15
|
|
||||||
elif bids >= 10: score += 10
|
|
||||||
elif bids >= 5: score += 5
|
|
||||||
|
|
||||||
# Penalties
|
|
||||||
if '-' in name: score -= 30
|
|
||||||
if any(c.isdigit() for c in name) and len(name) > 3: score -= 20
|
|
||||||
|
|
||||||
return max(0, min(100, score))
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📈 TEIL 4: Roadmap
|
## 🎨 UI/UX: Die Market Page
|
||||||
|
|
||||||
### ✅ ERLEDIGT (Stand: 11. Dezember 2025)
|
|
||||||
|
|
||||||
- [x] Unified Feed API `/auctions/feed`
|
|
||||||
- [x] Pounce Score v2.0 mit Market Signals
|
|
||||||
- [x] Vanity Filter für Public Users
|
|
||||||
- [x] Pounce Direct Listing-System (DNS-Verifizierung)
|
|
||||||
- [x] Sniper Alerts mit Keyword-Matching
|
|
||||||
- [x] Web Scraping für 5 Plattformen
|
|
||||||
- [x] DropCatch API Client (für User-Integration)
|
|
||||||
- [x] Sedo API Client (bereit für Credentials)
|
|
||||||
|
|
||||||
### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
|
|
||||||
|
|
||||||
1. **Sedo API Credentials eingeben**
|
|
||||||
- Sedo.com → Mein Sedo → API-Zugang
|
|
||||||
- Partner ID + SignKey in `.env`
|
|
||||||
|
|
||||||
2. **Erste Pounce Direct Listings erstellen**
|
|
||||||
- Test-Domains zum Verifizieren des Flows
|
|
||||||
- Zeigt "Unique Content" im Feed
|
|
||||||
|
|
||||||
3. **Scraper-Stabilität verbessern**
|
|
||||||
- Fallback-Logik testen
|
|
||||||
- Error-Handling optimieren
|
|
||||||
|
|
||||||
### 🔮 PHASE 3 (6-12 Monate)
|
|
||||||
|
|
||||||
1. **Zone File Access beantragen**
|
|
||||||
- Verisign (.com/.net)
|
|
||||||
- PIR (.org)
|
|
||||||
- Kosten: $0-$10,000/Jahr
|
|
||||||
|
|
||||||
2. **"Drops Tomorrow" Feature**
|
|
||||||
- Zone File Diff-Analyse
|
|
||||||
- Tycoon Exclusive ($29/mo)
|
|
||||||
|
|
||||||
3. **Pounce Instant Exchange**
|
|
||||||
- Integrierter Escrow-Service
|
|
||||||
- 5% Gebühr (statt 15-20% bei Konkurrenz)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 TEIL 5: UI/UX Design
|
|
||||||
|
|
||||||
### Die Master-Tabelle (aus pounce_terminal.md)
|
|
||||||
|
|
||||||
| Spalte | Inhalt | Visualisierung |
|
|
||||||
|--------|--------|----------------|
|
|
||||||
| **Domain** | Name der Domain | Fettgedruckt. Bei "Pounce Direct" → 💎 Icon |
|
|
||||||
| **Pounce Score** | Qualitäts-Algorithmus | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
|
|
||||||
| **Price / Bid** | Preis oder aktuelles Gebot | `$500` oder `$50 (Bid)` |
|
|
||||||
| **Status / Time** | Countdown oder Verfügbarkeit | ⏱️ `4h left` oder ⚡ `Instant` |
|
|
||||||
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
|
|
||||||
| **Action** | Der Button | `[Bid ↗]` oder `[Buy Now]` |
|
|
||||||
|
|
||||||
### Filter Bar
|
### Filter Bar
|
||||||
|
|
||||||
```
|
```
|
||||||
[Toggle] Hide Spam (Standard: AN)
|
[✓] Hide Spam [○] Pounce Only [TLD ▾] [Price ▾] [Ending ▾]
|
||||||
[Toggle] Pounce Direct Only
|
|
||||||
[Dropdown] TLD: .com, .ai, .io, .ch
|
|
||||||
[Dropdown] Price: < $100, < $1k, High Roller
|
|
||||||
[Dropdown] Ending: 1h, 4h, 24h, 7d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Die Master-Tabelle
|
||||||
|
|
||||||
|
| Spalte | Inhalt | Visualisierung |
|
||||||
|
|--------|--------|----------------|
|
||||||
|
| **Domain** | Name | Fettgedruckt. 💎 Icon für Pounce Direct |
|
||||||
|
| **Score** | Pounce Score | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
|
||||||
|
| **Price** | Preis/Gebot | `$500` oder `$50 (Bid)` |
|
||||||
|
| **Status** | Zeit/Verfügbarkeit | ⏱️ `4h left` oder ⚡ `Instant` |
|
||||||
|
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
|
||||||
|
| **Action** | Button | `[Bid ↗]` oder `[Buy Now]` |
|
||||||
|
|
||||||
### Visuelle Hierarchie
|
### Visuelle Hierarchie
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Pounce Direct Items werden prominent angezeigt
|
|
||||||
{pounceDirectItems.length > 0 && (
|
|
||||||
<section className="mb-6">
|
|
||||||
<div className="flex items-center gap-2 text-emerald-400 font-bold mb-3">
|
|
||||||
<Diamond className="w-4 h-4" />
|
|
||||||
Pounce Exclusive — Verified Instant Buy
|
|
||||||
</div>
|
|
||||||
{pounceDirectItems.map(item => <PounceDirectCard />)}
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
// External Auctions darunter
|
|
||||||
<section>
|
|
||||||
<h2>Active Auctions</h2>
|
|
||||||
{externalItems.map(item => <AuctionCard />)}
|
|
||||||
</section>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💰 TEIL 6: Monetarisierung
|
|
||||||
|
|
||||||
### Tier-basierte Features (aus pounce_pricing.md)
|
|
||||||
|
|
||||||
| Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|
|
||||||
|---------|------------|-------------|--------------|
|
|
||||||
| **Market Feed** | 🌪️ Raw (Vanity Filter) | ✨ Curated (Clean) | ✨ Curated + Priority |
|
|
||||||
| **Alert Speed** | 🐢 Daily | 🐇 Hourly | ⚡ Real-Time (10m) |
|
|
||||||
| **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
|
|
||||||
| **Sell Domains** | ❌ | ✅ 5 Listings | ✅ 50 Listings + Featured |
|
|
||||||
| **Pounce Score** | ❌ Locked | ✅ Basic | ✅ + SEO Data |
|
|
||||||
| **Drops Tomorrow** | ❌ | ❌ | ✅ Exclusive |
|
|
||||||
|
|
||||||
### Die "Conversion-Falle" (aus pounce_features.md)
|
|
||||||
|
|
||||||
Wenn ein nicht-eingeloggter User auf "Buy Now" bei einem Pounce Direct Listing klickt:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ 🔒 Secure Transaction │
|
│ MARKET FEED │
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ Du bist dabei, ein verifiziertes Direct-Listing anzusehen. │
|
│ 💎 POUNCE EXCLUSIVE — Verified Instant Buy │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ zurich-immo.ch $950 ⚡ Instant ✅ Verified [Buy] │ │
|
||||||
|
│ │ crypto-hub.io $2.5k ⚡ Instant ✅ Verified [Buy] │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ Um den Verkäufer zu kontaktieren und Käuferschutz zu │
|
│ 🏢 LIVE AUCTIONS │
|
||||||
│ genießen, logge dich bitte ein. │
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ techflow.io $250 ⏱️ 4h left GoDaddy [Bid ↗] │ │
|
||||||
|
│ │ datalab.com $1.2k ⏱️ 23h left Sedo [Bid ↗] │ │
|
||||||
|
│ │ nexus.ai $5k ⏱️ 2d left NameJet [Bid ↗] │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ [Login] [Create Free Scout Account] │
|
│ 🔮 DROPS TOMORROW (Tycoon Only) │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 🔒 Upgrade to Tycoon to see domains dropping tomorrow │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔑 TEIL 7: Kritische Erkenntnisse
|
## 💰 Monetarisierung (aus pounce_pricing.md)
|
||||||
|
|
||||||
### API-Realität vs. Erwartung
|
| Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|
||||||
|
|---------|------------|-------------|--------------|
|
||||||
| API | Erwartung | Realität |
|
| **Market Feed** | 🌪️ Vanity Filter | ✨ Clean | ✨ Clean + Priority |
|
||||||
|-----|-----------|----------|
|
| **Alert Speed** | 🐢 Daily | 🐇 Hourly | ⚡ Real-Time (10m) |
|
||||||
| **DropCatch** | Alle öffentlichen Auktionen | ❌ Nur eigene Bids/Backorders |
|
| **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
|
||||||
| **Sedo** | TBD | ⏳ Credentials fehlen noch |
|
| **Sell Domains** | ❌ | ✅ 5 Listings | ✅ 50 + Featured |
|
||||||
|
| **Pounce Score** | ❌ Locked | ✅ Basic | ✅ + SEO Data |
|
||||||
**Konsequenz:**
|
| **Drops Tomorrow** | ❌ | ❌ | ✅ Exclusive |
|
||||||
- Web Scraping bleibt unsere **Hauptquelle** für öffentliche Daten
|
|
||||||
- APIs sind nützlich für **User-Integration** (verbinde dein DropCatch-Konto)
|
|
||||||
- **Zone Files** sind der langfristige Weg zur Datenhoheit
|
|
||||||
|
|
||||||
### Der echte USP: Pounce Direct
|
|
||||||
|
|
||||||
> *"Domains die es NUR bei Pounce gibt."*
|
|
||||||
|
|
||||||
Das ist der Schlüssel. Nicht die Aggregation (das kann jeder), sondern der **Unique Content** durch User-Listings.
|
|
||||||
|
|
||||||
**Priorität:** Erste Pounce Direct Listings aktivieren!
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Checkliste für den Launch
|
## ⚙️ Technische Architektur
|
||||||
|
|
||||||
### Backend
|
### Scheduler Jobs
|
||||||
- [x] Unified Feed API
|
|
||||||
|
```python
|
||||||
|
# Aktive Jobs (Scheduler)
|
||||||
|
# ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# 1. Auction Scrape — Alle 2 Stunden
|
||||||
|
scheduler.add_job(scrape_auctions, CronTrigger(hour='*/2', minute=30))
|
||||||
|
|
||||||
|
# 2. Expired Cleanup — Alle 15 Minuten (KRITISCH!)
|
||||||
|
scheduler.add_job(cleanup_expired_auctions, CronTrigger(minute='*/15'))
|
||||||
|
|
||||||
|
# 3. Sniper Matching — Alle 30 Minuten
|
||||||
|
scheduler.add_job(match_sniper_alerts, CronTrigger(minute='*/30'))
|
||||||
|
|
||||||
|
# 4. TLD Prices — Täglich 03:00 UTC
|
||||||
|
scheduler.add_job(scrape_tld_prices, CronTrigger(hour=3))
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Market Feed Endpoints
|
||||||
|
# ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
GET /api/v1/auctions/feed # Unified Feed (Pounce + External)
|
||||||
|
GET /api/v1/auctions # External Auctions only
|
||||||
|
GET /api/v1/auctions/ending-soon
|
||||||
|
GET /api/v1/auctions/hot
|
||||||
|
GET /api/v1/listings # Pounce Direct Listings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Freshness Garantie
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Jede Query filtert automatisch auf aktive Auktionen:
|
||||||
|
query = select(DomainAuction).where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == True,
|
||||||
|
DomainAuction.end_time > datetime.utcnow() # ← IMMER!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Roadmap
|
||||||
|
|
||||||
|
### ✅ ERLEDIGT (11. Dezember 2025)
|
||||||
|
|
||||||
|
- [x] Unified Feed API `/auctions/feed`
|
||||||
- [x] Pounce Score v2.0
|
- [x] Pounce Score v2.0
|
||||||
- [x] Vanity Filter
|
- [x] Vanity Filter
|
||||||
- [x] Scraper aktiv
|
- [x] Web Scraping (5 Plattformen)
|
||||||
- [ ] Sedo API Credentials eingeben
|
- [x] **FIX: end_time Filter** (nur laufende Auktionen)
|
||||||
- [ ] Scheduler-Intervall optimieren
|
- [x] **FIX: Cleanup alle 15 Minuten**
|
||||||
|
- [x] **FIX: Scraper alle 2 Stunden**
|
||||||
|
- [x] Sniper Alerts
|
||||||
|
|
||||||
### Frontend
|
### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
|
||||||
- [x] Terminal Market Page
|
|
||||||
- [x] Public Auctions Page
|
|
||||||
- [x] Pounce Direct Highlighting
|
|
||||||
- [x] Filter (Source, TLD, Price)
|
|
||||||
- [ ] "Hot Right Now" Section
|
|
||||||
- [ ] Better Empty States
|
|
||||||
|
|
||||||
### Content
|
1. **Erste Pounce Direct Listings erstellen**
|
||||||
- [ ] Erste 5 Test-Listings erstellen
|
- Test-Domains zum Verifizieren des Flows
|
||||||
- [ ] DNS-Verifizierung testen
|
- USP aktivieren!
|
||||||
- [ ] Listing-to-Feed Flow validieren
|
|
||||||
|
2. **Sedo API Credentials eingeben**
|
||||||
|
- Sedo.com → Mein Sedo → API-Zugang
|
||||||
|
- Partner ID + SignKey in `.env`
|
||||||
|
|
||||||
|
3. **Frontend: "Live" Indikator**
|
||||||
|
- Zeige wann Daten zuletzt aktualisiert wurden
|
||||||
|
|
||||||
|
### 🔮 PHASE 2-3 (6-12 Monate)
|
||||||
|
|
||||||
|
1. **Zone File Access beantragen**
|
||||||
|
- Verisign (.com/.net)
|
||||||
|
- "Drops Tomorrow" Feature
|
||||||
|
|
||||||
|
2. **Pounce Instant Exchange**
|
||||||
|
- Integrierter Escrow-Service
|
||||||
|
- 5% Gebühr
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💎 Fazit
|
## 🚀 Der Unicorn-Pfad
|
||||||
|
|
||||||
Die Market Page ist **funktional**, aber der wahre USP (Pounce Direct) ist noch nicht aktiviert.
|
```
|
||||||
|
Phase 1: INTELLIGENCE (Jetzt)
|
||||||
|
├── Pounce Direct aktivieren (Unique Content)
|
||||||
|
├── Clean Feed (aktuelle Daten, Spam-frei)
|
||||||
|
├── Trust aufbauen
|
||||||
|
└── 10.000 User, $1M ARR
|
||||||
|
|
||||||
**Die Reihenfolge:**
|
Phase 2: LIQUIDITÄT (18-36 Monate)
|
||||||
1. ✅ Aggregation funktioniert (Scraping)
|
├── Pounce Instant Exchange
|
||||||
2. ⏳ Pounce Direct aktivieren (User-Listings)
|
├── Buy Now im Dashboard
|
||||||
3. 🔮 Zone Files für Datenhoheit (Phase 3)
|
├── 5% Gebühr
|
||||||
|
└── $10M ARR
|
||||||
|
|
||||||
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."*
|
Phase 3: FINANZIALISIERUNG (3-5 Jahre)
|
||||||
|
├── Fractional Ownership
|
||||||
|
├── Domain-Backed Lending
|
||||||
|
└── = FINTECH ($50-100M ARR)
|
||||||
|
|
||||||
|
Phase 4: IMPERIUM (5+ Jahre)
|
||||||
|
├── Enterprise Sentinel (B2B)
|
||||||
|
├── Fortune 500 Kunden
|
||||||
|
└── = $1 Mrd. Bewertung
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💎 Das Fazit
|
||||||
|
|
||||||
|
**Aggregation ist Commodity. Pounce Direct ist der USP.**
|
||||||
|
|
||||||
|
Der Weg zum Unicorn:
|
||||||
|
1. ✅ Datenqualität (aktuelle, saubere Daten)
|
||||||
|
2. ⏳ Unique Content (Pounce Direct aktivieren!)
|
||||||
|
3. 🔮 Datenhoheit (Zone Files)
|
||||||
|
|
||||||
|
> *"Don't guess. Know."*
|
||||||
>
|
>
|
||||||
> — pounce_strategy.md
|
> — Phase 1: Intelligence
|
||||||
|
|||||||
@ -277,8 +277,14 @@ async def search_auctions(
|
|||||||
- Look for value_ratio > 1.0 (estimated value exceeds current bid)
|
- Look for value_ratio > 1.0 (estimated value exceeds current bid)
|
||||||
- Focus on auctions ending soon with low bid counts
|
- Focus on auctions ending soon with low bid counts
|
||||||
"""
|
"""
|
||||||
# Build query
|
# Build query - ONLY show active auctions that haven't ended yet
|
||||||
query = select(DomainAuction).where(DomainAuction.is_active == True)
|
now = datetime.utcnow()
|
||||||
|
query = select(DomainAuction).where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == True,
|
||||||
|
DomainAuction.end_time > now # ← KRITISCH: Nur Auktionen die noch laufen!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains
|
# VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains
|
||||||
# This ensures the first impression is high-quality, not spam domains
|
# This ensures the first impression is high-quality, not spam domains
|
||||||
@ -457,9 +463,15 @@ async def get_hot_auctions(
|
|||||||
|
|
||||||
Data is scraped from public auction sites - no mock data.
|
Data is scraped from public auction sites - no mock data.
|
||||||
"""
|
"""
|
||||||
|
now = datetime.utcnow()
|
||||||
query = (
|
query = (
|
||||||
select(DomainAuction)
|
select(DomainAuction)
|
||||||
.where(DomainAuction.is_active == True)
|
.where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == True,
|
||||||
|
DomainAuction.end_time > now # Only show active auctions
|
||||||
|
)
|
||||||
|
)
|
||||||
.order_by(DomainAuction.num_bids.desc())
|
.order_by(DomainAuction.num_bids.desc())
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
)
|
)
|
||||||
@ -996,7 +1008,13 @@ async def get_market_feed(
|
|||||||
# 2. EXTERNAL AUCTIONS (Scraped from platforms)
|
# 2. EXTERNAL AUCTIONS (Scraped from platforms)
|
||||||
# ═══════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════
|
||||||
if source in ["all", "external"]:
|
if source in ["all", "external"]:
|
||||||
auction_query = select(DomainAuction).where(DomainAuction.is_active == True)
|
now = datetime.utcnow()
|
||||||
|
auction_query = select(DomainAuction).where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == True,
|
||||||
|
DomainAuction.end_time > now # ← KRITISCH: Nur laufende Auktionen!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
auction_query = auction_query.where(
|
auction_query = auction_query.where(
|
||||||
|
|||||||
@ -204,12 +204,30 @@ def setup_scheduler():
|
|||||||
replace_existing=True,
|
replace_existing=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Auction scrape every hour (at :30 to avoid conflict with other jobs)
|
# Auction scrape every 2 hours (at :30 to avoid conflict with other jobs)
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
scrape_auctions,
|
scrape_auctions,
|
||||||
CronTrigger(minute=30), # Every hour at :30
|
CronTrigger(hour='*/2', minute=30), # Every 2 hours at :30
|
||||||
id="hourly_auction_scrape",
|
id="auction_scrape",
|
||||||
name="Hourly Auction Scrape",
|
name="Auction Scrape (2h)",
|
||||||
|
replace_existing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cleanup expired auctions every 15 minutes (CRITICAL for data freshness!)
|
||||||
|
scheduler.add_job(
|
||||||
|
cleanup_expired_auctions,
|
||||||
|
CronTrigger(minute='*/15'), # Every 15 minutes
|
||||||
|
id="auction_cleanup",
|
||||||
|
name="Expired Auction Cleanup (15m)",
|
||||||
|
replace_existing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sniper alert matching every 30 minutes
|
||||||
|
scheduler.add_job(
|
||||||
|
match_sniper_alerts,
|
||||||
|
CronTrigger(minute='*/30'), # Every 30 minutes
|
||||||
|
id="sniper_matching",
|
||||||
|
name="Sniper Alert Matching (30m)",
|
||||||
replace_existing=True,
|
replace_existing=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -220,7 +238,9 @@ def setup_scheduler():
|
|||||||
f"\n - Tycoon domain check every 10 minutes"
|
f"\n - Tycoon domain check every 10 minutes"
|
||||||
f"\n - TLD price scrape at 03:00 UTC"
|
f"\n - TLD price scrape at 03:00 UTC"
|
||||||
f"\n - Price change alerts at 04:00 UTC"
|
f"\n - Price change alerts at 04:00 UTC"
|
||||||
f"\n - Auction scrape every hour at :30"
|
f"\n - Auction scrape every 2 hours at :30"
|
||||||
|
f"\n - Expired auction cleanup every 15 minutes"
|
||||||
|
f"\n - Sniper alert matching every 30 minutes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -302,6 +322,58 @@ async def check_price_changes():
|
|||||||
logger.exception(f"Price change check failed: {e}")
|
logger.exception(f"Price change check failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def cleanup_expired_auctions():
|
||||||
|
"""
|
||||||
|
Mark expired auctions as inactive and delete very old ones.
|
||||||
|
|
||||||
|
This is CRITICAL for data freshness! Without this, the Market page
|
||||||
|
would show auctions that ended days ago.
|
||||||
|
|
||||||
|
Runs every 15 minutes to ensure users always see live data.
|
||||||
|
"""
|
||||||
|
from app.models.auction import DomainAuction
|
||||||
|
from sqlalchemy import update, delete
|
||||||
|
|
||||||
|
logger.info("Starting expired auction cleanup...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with AsyncSessionLocal() as db:
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
# 1. Mark ended auctions as inactive
|
||||||
|
stmt = (
|
||||||
|
update(DomainAuction)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.end_time < now,
|
||||||
|
DomainAuction.is_active == True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.values(is_active=False)
|
||||||
|
)
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
marked_inactive = result.rowcount
|
||||||
|
|
||||||
|
# 2. Delete very old inactive auctions (> 7 days)
|
||||||
|
cutoff = now - timedelta(days=7)
|
||||||
|
stmt = delete(DomainAuction).where(
|
||||||
|
and_(
|
||||||
|
DomainAuction.is_active == False,
|
||||||
|
DomainAuction.end_time < cutoff
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
deleted = result.rowcount
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
if marked_inactive > 0 or deleted > 0:
|
||||||
|
logger.info(f"Auction cleanup: {marked_inactive} marked inactive, {deleted} deleted")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Auction cleanup failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def scrape_auctions():
|
async def scrape_auctions():
|
||||||
"""Scheduled task to scrape domain auctions from public sources."""
|
"""Scheduled task to scrape domain auctions from public sources."""
|
||||||
from app.services.auction_scraper import auction_scraper
|
from app.services.auction_scraper import auction_scraper
|
||||||
|
|||||||
Reference in New Issue
Block a user