diff --git a/backend/app/api/listings.py b/backend/app/api/listings.py
index 2f20ad7..96db302 100644
--- a/backend/app/api/listings.py
+++ b/backend/app/api/listings.py
@@ -533,7 +533,13 @@ async def create_listing(
listing_count = user_listings.scalar() or 0
# Listing limits by tier (from pounce_pricing.md)
- tier = current_user.subscription.tier if current_user.subscription else "scout"
+ # Load subscription separately to avoid async lazy loading issues
+ from app.models.subscription import Subscription
+ sub_result = await db.execute(
+ select(Subscription).where(Subscription.user_id == current_user.id)
+ )
+ subscription = sub_result.scalar_one_or_none()
+ tier = subscription.tier if subscription else "scout"
limits = {"scout": 0, "trader": 5, "tycoon": 50}
max_listings = limits.get(tier, 0)
diff --git a/backend/app/api/yield_domains.py b/backend/app/api/yield_domains.py
index 1255a0b..b4fcff4 100644
--- a/backend/app/api/yield_domains.py
+++ b/backend/app/api/yield_domains.py
@@ -9,8 +9,8 @@ from decimal import Decimal
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
-from sqlalchemy import func, and_, or_
-from sqlalchemy.orm import Session
+from sqlalchemy import func, and_, or_, Integer, case, select
+from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db, get_current_user
from app.models.user import User
@@ -97,31 +97,47 @@ async def analyze_domain_intent(
@router.get("/dashboard", response_model=YieldDashboardResponse)
async def get_yield_dashboard(
- db: Session = Depends(get_db),
+ db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Get yield dashboard with stats, domains, and recent transactions.
"""
# Get user's yield domains
- domains = db.query(YieldDomain).filter(
- YieldDomain.user_id == current_user.id
- ).order_by(YieldDomain.total_revenue.desc()).all()
+ result = await db.execute(
+ select(YieldDomain)
+ .where(YieldDomain.user_id == current_user.id)
+ .order_by(YieldDomain.total_revenue.desc())
+ )
+ domains = list(result.scalars().all())
# Calculate stats
now = datetime.utcnow()
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
- # Monthly stats from transactions
- monthly_stats = db.query(
- func.count(YieldTransaction.id).label("count"),
- func.sum(YieldTransaction.net_amount).label("revenue"),
- func.sum(func.cast(YieldTransaction.event_type == "click", Integer)).label("clicks"),
- func.sum(func.cast(YieldTransaction.event_type.in_(["lead", "sale"]), Integer)).label("conversions"),
- ).join(YieldDomain).filter(
- YieldDomain.user_id == current_user.id,
- YieldTransaction.created_at >= month_start,
- ).first()
+ # Monthly stats from transactions (simplified for async)
+ monthly_revenue = Decimal("0")
+ monthly_clicks = 0
+ monthly_conversions = 0
+
+ if domains:
+ domain_ids = [d.id for d in domains]
+ monthly_result = await db.execute(
+ select(
+ func.count(YieldTransaction.id).label("count"),
+ func.coalesce(func.sum(YieldTransaction.net_amount), 0).label("revenue"),
+ func.sum(case((YieldTransaction.event_type == "click", 1), else_=0)).label("clicks"),
+ func.sum(case((YieldTransaction.event_type.in_(["lead", "sale"]), 1), else_=0)).label("conversions"),
+ ).where(
+ YieldTransaction.yield_domain_id.in_(domain_ids),
+ YieldTransaction.created_at >= month_start,
+ )
+ )
+ monthly_stats = monthly_result.first()
+ if monthly_stats:
+ monthly_revenue = monthly_stats.revenue or Decimal("0")
+ monthly_clicks = monthly_stats.clicks or 0
+ monthly_conversions = monthly_stats.conversions or 0
# Aggregate domain stats
total_active = sum(1 for d in domains if d.status == "active")
@@ -131,16 +147,29 @@ async def get_yield_dashboard(
lifetime_conversions = sum(d.total_conversions for d in domains)
# Pending payout
- pending_payout = db.query(func.sum(YieldTransaction.net_amount)).filter(
- YieldTransaction.yield_domain_id.in_([d.id for d in domains]),
- YieldTransaction.status == "confirmed",
- YieldTransaction.paid_at.is_(None),
- ).scalar() or Decimal("0")
+ pending_payout = Decimal("0")
+ if domains:
+ domain_ids = [d.id for d in domains]
+ pending_result = await db.execute(
+ select(func.coalesce(func.sum(YieldTransaction.net_amount), 0)).where(
+ YieldTransaction.yield_domain_id.in_(domain_ids),
+ YieldTransaction.status == "confirmed",
+ YieldTransaction.paid_at.is_(None),
+ )
+ )
+ pending_payout = pending_result.scalar() or Decimal("0")
# Get recent transactions
- recent_txs = db.query(YieldTransaction).join(YieldDomain).filter(
- YieldDomain.user_id == current_user.id,
- ).order_by(YieldTransaction.created_at.desc()).limit(10).all()
+ recent_txs = []
+ if domains:
+ domain_ids = [d.id for d in domains]
+ recent_result = await db.execute(
+ select(YieldTransaction)
+ .where(YieldTransaction.yield_domain_id.in_(domain_ids))
+ .order_by(YieldTransaction.created_at.desc())
+ .limit(10)
+ )
+ recent_txs = list(recent_result.scalars().all())
# Top performing domains
top_domains = sorted(domains, key=lambda d: d.total_revenue, reverse=True)[:5]
@@ -149,9 +178,9 @@ async def get_yield_dashboard(
total_domains=len(domains),
active_domains=total_active,
pending_domains=total_pending,
- monthly_revenue=monthly_stats.revenue or Decimal("0"),
- monthly_clicks=monthly_stats.clicks or 0,
- monthly_conversions=monthly_stats.conversions or 0,
+ monthly_revenue=monthly_revenue,
+ monthly_clicks=monthly_clicks,
+ monthly_conversions=monthly_conversions,
lifetime_revenue=lifetime_revenue,
lifetime_clicks=lifetime_clicks,
lifetime_conversions=lifetime_conversions,
@@ -177,22 +206,34 @@ async def list_yield_domains(
status: Optional[str] = Query(None, description="Filter by status"),
limit: int = Query(50, le=100),
offset: int = Query(0, ge=0),
- db: Session = Depends(get_db),
+ db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
List user's yield domains.
"""
- query = db.query(YieldDomain).filter(YieldDomain.user_id == current_user.id)
+ query = select(YieldDomain).where(YieldDomain.user_id == current_user.id)
if status:
- query = query.filter(YieldDomain.status == status)
+ query = query.where(YieldDomain.status == status)
- total = query.count()
- domains = query.order_by(YieldDomain.created_at.desc()).offset(offset).limit(limit).all()
+ # Get total count
+ count_result = await db.execute(
+ select(func.count(YieldDomain.id)).where(YieldDomain.user_id == current_user.id)
+ )
+ total = count_result.scalar() or 0
- # Aggregates
- all_domains = db.query(YieldDomain).filter(YieldDomain.user_id == current_user.id).all()
+ # Get domains
+ result = await db.execute(
+ query.order_by(YieldDomain.created_at.desc()).offset(offset).limit(limit)
+ )
+ domains = list(result.scalars().all())
+
+ # Aggregates from all domains
+ all_result = await db.execute(
+ select(YieldDomain).where(YieldDomain.user_id == current_user.id)
+ )
+ all_domains = list(all_result.scalars().all())
total_active = sum(1 for d in all_domains if d.status == "active")
total_revenue = sum(d.total_revenue for d in all_domains)
total_clicks = sum(d.total_clicks for d in all_domains)
@@ -209,16 +250,19 @@ async def list_yield_domains(
@router.get("/domains/{domain_id}", response_model=YieldDomainResponse)
async def get_yield_domain(
domain_id: int,
- db: Session = Depends(get_db),
+ db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Get details of a specific yield domain.
"""
- domain = db.query(YieldDomain).filter(
- YieldDomain.id == domain_id,
- YieldDomain.user_id == current_user.id,
- ).first()
+ result = await db.execute(
+ select(YieldDomain).where(
+ YieldDomain.id == domain_id,
+ YieldDomain.user_id == current_user.id,
+ )
+ )
+ domain = result.scalar_one_or_none()
if not domain:
raise HTTPException(status_code=404, detail="Yield domain not found")
diff --git a/frontend/src/app/terminal/radar/page.tsx b/frontend/src/app/terminal/radar/page.tsx
index 1585bdc..8160fe9 100644
--- a/frontend/src/app/terminal/radar/page.tsx
+++ b/frontend/src/app/terminal/radar/page.tsx
@@ -547,70 +547,111 @@ export default function RadarPage() {