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
684 lines
23 KiB
Python
684 lines
23 KiB
Python
"""
|
|
Yield Domain API endpoints.
|
|
|
|
Manages domain activation for yield/intent routing and revenue tracking.
|
|
"""
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
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
|
|
from app.models.yield_domain import YieldDomain, YieldTransaction, YieldPayout, AffiliatePartner
|
|
from app.config import get_settings
|
|
|
|
settings = get_settings()
|
|
from app.schemas.yield_domain import (
|
|
YieldDomainCreate,
|
|
YieldDomainUpdate,
|
|
YieldDomainResponse,
|
|
YieldDomainListResponse,
|
|
YieldTransactionResponse,
|
|
YieldTransactionListResponse,
|
|
YieldPayoutResponse,
|
|
YieldPayoutListResponse,
|
|
YieldDashboardStats,
|
|
YieldDashboardResponse,
|
|
DomainYieldAnalysis,
|
|
IntentAnalysis,
|
|
YieldValueEstimate,
|
|
AffiliatePartnerResponse,
|
|
DNSVerificationResult,
|
|
DNSSetupInstructions,
|
|
ActivateYieldRequest,
|
|
ActivateYieldResponse,
|
|
)
|
|
from app.services.intent_detector import (
|
|
detect_domain_intent,
|
|
estimate_domain_yield,
|
|
get_intent_detector,
|
|
)
|
|
|
|
router = APIRouter(prefix="/yield", tags=["yield"])
|
|
|
|
# DNS Configuration (would be in config in production)
|
|
YIELD_NAMESERVERS = ["ns1.pounce.io", "ns2.pounce.io"]
|
|
YIELD_CNAME_TARGET = "yield.pounce.io"
|
|
|
|
|
|
# ============================================================================
|
|
# Intent Analysis (Public)
|
|
# ============================================================================
|
|
|
|
@router.post("/analyze", response_model=DomainYieldAnalysis)
|
|
async def analyze_domain_intent(
|
|
domain: str = Query(..., min_length=3, description="Domain to analyze"),
|
|
):
|
|
"""
|
|
Analyze a domain's intent and estimate yield potential.
|
|
|
|
This endpoint is public - no authentication required.
|
|
"""
|
|
analysis = estimate_domain_yield(domain)
|
|
|
|
intent_result = detect_domain_intent(domain)
|
|
|
|
return DomainYieldAnalysis(
|
|
domain=domain,
|
|
intent=IntentAnalysis(
|
|
category=intent_result.category,
|
|
subcategory=intent_result.subcategory,
|
|
confidence=intent_result.confidence,
|
|
keywords_matched=intent_result.keywords_matched,
|
|
suggested_partners=intent_result.suggested_partners,
|
|
monetization_potential=intent_result.monetization_potential,
|
|
),
|
|
value=YieldValueEstimate(
|
|
estimated_monthly_min=analysis["value"]["estimated_monthly_min"],
|
|
estimated_monthly_max=analysis["value"]["estimated_monthly_max"],
|
|
currency=analysis["value"]["currency"],
|
|
potential=analysis["value"]["potential"],
|
|
confidence=analysis["value"]["confidence"],
|
|
geo=analysis["value"]["geo"],
|
|
),
|
|
partners=analysis["partners"],
|
|
monetization_potential=analysis["monetization_potential"],
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Dashboard
|
|
# ============================================================================
|
|
|
|
@router.get("/dashboard", response_model=YieldDashboardResponse)
|
|
async def get_yield_dashboard(
|
|
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
|
|
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 (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")
|
|
total_pending = sum(1 for d in domains if d.status in ["pending", "verifying"])
|
|
lifetime_revenue = sum(d.total_revenue for d in domains)
|
|
lifetime_clicks = sum(d.total_clicks for d in domains)
|
|
lifetime_conversions = sum(d.total_conversions for d in domains)
|
|
|
|
# Pending payout
|
|
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 = []
|
|
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]
|
|
|
|
stats = YieldDashboardStats(
|
|
total_domains=len(domains),
|
|
active_domains=total_active,
|
|
pending_domains=total_pending,
|
|
monthly_revenue=monthly_revenue,
|
|
monthly_clicks=monthly_clicks,
|
|
monthly_conversions=monthly_conversions,
|
|
lifetime_revenue=lifetime_revenue,
|
|
lifetime_clicks=lifetime_clicks,
|
|
lifetime_conversions=lifetime_conversions,
|
|
pending_payout=pending_payout,
|
|
next_payout_date=month_start + timedelta(days=32), # Approx next month
|
|
currency="CHF",
|
|
)
|
|
|
|
return YieldDashboardResponse(
|
|
stats=stats,
|
|
domains=[_domain_to_response(d) for d in domains],
|
|
recent_transactions=[_tx_to_response(tx) for tx in recent_txs],
|
|
top_domains=[_domain_to_response(d) for d in top_domains],
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Domain Management
|
|
# ============================================================================
|
|
|
|
@router.get("/domains", response_model=YieldDomainListResponse)
|
|
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: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
List user's yield domains.
|
|
"""
|
|
query = select(YieldDomain).where(YieldDomain.user_id == current_user.id)
|
|
|
|
if status:
|
|
query = query.where(YieldDomain.status == status)
|
|
|
|
# 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
|
|
|
|
# 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)
|
|
|
|
return YieldDomainListResponse(
|
|
domains=[_domain_to_response(d) for d in domains],
|
|
total=total,
|
|
total_active=total_active,
|
|
total_revenue=total_revenue,
|
|
total_clicks=total_clicks,
|
|
)
|
|
|
|
|
|
@router.get("/domains/{domain_id}", response_model=YieldDomainResponse)
|
|
async def get_yield_domain(
|
|
domain_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Get details of a specific yield domain.
|
|
"""
|
|
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")
|
|
|
|
return _domain_to_response(domain)
|
|
|
|
|
|
@router.post("/activate", response_model=ActivateYieldResponse)
|
|
async def activate_domain_for_yield(
|
|
request: ActivateYieldRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Activate a domain for yield/intent routing.
|
|
|
|
This creates the yield domain record and returns DNS setup instructions.
|
|
"""
|
|
domain = request.domain.lower().strip()
|
|
|
|
# Check if domain already exists
|
|
existing = db.query(YieldDomain).filter(YieldDomain.domain == domain).first()
|
|
if existing:
|
|
if existing.user_id == current_user.id:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Domain already activated for yield"
|
|
)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Domain is already registered by another user"
|
|
)
|
|
|
|
# Analyze domain intent
|
|
intent_result = detect_domain_intent(domain)
|
|
value_estimate = get_intent_detector().estimate_value(domain)
|
|
|
|
# Create yield domain record
|
|
yield_domain = YieldDomain(
|
|
user_id=current_user.id,
|
|
domain=domain,
|
|
detected_intent=f"{intent_result.category}_{intent_result.subcategory}" if intent_result.subcategory else intent_result.category,
|
|
intent_confidence=intent_result.confidence,
|
|
intent_keywords=json.dumps(intent_result.keywords_matched),
|
|
status="pending",
|
|
)
|
|
|
|
# Find best matching partner
|
|
if intent_result.suggested_partners:
|
|
partner = db.query(AffiliatePartner).filter(
|
|
AffiliatePartner.slug == intent_result.suggested_partners[0],
|
|
AffiliatePartner.is_active == True,
|
|
).first()
|
|
if partner:
|
|
yield_domain.partner_id = partner.id
|
|
yield_domain.active_route = partner.slug
|
|
|
|
db.add(yield_domain)
|
|
db.commit()
|
|
db.refresh(yield_domain)
|
|
|
|
# Create DNS instructions
|
|
dns_instructions = DNSSetupInstructions(
|
|
domain=domain,
|
|
nameservers=YIELD_NAMESERVERS,
|
|
cname_host="@",
|
|
cname_target=YIELD_CNAME_TARGET,
|
|
verification_url=f"{settings.site_url}/api/v1/yield/verify/{yield_domain.id}",
|
|
)
|
|
|
|
return ActivateYieldResponse(
|
|
domain_id=yield_domain.id,
|
|
domain=domain,
|
|
status=yield_domain.status,
|
|
intent=IntentAnalysis(
|
|
category=intent_result.category,
|
|
subcategory=intent_result.subcategory,
|
|
confidence=intent_result.confidence,
|
|
keywords_matched=intent_result.keywords_matched,
|
|
suggested_partners=intent_result.suggested_partners,
|
|
monetization_potential=intent_result.monetization_potential,
|
|
),
|
|
value_estimate=YieldValueEstimate(
|
|
estimated_monthly_min=value_estimate["estimated_monthly_min"],
|
|
estimated_monthly_max=value_estimate["estimated_monthly_max"],
|
|
currency=value_estimate["currency"],
|
|
potential=value_estimate["potential"],
|
|
confidence=value_estimate["confidence"],
|
|
geo=value_estimate["geo"],
|
|
),
|
|
dns_instructions=dns_instructions,
|
|
message="Domain registered! Point your DNS to our nameservers to complete activation.",
|
|
)
|
|
|
|
|
|
@router.post("/domains/{domain_id}/verify", response_model=DNSVerificationResult)
|
|
async def verify_domain_dns(
|
|
domain_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Verify DNS configuration for a yield domain.
|
|
"""
|
|
domain = db.query(YieldDomain).filter(
|
|
YieldDomain.id == domain_id,
|
|
YieldDomain.user_id == current_user.id,
|
|
).first()
|
|
|
|
if not domain:
|
|
raise HTTPException(status_code=404, detail="Yield domain not found")
|
|
|
|
# Perform DNS check (simplified - in production use dnspython)
|
|
verified = False
|
|
actual_ns = []
|
|
error = None
|
|
|
|
try:
|
|
import dns.resolver
|
|
|
|
# Check nameservers
|
|
try:
|
|
answers = dns.resolver.resolve(domain.domain, 'NS')
|
|
actual_ns = [str(rr.target).rstrip('.') for rr in answers]
|
|
|
|
# Check if our nameservers are set
|
|
our_ns_set = set(ns.lower() for ns in YIELD_NAMESERVERS)
|
|
actual_ns_set = set(ns.lower() for ns in actual_ns)
|
|
|
|
if our_ns_set.issubset(actual_ns_set):
|
|
verified = True
|
|
except dns.resolver.NXDOMAIN:
|
|
error = "Domain does not exist"
|
|
except dns.resolver.NoAnswer:
|
|
# Try CNAME instead
|
|
try:
|
|
cname_answers = dns.resolver.resolve(domain.domain, 'CNAME')
|
|
for rr in cname_answers:
|
|
if str(rr.target).rstrip('.').lower() == YIELD_CNAME_TARGET.lower():
|
|
verified = True
|
|
break
|
|
except Exception:
|
|
error = "No NS or CNAME records found"
|
|
except Exception as e:
|
|
error = str(e)
|
|
|
|
except ImportError:
|
|
# dnspython not installed - simulate for development
|
|
verified = True # Auto-verify in dev
|
|
actual_ns = YIELD_NAMESERVERS
|
|
|
|
# Update domain status
|
|
if verified and not domain.dns_verified:
|
|
domain.dns_verified = True
|
|
domain.dns_verified_at = datetime.utcnow()
|
|
domain.status = "active"
|
|
domain.activated_at = datetime.utcnow()
|
|
db.commit()
|
|
|
|
return DNSVerificationResult(
|
|
domain=domain.domain,
|
|
verified=verified,
|
|
expected_ns=YIELD_NAMESERVERS,
|
|
actual_ns=actual_ns,
|
|
cname_ok=verified and not actual_ns,
|
|
error=error,
|
|
checked_at=datetime.utcnow(),
|
|
)
|
|
|
|
|
|
@router.patch("/domains/{domain_id}", response_model=YieldDomainResponse)
|
|
async def update_yield_domain(
|
|
domain_id: int,
|
|
update: YieldDomainUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Update yield domain settings.
|
|
"""
|
|
domain = db.query(YieldDomain).filter(
|
|
YieldDomain.id == domain_id,
|
|
YieldDomain.user_id == current_user.id,
|
|
).first()
|
|
|
|
if not domain:
|
|
raise HTTPException(status_code=404, detail="Yield domain not found")
|
|
|
|
# Apply updates
|
|
if update.active_route is not None:
|
|
# Validate partner exists
|
|
partner = db.query(AffiliatePartner).filter(
|
|
AffiliatePartner.slug == update.active_route,
|
|
AffiliatePartner.is_active == True,
|
|
).first()
|
|
if not partner:
|
|
raise HTTPException(status_code=400, detail="Invalid partner route")
|
|
domain.active_route = update.active_route
|
|
domain.partner_id = partner.id
|
|
|
|
if update.landing_page_url is not None:
|
|
domain.landing_page_url = update.landing_page_url
|
|
|
|
if update.status is not None:
|
|
if update.status == "paused":
|
|
domain.status = "paused"
|
|
domain.paused_at = datetime.utcnow()
|
|
elif update.status == "active" and domain.dns_verified:
|
|
domain.status = "active"
|
|
domain.paused_at = None
|
|
|
|
db.commit()
|
|
db.refresh(domain)
|
|
|
|
return _domain_to_response(domain)
|
|
|
|
|
|
@router.delete("/domains/{domain_id}")
|
|
async def delete_yield_domain(
|
|
domain_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Remove a domain from yield program.
|
|
"""
|
|
domain = db.query(YieldDomain).filter(
|
|
YieldDomain.id == domain_id,
|
|
YieldDomain.user_id == current_user.id,
|
|
).first()
|
|
|
|
if not domain:
|
|
raise HTTPException(status_code=404, detail="Yield domain not found")
|
|
|
|
db.delete(domain)
|
|
db.commit()
|
|
|
|
return {"message": "Yield domain removed"}
|
|
|
|
|
|
# ============================================================================
|
|
# Transactions
|
|
# ============================================================================
|
|
|
|
@router.get("/transactions", response_model=YieldTransactionListResponse)
|
|
async def list_transactions(
|
|
domain_id: Optional[int] = Query(None),
|
|
status: Optional[str] = Query(None),
|
|
limit: int = Query(50, le=100),
|
|
offset: int = Query(0, ge=0),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
List yield transactions for user's domains.
|
|
"""
|
|
# Get user's domain IDs
|
|
domain_ids = db.query(YieldDomain.id).filter(
|
|
YieldDomain.user_id == current_user.id
|
|
).subquery()
|
|
|
|
query = db.query(YieldTransaction).filter(
|
|
YieldTransaction.yield_domain_id.in_(domain_ids)
|
|
)
|
|
|
|
if domain_id:
|
|
query = query.filter(YieldTransaction.yield_domain_id == domain_id)
|
|
|
|
if status:
|
|
query = query.filter(YieldTransaction.status == status)
|
|
|
|
total = query.count()
|
|
transactions = query.order_by(YieldTransaction.created_at.desc()).offset(offset).limit(limit).all()
|
|
|
|
# Aggregates
|
|
total_gross = sum(tx.gross_amount for tx in transactions)
|
|
total_net = sum(tx.net_amount for tx in transactions)
|
|
|
|
return YieldTransactionListResponse(
|
|
transactions=[_tx_to_response(tx) for tx in transactions],
|
|
total=total,
|
|
total_gross=total_gross,
|
|
total_net=total_net,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Payouts
|
|
# ============================================================================
|
|
|
|
@router.get("/payouts", response_model=YieldPayoutListResponse)
|
|
async def list_payouts(
|
|
status: Optional[str] = Query(None),
|
|
limit: int = Query(20, le=50),
|
|
offset: int = Query(0, ge=0),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""
|
|
List user's yield payouts.
|
|
"""
|
|
query = db.query(YieldPayout).filter(YieldPayout.user_id == current_user.id)
|
|
|
|
if status:
|
|
query = query.filter(YieldPayout.status == status)
|
|
|
|
total = query.count()
|
|
payouts = query.order_by(YieldPayout.created_at.desc()).offset(offset).limit(limit).all()
|
|
|
|
# Aggregates
|
|
total_paid = sum(p.amount for p in payouts if p.status == "completed")
|
|
total_pending = sum(p.amount for p in payouts if p.status in ["pending", "processing"])
|
|
|
|
return YieldPayoutListResponse(
|
|
payouts=[_payout_to_response(p) for p in payouts],
|
|
total=total,
|
|
total_paid=total_paid,
|
|
total_pending=total_pending,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Partners (Public info)
|
|
# ============================================================================
|
|
|
|
@router.get("/partners", response_model=list[AffiliatePartnerResponse])
|
|
async def list_partners(
|
|
category: Optional[str] = Query(None, description="Filter by intent category"),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
List available affiliate partners.
|
|
"""
|
|
query = db.query(AffiliatePartner).filter(AffiliatePartner.is_active == True)
|
|
|
|
partners = query.order_by(AffiliatePartner.priority.desc()).all()
|
|
|
|
# Filter by category if specified
|
|
if category:
|
|
partners = [p for p in partners if category in p.intent_list]
|
|
|
|
return [
|
|
AffiliatePartnerResponse(
|
|
slug=p.slug,
|
|
name=p.name,
|
|
network=p.network,
|
|
intent_categories=p.intent_list,
|
|
geo_countries=p.country_list,
|
|
payout_type=p.payout_type,
|
|
description=p.description,
|
|
logo_url=p.logo_url,
|
|
)
|
|
for p in partners
|
|
]
|
|
|
|
|
|
# ============================================================================
|
|
# Helpers
|
|
# ============================================================================
|
|
|
|
def _domain_to_response(domain: YieldDomain) -> YieldDomainResponse:
|
|
"""Convert YieldDomain model to response schema."""
|
|
return YieldDomainResponse(
|
|
id=domain.id,
|
|
domain=domain.domain,
|
|
status=domain.status,
|
|
detected_intent=domain.detected_intent,
|
|
intent_confidence=domain.intent_confidence,
|
|
active_route=domain.active_route,
|
|
partner_name=domain.partner.name if domain.partner else None,
|
|
dns_verified=domain.dns_verified,
|
|
dns_verified_at=domain.dns_verified_at,
|
|
total_clicks=domain.total_clicks,
|
|
total_conversions=domain.total_conversions,
|
|
total_revenue=domain.total_revenue,
|
|
currency=domain.currency,
|
|
activated_at=domain.activated_at,
|
|
created_at=domain.created_at,
|
|
)
|
|
|
|
|
|
def _tx_to_response(tx: YieldTransaction) -> YieldTransactionResponse:
|
|
"""Convert YieldTransaction model to response schema."""
|
|
return YieldTransactionResponse(
|
|
id=tx.id,
|
|
event_type=tx.event_type,
|
|
partner_slug=tx.partner_slug,
|
|
gross_amount=tx.gross_amount,
|
|
net_amount=tx.net_amount,
|
|
currency=tx.currency,
|
|
status=tx.status,
|
|
geo_country=tx.geo_country,
|
|
created_at=tx.created_at,
|
|
confirmed_at=tx.confirmed_at,
|
|
)
|
|
|
|
|
|
def _payout_to_response(payout: YieldPayout) -> YieldPayoutResponse:
|
|
"""Convert YieldPayout model to response schema."""
|
|
return YieldPayoutResponse(
|
|
id=payout.id,
|
|
amount=payout.amount,
|
|
currency=payout.currency,
|
|
period_start=payout.period_start,
|
|
period_end=payout.period_end,
|
|
transaction_count=payout.transaction_count,
|
|
status=payout.status,
|
|
payment_method=payout.payment_method,
|
|
payment_reference=payout.payment_reference,
|
|
created_at=payout.created_at,
|
|
completed_at=payout.completed_at,
|
|
)
|
|
|
|
|
|
# Missing import
|
|
from sqlalchemy import Integer
|
|
|