feat: add blockchain RPC blocks-range endpoint and marketplace bid listing
Blockchain Node: - Replace /blocks (pagination) with /blocks-range (height range query) - Add start/end height parameters with 1000-block max range validation - Return blocks in ascending height order instead of descending - Update metrics names (rpc_get_blocks_range_*) - Remove total count from response (return start/end/count instead) Coordinator API: - Add effective_url property to DatabaseConfig (SQLite/PostgreSQL defaults
This commit is contained in:
@@ -12,7 +12,15 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from ..schemas import ExchangePaymentRequest, ExchangePaymentResponse
|
||||
from ..schemas import (
|
||||
ExchangePaymentRequest,
|
||||
ExchangePaymentResponse,
|
||||
ExchangeRatesResponse,
|
||||
PaymentStatusResponse,
|
||||
MarketStatsResponse,
|
||||
WalletBalanceResponse,
|
||||
WalletInfoResponse
|
||||
)
|
||||
from ..services.bitcoin_wallet import get_wallet_balance, get_wallet_info
|
||||
|
||||
router = APIRouter(tags=["exchange"])
|
||||
@@ -70,7 +78,8 @@ async def create_payment(
|
||||
|
||||
return payment
|
||||
|
||||
@router.get("/exchange/payment-status/{payment_id}")
|
||||
|
||||
@router.get("/exchange/payment-status/{payment_id}", response_model=PaymentStatusResponse)
|
||||
async def get_payment_status(payment_id: str) -> Dict[str, Any]:
|
||||
"""Get payment status"""
|
||||
|
||||
@@ -85,6 +94,7 @@ async def get_payment_status(payment_id: str) -> Dict[str, Any]:
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
@router.post("/exchange/confirm-payment/{payment_id}")
|
||||
async def confirm_payment(
|
||||
payment_id: str,
|
||||
@@ -121,18 +131,20 @@ async def confirm_payment(
|
||||
'aitbc_amount': payment['aitbc_amount']
|
||||
}
|
||||
|
||||
@router.get("/exchange/rates")
|
||||
async def get_exchange_rates() -> Dict[str, float]:
|
||||
|
||||
@router.get("/exchange/rates", response_model=ExchangeRatesResponse)
|
||||
async def get_exchange_rates() -> ExchangeRatesResponse:
|
||||
"""Get current exchange rates"""
|
||||
|
||||
return {
|
||||
'btc_to_aitbc': BITCOIN_CONFIG['exchange_rate'],
|
||||
'aitbc_to_btc': 1.0 / BITCOIN_CONFIG['exchange_rate'],
|
||||
'fee_percent': 0.5
|
||||
}
|
||||
return ExchangeRatesResponse(
|
||||
btc_to_aitbc=BITCOIN_CONFIG['exchange_rate'],
|
||||
aitbc_to_btc=1.0 / BITCOIN_CONFIG['exchange_rate'],
|
||||
fee_percent=0.5
|
||||
)
|
||||
|
||||
@router.get("/exchange/market-stats")
|
||||
async def get_market_stats() -> Dict[str, Any]:
|
||||
|
||||
@router.get("/exchange/market-stats", response_model=MarketStatsResponse)
|
||||
async def get_market_stats() -> MarketStatsResponse:
|
||||
"""Get market statistics"""
|
||||
|
||||
# Calculate 24h volume from payments
|
||||
@@ -148,28 +160,32 @@ async def get_market_stats() -> Dict[str, Any]:
|
||||
base_price = 1.0 / BITCOIN_CONFIG['exchange_rate']
|
||||
price_change_percent = 5.2 # Simulated +5.2%
|
||||
|
||||
return {
|
||||
'price': base_price,
|
||||
'price_change_24h': price_change_percent,
|
||||
'daily_volume': daily_volume,
|
||||
'daily_volume_btc': daily_volume / BITCOIN_CONFIG['exchange_rate'],
|
||||
'total_payments': len([p for p in payments.values() if p['status'] == 'confirmed']),
|
||||
'pending_payments': len([p for p in payments.values() if p['status'] == 'pending'])
|
||||
}
|
||||
return MarketStatsResponse(
|
||||
price=base_price,
|
||||
price_change_24h=price_change_percent,
|
||||
daily_volume=daily_volume,
|
||||
daily_volume_btc=daily_volume / BITCOIN_CONFIG['exchange_rate'],
|
||||
total_payments=len([p for p in payments.values() if p['status'] == 'confirmed']),
|
||||
pending_payments=len([p for p in payments.values() if p['status'] == 'pending'])
|
||||
)
|
||||
|
||||
@router.get("/exchange/wallet/balance")
|
||||
async def get_wallet_balance_api() -> Dict[str, Any]:
|
||||
|
||||
@router.get("/exchange/wallet/balance", response_model=WalletBalanceResponse)
|
||||
async def get_wallet_balance_api() -> WalletBalanceResponse:
|
||||
"""Get Bitcoin wallet balance"""
|
||||
try:
|
||||
return get_wallet_balance()
|
||||
balance_data = get_wallet_balance()
|
||||
return WalletBalanceResponse(**balance_data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/exchange/wallet/info")
|
||||
async def get_wallet_info_api() -> Dict[str, Any]:
|
||||
|
||||
@router.get("/exchange/wallet/info", response_model=WalletInfoResponse)
|
||||
async def get_wallet_info_api() -> WalletInfoResponse:
|
||||
"""Get comprehensive wallet information"""
|
||||
try:
|
||||
return get_wallet_info()
|
||||
wallet_data = get_wallet_info()
|
||||
return WalletInfoResponse(**wallet_data)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import status as http_status
|
||||
|
||||
from ..schemas import MarketplaceBidRequest, MarketplaceOfferView, MarketplaceStatsView
|
||||
from ..schemas import MarketplaceBidRequest, MarketplaceOfferView, MarketplaceStatsView, MarketplaceBidView
|
||||
from ..services import MarketplaceService
|
||||
from ..storage import SessionDep
|
||||
from ..metrics import marketplace_requests_total, marketplace_errors_total
|
||||
@@ -74,3 +74,51 @@ async def submit_marketplace_bid(
|
||||
except Exception:
|
||||
marketplace_errors_total.labels(endpoint="/marketplace/bids", method="POST", error_type="internal").inc()
|
||||
raise
|
||||
|
||||
|
||||
@router.get(
|
||||
"/marketplace/bids",
|
||||
response_model=list[MarketplaceBidView],
|
||||
summary="List marketplace bids",
|
||||
)
|
||||
async def list_marketplace_bids(
|
||||
*,
|
||||
session: SessionDep,
|
||||
status_filter: str | None = Query(default=None, alias="status", description="Filter by bid status"),
|
||||
provider_filter: str | None = Query(default=None, alias="provider", description="Filter by provider ID"),
|
||||
limit: int = Query(default=100, ge=1, le=500),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
) -> list[MarketplaceBidView]:
|
||||
marketplace_requests_total.labels(endpoint="/marketplace/bids", method="GET").inc()
|
||||
service = _get_service(session)
|
||||
try:
|
||||
return service.list_bids(status=status_filter, provider=provider_filter, limit=limit, offset=offset)
|
||||
except ValueError:
|
||||
marketplace_errors_total.labels(endpoint="/marketplace/bids", method="GET", error_type="invalid_request").inc()
|
||||
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail="invalid filter") from None
|
||||
except Exception:
|
||||
marketplace_errors_total.labels(endpoint="/marketplace/bids", method="GET", error_type="internal").inc()
|
||||
raise
|
||||
|
||||
|
||||
@router.get(
|
||||
"/marketplace/bids/{bid_id}",
|
||||
response_model=MarketplaceBidView,
|
||||
summary="Get bid details",
|
||||
)
|
||||
async def get_marketplace_bid(
|
||||
bid_id: str,
|
||||
session: SessionDep,
|
||||
) -> MarketplaceBidView:
|
||||
marketplace_requests_total.labels(endpoint="/marketplace/bids/{bid_id}", method="GET").inc()
|
||||
service = _get_service(session)
|
||||
try:
|
||||
bid = service.get_bid(bid_id)
|
||||
if not bid:
|
||||
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="bid not found")
|
||||
return bid
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
marketplace_errors_total.labels(endpoint="/marketplace/bids/{bid_id}", method="GET", error_type="internal").inc()
|
||||
raise
|
||||
|
||||
@@ -14,6 +14,8 @@ class WebVitalsEntry(BaseModel):
|
||||
name: str
|
||||
startTime: Optional[float] = None
|
||||
duration: Optional[float] = None
|
||||
value: Optional[float] = None
|
||||
hadRecentInput: Optional[bool] = None
|
||||
|
||||
class WebVitalsMetric(BaseModel):
|
||||
name: str
|
||||
@@ -31,6 +33,20 @@ async def collect_web_vitals(metric: WebVitalsMetric):
|
||||
This endpoint receives Core Web Vitals (LCP, FID, CLS, TTFB, FCP) for monitoring.
|
||||
"""
|
||||
try:
|
||||
# Filter entries to only include supported fields
|
||||
filtered_entries = []
|
||||
for entry in metric.entries:
|
||||
filtered_entry = {
|
||||
"name": entry.name,
|
||||
"startTime": entry.startTime,
|
||||
"duration": entry.duration,
|
||||
"value": entry.value,
|
||||
"hadRecentInput": entry.hadRecentInput
|
||||
}
|
||||
# Remove None values
|
||||
filtered_entry = {k: v for k, v in filtered_entry.items() if v is not None}
|
||||
filtered_entries.append(filtered_entry)
|
||||
|
||||
# Log the metric for monitoring/analysis
|
||||
logging.info(f"Web Vitals - {metric.name}: {metric.value}ms (ID: {metric.id}) from {metric.url or 'unknown'}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user