ci: replace artifact upload with Gitea release API and add structured logging with rate limiting
- Replaced actions/upload-artifact with Gitea API release creation in build-miner-binary.yml - Added separate steps for uploading binary, package, and checksums to Gitea release - Added StructuredFormatter class for JSON log output in aitbc_logging.py - Added structured logging support with log_context() context manager and LogContext class - Added structured parameter to setup_logger() and configure_logging()
This commit is contained in:
@@ -77,11 +77,54 @@ jobs:
|
|||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "version=$VERSION"
|
echo "version=$VERSION"
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Create Gitea release
|
||||||
uses: actions/upload-artifact@v3
|
run: |
|
||||||
with:
|
cd "${{ env.WORKSPACE }}/repo"
|
||||||
name: miner-binary
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
path: |
|
|
||||||
${{ env.WORKSPACE }}/repo/scripts/gpu/aitbc-miner-debian
|
# Create release using Gitea API
|
||||||
${{ env.WORKSPACE }}/repo/scripts/gpu/aitbc-miner-debian-package.tar.gz
|
curl -X POST \
|
||||||
${{ env.WORKSPACE }}/repo/scripts/gpu/SHA256SUMS
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://gitea.bubuit.net:3000/api/v1/repos/oib/aitbc/releases \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"v${VERSION}\",
|
||||||
|
\"target_commitish\": \"main\",
|
||||||
|
\"name\": \"AITBC Miner v${VERSION}\",
|
||||||
|
\"body\": \"AITBC Miner for Debian Stable (trixie)\\n\\n## Changes\\n- See commit history for details\",
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": false
|
||||||
|
}"
|
||||||
|
|
||||||
|
- name: Upload binary to Gitea release
|
||||||
|
run: |
|
||||||
|
cd "${{ env.WORKSPACE }}/repo"
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
|
||||||
|
# Upload binary
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-F "attachment=@scripts/gpu/aitbc-miner-debian" \
|
||||||
|
http://gitea.bubuit.net:3000/api/v1/repos/oib/aitbc/releases/v${VERSION}/assets
|
||||||
|
|
||||||
|
- name: Upload package to Gitea release
|
||||||
|
run: |
|
||||||
|
cd "${{ env.WORKSPACE }}/repo"
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
|
||||||
|
# Upload package
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-F "attachment=@scripts/gpu/aitbc-miner-debian-package.tar.gz" \
|
||||||
|
http://gitea.bubuit.net:3000/api/v1/repos/oib/aitbc/releases/v${VERSION}/assets
|
||||||
|
|
||||||
|
- name: Upload checksums to Gitea release
|
||||||
|
run: |
|
||||||
|
cd "${{ env.WORKSPACE }}/repo"
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
|
|
||||||
|
# Upload checksums
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-F "attachment=@scripts/gpu/SHA256SUMS" \
|
||||||
|
http://gitea.bubuit.net:3000/api/v1/repos/oib/aitbc/releases/v${VERSION}/assets
|
||||||
|
|||||||
@@ -5,23 +5,56 @@ Centralized logging utilities for the AITBC project
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
import json
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
class StructuredFormatter(logging.Formatter):
|
||||||
|
"""Structured JSON formatter for log aggregation"""
|
||||||
|
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
"""Format log record as structured JSON"""
|
||||||
|
log_entry = {
|
||||||
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||||
|
"level": record.levelname,
|
||||||
|
"logger": record.name,
|
||||||
|
"message": record.getMessage(),
|
||||||
|
"module": record.module,
|
||||||
|
"function": record.funcName,
|
||||||
|
"line": record.lineno,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add extra fields if present
|
||||||
|
if hasattr(record, 'extra'):
|
||||||
|
log_entry.update(record.extra)
|
||||||
|
|
||||||
|
# Add exception info if present
|
||||||
|
if record.exc_info:
|
||||||
|
log_entry["exception"] = self.formatException(record.exc_info)
|
||||||
|
|
||||||
|
return json.dumps(log_entry)
|
||||||
|
|
||||||
def setup_logger(
|
def setup_logger(
|
||||||
name: str,
|
name: str,
|
||||||
level: str = "INFO",
|
level: str = "INFO",
|
||||||
format_string: Optional[str] = None
|
format_string: Optional[str] = None,
|
||||||
|
structured: bool = False
|
||||||
) -> logging.Logger:
|
) -> logging.Logger:
|
||||||
"""Setup a logger with consistent formatting"""
|
"""Setup a logger with consistent formatting"""
|
||||||
if format_string is None:
|
|
||||||
format_string = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
|
|
||||||
logger = logging.getLogger(name)
|
logger = logging.getLogger(name)
|
||||||
logger.setLevel(getattr(logging, level.upper()))
|
logger.setLevel(getattr(logging, level.upper()))
|
||||||
|
|
||||||
if not logger.handlers:
|
if not logger.handlers:
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
formatter = logging.Formatter(format_string)
|
|
||||||
|
if structured:
|
||||||
|
formatter = StructuredFormatter()
|
||||||
|
else:
|
||||||
|
if format_string is None:
|
||||||
|
format_string = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
formatter = logging.Formatter(format_string)
|
||||||
|
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
@@ -31,6 +64,48 @@ def get_logger(name: str) -> logging.Logger:
|
|||||||
"""Get a logger instance"""
|
"""Get a logger instance"""
|
||||||
return logging.getLogger(name)
|
return logging.getLogger(name)
|
||||||
|
|
||||||
def configure_logging(level: str = "INFO") -> None:
|
def configure_logging(level: str = "INFO", structured: bool = False) -> None:
|
||||||
"""Configure root logging level"""
|
"""Configure root logging level"""
|
||||||
logging.basicConfig(level=getattr(logging, level.upper()))
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(getattr(logging, level.upper()))
|
||||||
|
|
||||||
|
if structured:
|
||||||
|
# Remove existing handlers
|
||||||
|
for handler in root_logger.handlers[:]:
|
||||||
|
root_logger.removeHandler(handler)
|
||||||
|
|
||||||
|
# Add structured handler
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setFormatter(StructuredFormatter())
|
||||||
|
root_logger.addHandler(handler)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def log_context(**kwargs):
|
||||||
|
"""Context manager for adding contextual information to logs"""
|
||||||
|
logger = logging.getLogger()
|
||||||
|
extra = {'extra': kwargs}
|
||||||
|
|
||||||
|
class ContextFilter(logging.Filter):
|
||||||
|
def filter(self, record):
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(record, key, value)
|
||||||
|
return True
|
||||||
|
|
||||||
|
context_filter = ContextFilter()
|
||||||
|
logger.addFilter(context_filter)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
logger.removeFilter(context_filter)
|
||||||
|
|
||||||
|
class LogContext:
|
||||||
|
"""Class for adding contextual information to logs across multiple calls"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.context = kwargs
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return log_context(**self.context).__enter__()
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
pass
|
||||||
|
|||||||
414
aitbc/alerting.py
Normal file
414
aitbc/alerting.py
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
"""
|
||||||
|
AITBC Alerting Module
|
||||||
|
Alerting and notification system for AITBC applications
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Callable, Dict, Any, List, Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .aitbc_logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AlertSeverity(Enum):
|
||||||
|
"""Alert severity levels"""
|
||||||
|
INFO = "info"
|
||||||
|
WARNING = "warning"
|
||||||
|
ERROR = "error"
|
||||||
|
CRITICAL = "critical"
|
||||||
|
|
||||||
|
|
||||||
|
class AlertStatus(Enum):
|
||||||
|
"""Alert status"""
|
||||||
|
ACTIVE = "active"
|
||||||
|
ACKNOWLEDGED = "acknowledged"
|
||||||
|
RESOLVED = "resolved"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Alert:
|
||||||
|
"""Alert data structure"""
|
||||||
|
id: str
|
||||||
|
severity: AlertSeverity
|
||||||
|
title: str
|
||||||
|
message: str
|
||||||
|
source: str
|
||||||
|
timestamp: datetime = field(default_factory=datetime.utcnow)
|
||||||
|
status: AlertStatus = AlertStatus.ACTIVE
|
||||||
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
acknowledged_by: Optional[str] = None
|
||||||
|
acknowledged_at: Optional[datetime] = None
|
||||||
|
resolved_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""Convert alert to dictionary"""
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"severity": self.severity.value,
|
||||||
|
"title": self.title,
|
||||||
|
"message": self.message,
|
||||||
|
"source": self.source,
|
||||||
|
"timestamp": self.timestamp.isoformat(),
|
||||||
|
"status": self.status.value,
|
||||||
|
"metadata": self.metadata,
|
||||||
|
"acknowledged_by": self.acknowledged_by,
|
||||||
|
"acknowledged_at": self.acknowledged_at.isoformat() if self.acknowledged_at else None,
|
||||||
|
"resolved_at": self.resolved_at.isoformat() if self.resolved_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AlertChannel:
|
||||||
|
"""Base class for alert channels"""
|
||||||
|
|
||||||
|
async def send(self, alert: Alert) -> bool:
|
||||||
|
"""
|
||||||
|
Send alert through this channel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alert: Alert to send
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class LogAlertChannel(AlertChannel):
|
||||||
|
"""Log-based alert channel"""
|
||||||
|
|
||||||
|
async def send(self, alert: Alert) -> bool:
|
||||||
|
"""Send alert to logs"""
|
||||||
|
try:
|
||||||
|
log_level = {
|
||||||
|
AlertSeverity.INFO: logger.info,
|
||||||
|
AlertSeverity.WARNING: logger.warning,
|
||||||
|
AlertSeverity.ERROR: logger.error,
|
||||||
|
AlertSeverity.CRITICAL: logger.critical,
|
||||||
|
}.get(alert.severity, logger.info)
|
||||||
|
|
||||||
|
log_level(
|
||||||
|
f"Alert [{alert.severity.value.upper()}]: {alert.title}",
|
||||||
|
extra={
|
||||||
|
"alert_id": alert.id,
|
||||||
|
"severity": alert.severity.value,
|
||||||
|
"source": alert.source,
|
||||||
|
"metadata": alert.metadata,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send log alert: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookAlertChannel(AlertChannel):
|
||||||
|
"""Webhook-based alert channel"""
|
||||||
|
|
||||||
|
def __init__(self, url: str, headers: Optional[Dict[str, str]] = None):
|
||||||
|
"""
|
||||||
|
Initialize webhook channel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: Webhook URL
|
||||||
|
headers: HTTP headers
|
||||||
|
"""
|
||||||
|
self.url = url
|
||||||
|
self.headers = headers or {}
|
||||||
|
|
||||||
|
async def send(self, alert: Alert) -> bool:
|
||||||
|
"""Send alert via webhook"""
|
||||||
|
try:
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
self.url,
|
||||||
|
json=alert.to_dict(),
|
||||||
|
headers=self.headers,
|
||||||
|
timeout=10.0
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send webhook alert: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class AlertRule:
|
||||||
|
"""Alert rule definition"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
condition: Callable[[], bool],
|
||||||
|
severity: AlertSeverity,
|
||||||
|
title_template: str,
|
||||||
|
message_template: str,
|
||||||
|
source: str,
|
||||||
|
check_interval: int = 60,
|
||||||
|
cooldown: int = 300,
|
||||||
|
metadata: Optional[Dict[str, Any]] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize alert rule
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Rule name
|
||||||
|
condition: Function that returns True if alert should fire
|
||||||
|
severity: Alert severity
|
||||||
|
title_template: Template for alert title
|
||||||
|
message_template: Template for alert message
|
||||||
|
source: Alert source
|
||||||
|
check_interval: Check interval in seconds
|
||||||
|
cooldown: Cooldown period in seconds
|
||||||
|
metadata: Additional metadata
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.condition = condition
|
||||||
|
self.severity = severity
|
||||||
|
self.title_template = title_template
|
||||||
|
self.message_template = message_template
|
||||||
|
self.source = source
|
||||||
|
self.check_interval = check_interval
|
||||||
|
self.cooldown = cooldown
|
||||||
|
self.metadata = metadata or {}
|
||||||
|
self.last_fired: Optional[datetime] = None
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
def should_fire(self) -> bool:
|
||||||
|
"""Check if alert should fire"""
|
||||||
|
if not self.enabled:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.last_fired:
|
||||||
|
time_since_last = (datetime.utcnow() - self.last_fired).total_seconds()
|
||||||
|
if time_since_last < self.cooldown:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.condition()
|
||||||
|
|
||||||
|
def fire(self) -> Alert:
|
||||||
|
"""Create alert from this rule"""
|
||||||
|
self.last_fired = datetime.utcnow()
|
||||||
|
return Alert(
|
||||||
|
id=f"{self.name}-{int(datetime.utcnow().timestamp())}",
|
||||||
|
severity=self.severity,
|
||||||
|
title=self.title_template,
|
||||||
|
message=self.message_template,
|
||||||
|
source=self.source,
|
||||||
|
metadata=self.metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AlertManager:
|
||||||
|
"""Alert manager for handling alerts and rules"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize alert manager"""
|
||||||
|
self.rules: Dict[str, AlertRule] = {}
|
||||||
|
self.channels: List[AlertChannel] = []
|
||||||
|
self.active_alerts: Dict[str, Alert] = {}
|
||||||
|
self.alert_history: List[Alert] = []
|
||||||
|
self._running = False
|
||||||
|
self._task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
|
def add_rule(self, rule: AlertRule) -> None:
|
||||||
|
"""
|
||||||
|
Add alert rule
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rule: Alert rule to add
|
||||||
|
"""
|
||||||
|
self.rules[rule.name] = rule
|
||||||
|
logger.info(f"Added alert rule: {rule.name}")
|
||||||
|
|
||||||
|
def remove_rule(self, name: str) -> None:
|
||||||
|
"""
|
||||||
|
Remove alert rule
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Rule name
|
||||||
|
"""
|
||||||
|
if name in self.rules:
|
||||||
|
del self.rules[name]
|
||||||
|
logger.info(f"Removed alert rule: {name}")
|
||||||
|
|
||||||
|
def add_channel(self, channel: AlertChannel) -> None:
|
||||||
|
"""
|
||||||
|
Add alert channel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel: Alert channel to add
|
||||||
|
"""
|
||||||
|
self.channels.append(channel)
|
||||||
|
logger.info(f"Added alert channel: {channel.__class__.__name__}")
|
||||||
|
|
||||||
|
async def check_rules(self) -> None:
|
||||||
|
"""Check all alert rules and fire if needed"""
|
||||||
|
for rule in self.rules.values():
|
||||||
|
try:
|
||||||
|
if rule.should_fire():
|
||||||
|
alert = rule.fire()
|
||||||
|
await self.send_alert(alert)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking rule {rule.name}: {e}")
|
||||||
|
|
||||||
|
async def send_alert(self, alert: Alert) -> None:
|
||||||
|
"""
|
||||||
|
Send alert through all channels
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alert: Alert to send
|
||||||
|
"""
|
||||||
|
self.active_alerts[alert.id] = alert
|
||||||
|
self.alert_history.append(alert)
|
||||||
|
|
||||||
|
# Keep history limited
|
||||||
|
if len(self.alert_history) > 1000:
|
||||||
|
self.alert_history = self.alert_history[-1000:]
|
||||||
|
|
||||||
|
# Send through all channels
|
||||||
|
for channel in self.channels:
|
||||||
|
try:
|
||||||
|
await channel.send(alert)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send alert through channel: {e}")
|
||||||
|
|
||||||
|
async def acknowledge_alert(self, alert_id: str, acknowledged_by: str) -> bool:
|
||||||
|
"""
|
||||||
|
Acknowledge an alert
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alert_id: Alert ID
|
||||||
|
acknowledged_by: User acknowledging the alert
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if acknowledged successfully
|
||||||
|
"""
|
||||||
|
if alert_id in self.active_alerts:
|
||||||
|
alert = self.active_alerts[alert_id]
|
||||||
|
alert.status = AlertStatus.ACKNOWLEDGED
|
||||||
|
alert.acknowledged_by = acknowledged_by
|
||||||
|
alert.acknowledged_at = datetime.utcnow()
|
||||||
|
logger.info(f"Alert acknowledged: {alert_id} by {acknowledged_by}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resolve_alert(self, alert_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Resolve an alert
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alert_id: Alert ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if resolved successfully
|
||||||
|
"""
|
||||||
|
if alert_id in self.active_alerts:
|
||||||
|
alert = self.active_alerts[alert_id]
|
||||||
|
alert.status = AlertStatus.RESOLVED
|
||||||
|
alert.resolved_at = datetime.utcnow()
|
||||||
|
del self.active_alerts[alert_id]
|
||||||
|
logger.info(f"Alert resolved: {alert_id}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_active_alerts(self) -> List[Alert]:
|
||||||
|
"""Get all active alerts"""
|
||||||
|
return list(self.active_alerts.values())
|
||||||
|
|
||||||
|
def get_alert_history(self, limit: int = 100) -> List[Alert]:
|
||||||
|
"""
|
||||||
|
Get alert history
|
||||||
|
|
||||||
|
Args:
|
||||||
|
limit: Maximum number of alerts to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of alerts
|
||||||
|
"""
|
||||||
|
return self.alert_history[-limit:]
|
||||||
|
|
||||||
|
async def start(self) -> None:
|
||||||
|
"""Start alert manager background task"""
|
||||||
|
if self._running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._running = True
|
||||||
|
self._task = asyncio.create_task(self._run_checks())
|
||||||
|
logger.info("Alert manager started")
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
"""Stop alert manager background task"""
|
||||||
|
if not self._running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._running = False
|
||||||
|
if self._task:
|
||||||
|
self._task.cancel()
|
||||||
|
try:
|
||||||
|
await self._task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
logger.info("Alert manager stopped")
|
||||||
|
|
||||||
|
async def _run_checks(self) -> None:
|
||||||
|
"""Background task to check alert rules"""
|
||||||
|
while self._running:
|
||||||
|
try:
|
||||||
|
await self.check_rules()
|
||||||
|
|
||||||
|
# Calculate sleep time based on minimum check interval
|
||||||
|
min_interval = min((rule.check_interval for rule in self.rules.values()), default=60)
|
||||||
|
await asyncio.sleep(min_interval)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in alert check loop: {e}")
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
|
# Global alert manager instance
|
||||||
|
_alert_manager: Optional[AlertManager] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_alert_manager() -> AlertManager:
|
||||||
|
"""
|
||||||
|
Get global alert manager instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Alert manager instance
|
||||||
|
"""
|
||||||
|
global _alert_manager
|
||||||
|
if _alert_manager is None:
|
||||||
|
_alert_manager = AlertManager()
|
||||||
|
# Add default log channel
|
||||||
|
_alert_manager.add_channel(LogAlertChannel())
|
||||||
|
return _alert_manager
|
||||||
|
|
||||||
|
|
||||||
|
def setup_alerting(
|
||||||
|
webhook_url: Optional[str] = None,
|
||||||
|
webhook_headers: Optional[Dict[str, str]] = None
|
||||||
|
) -> AlertManager:
|
||||||
|
"""
|
||||||
|
Setup alerting system
|
||||||
|
|
||||||
|
Args:
|
||||||
|
webhook_url: Optional webhook URL for alerts
|
||||||
|
webhook_headers: Optional webhook headers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Alert manager instance
|
||||||
|
"""
|
||||||
|
manager = get_alert_manager()
|
||||||
|
|
||||||
|
if webhook_url:
|
||||||
|
manager.add_channel(WebhookAlertChannel(webhook_url, webhook_headers))
|
||||||
|
|
||||||
|
return manager
|
||||||
294
aitbc/tracing.py
Normal file
294
aitbc/tracing.py
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
"""
|
||||||
|
AITBC Distributed Tracing Module
|
||||||
|
OpenTelemetry-based distributed tracing for AITBC applications
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Dict, Any, Callable
|
||||||
|
from functools import wraps
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import os
|
||||||
|
|
||||||
|
# OpenTelemetry imports (optional - gracefully handle if not installed)
|
||||||
|
try:
|
||||||
|
from opentelemetry import trace
|
||||||
|
from opentelemetry.sdk.trace import TracerProvider
|
||||||
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
|
||||||
|
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
|
||||||
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
||||||
|
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
||||||
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||||
|
from opentelemetry.trace import Status, StatusCode
|
||||||
|
OPENTELEMETRY_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
OPENTELEMETRY_AVAILABLE = False
|
||||||
|
|
||||||
|
# Global tracer instance
|
||||||
|
_tracer: Optional[object] = None
|
||||||
|
_tracer_provider: Optional[object] = None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_tracing(
|
||||||
|
service_name: str,
|
||||||
|
service_version: str = "1.0.0",
|
||||||
|
exporter: str = "console",
|
||||||
|
sample_rate: float = 1.0
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Setup OpenTelemetry tracing for the service
|
||||||
|
|
||||||
|
Args:
|
||||||
|
service_name: Name of the service
|
||||||
|
service_version: Version of the service
|
||||||
|
exporter: Exporter type ('console', 'otlp', 'none')
|
||||||
|
sample_rate: Sampling rate (0.0 to 1.0)
|
||||||
|
"""
|
||||||
|
global _tracer, _tracer_provider
|
||||||
|
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
print("OpenTelemetry not available, tracing disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create resource with service information
|
||||||
|
resource = Resource.create({
|
||||||
|
SERVICE_NAME: service_name,
|
||||||
|
"service.version": service_version,
|
||||||
|
"deployment.environment": os.getenv("APP_ENV", "development")
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create tracer provider
|
||||||
|
_tracer_provider = TracerProvider(resource=resource)
|
||||||
|
|
||||||
|
# Configure exporter based on type
|
||||||
|
if exporter == "console":
|
||||||
|
span_processor = BatchSpanProcessor(ConsoleSpanExporter())
|
||||||
|
_tracer_provider.add_span_processor(span_processor)
|
||||||
|
elif exporter == "otlp":
|
||||||
|
try:
|
||||||
|
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||||
|
otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
|
||||||
|
span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=otlp_endpoint))
|
||||||
|
_tracer_provider.add_span_processor(span_processor)
|
||||||
|
except ImportError:
|
||||||
|
print("OTLP exporter not available, falling back to console")
|
||||||
|
span_processor = BatchSpanProcessor(ConsoleSpanExporter())
|
||||||
|
_tracer_provider.add_span_processor(span_processor)
|
||||||
|
|
||||||
|
# Set global tracer provider
|
||||||
|
trace.set_tracer_provider(_tracer_provider)
|
||||||
|
|
||||||
|
# Get tracer
|
||||||
|
_tracer = trace.get_tracer(__name__)
|
||||||
|
|
||||||
|
print(f"Tracing enabled for {service_name} with {exporter} exporter")
|
||||||
|
|
||||||
|
|
||||||
|
def get_tracer() -> Optional[object]:
|
||||||
|
"""
|
||||||
|
Get the global tracer instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tracer instance or None if not configured
|
||||||
|
"""
|
||||||
|
return _tracer
|
||||||
|
|
||||||
|
|
||||||
|
def instrument_fastapi(app) -> None:
|
||||||
|
"""
|
||||||
|
Instrument FastAPI application with tracing
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI application instance
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
print("OpenTelemetry not available, FastAPI instrumentation disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
FastAPIInstrumentor.instrument_app(app)
|
||||||
|
print("FastAPI instrumentation enabled")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to instrument FastAPI: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def instrument_httpx() -> None:
|
||||||
|
"""Instrument HTTPX client with tracing"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
print("OpenTelemetry not available, HTTPX instrumentation disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
HTTPXClientInstrumentor().instrument()
|
||||||
|
print("HTTPX instrumentation enabled")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to instrument HTTPX: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def instrument_sqlalchemy(engine) -> None:
|
||||||
|
"""
|
||||||
|
Instrument SQLAlchemy engine with tracing
|
||||||
|
|
||||||
|
Args:
|
||||||
|
engine: SQLAlchemy engine instance
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
print("OpenTelemetry not available, SQLAlchemy instrumentation disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
SQLAlchemyInstrumentor().instrument(engine=engine)
|
||||||
|
print("SQLAlchemy instrumentation enabled")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to instrument SQLAlchemy: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def trace_span(
|
||||||
|
name: str,
|
||||||
|
attributes: Optional[Dict[str, Any]] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Context manager for creating a trace span
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Span name
|
||||||
|
attributes: Span attributes
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Span object if tracing is available
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE or _tracer is None:
|
||||||
|
yield None
|
||||||
|
return
|
||||||
|
|
||||||
|
with _tracer.start_as_current_span(name, attributes=attributes or {}) as span:
|
||||||
|
yield span
|
||||||
|
|
||||||
|
|
||||||
|
def trace_function(name: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Decorator for tracing function execution
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Span name (defaults to function name)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decorated function
|
||||||
|
"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
if not OPENTELEMETRY_AVAILABLE or _tracer is None:
|
||||||
|
return func
|
||||||
|
|
||||||
|
span_name = name or f"{func.__module__}.{func.__name__}"
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with _tracer.start_as_current_span(span_name) as span:
|
||||||
|
# Add function arguments as attributes (if small)
|
||||||
|
try:
|
||||||
|
if args and len(args) < 3:
|
||||||
|
span.set_attribute("args", str(args))
|
||||||
|
if kwargs and len(kwargs) < 3:
|
||||||
|
span.set_attribute("kwargs", str(kwargs))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
span.set_status(Status(StatusCode.OK))
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
span.set_status(Status(StatusCode.ERROR, str(e)))
|
||||||
|
span.record_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def trace_async_function(name: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Decorator for tracing async function execution
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Span name (defaults to function name)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decorated async function
|
||||||
|
"""
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
if not OPENTELEMETRY_AVAILABLE or _tracer is None:
|
||||||
|
return func
|
||||||
|
|
||||||
|
span_name = name or f"{func.__module__}.{func.__name__}"
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
with _tracer.start_as_current_span(span_name) as span:
|
||||||
|
# Add function arguments as attributes (if small)
|
||||||
|
try:
|
||||||
|
if args and len(args) < 3:
|
||||||
|
span.set_attribute("args", str(args))
|
||||||
|
if kwargs and len(kwargs) < 3:
|
||||||
|
span.set_attribute("kwargs", str(kwargs))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await func(*args, **kwargs)
|
||||||
|
span.set_status(Status(StatusCode.OK))
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
span.set_status(Status(StatusCode.ERROR, str(e)))
|
||||||
|
span.record_exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def set_span_attribute(key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
Set an attribute on the current span
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: Attribute key
|
||||||
|
value: Attribute value
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_span = trace.get_current_span()
|
||||||
|
if current_span:
|
||||||
|
current_span.set_attribute(key, str(value))
|
||||||
|
|
||||||
|
|
||||||
|
def set_span_error(exception: Exception) -> None:
|
||||||
|
"""
|
||||||
|
Record an exception on the current span
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exception: Exception to record
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_span = trace.get_current_span()
|
||||||
|
if current_span:
|
||||||
|
current_span.set_status(Status(StatusCode.ERROR, str(exception)))
|
||||||
|
current_span.record_exception(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def add_span_event(name: str, attributes: Optional[Dict[str, Any]] = None) -> None:
|
||||||
|
"""
|
||||||
|
Add an event to the current span
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Event name
|
||||||
|
attributes: Event attributes
|
||||||
|
"""
|
||||||
|
if not OPENTELEMETRY_AVAILABLE:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_span = trace.get_current_span()
|
||||||
|
if current_span:
|
||||||
|
current_span.add_event(name, attributes or {})
|
||||||
@@ -10,9 +10,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
from ..services.ai_analytics.adaptive_learning import AdaptiveLearningService
|
from ..services.ai_analytics.adaptive_learning import AdaptiveLearningService
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
@@ -23,7 +25,8 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"], summary="Adaptive Learning Service Health")
|
@router.get("/health", tags=["health"], summary="Adaptive Learning Service Health")
|
||||||
async def adaptive_learning_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def adaptive_learning_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Health check for Adaptive Learning Service (Port 8011)
|
Health check for Adaptive Learning Service (Port 8011)
|
||||||
"""
|
"""
|
||||||
@@ -103,7 +106,8 @@ async def adaptive_learning_health(session: Annotated[Session, Depends(get_sessi
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health/deep", tags=["health"], summary="Deep Adaptive Learning Service Health")
|
@router.get("/health/deep", tags=["health"], summary="Deep Adaptive Learning Service Health")
|
||||||
async def adaptive_learning_deep_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def adaptive_learning_deep_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Deep health check with learning framework validation
|
Deep health check with learning framework validation
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request
|
from fastapi import APIRouter, Depends, Header, HTTPException, Request
|
||||||
from slowapi import Limiter
|
|
||||||
from slowapi.util import get_remote_address
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
from ..deps import require_admin_key
|
from ..deps import require_admin_key
|
||||||
@@ -19,12 +18,12 @@ from ..utils.cache import cached, get_cache_config
|
|||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
limiter = Limiter(key_func=get_remote_address)
|
|
||||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/debug-settings", summary="Debug settings")
|
@router.get("/debug-settings", summary="Debug settings")
|
||||||
async def debug_settings() -> dict: # type: ignore[arg-type]
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def debug_settings(request: Request) -> dict: # type: ignore[arg-type]
|
||||||
# SECURITY FIX: Mask API keys before returning to prevent clear-text exposure
|
# SECURITY FIX: Mask API keys before returning to prevent clear-text exposure
|
||||||
def mask_keys(keys: list[str]) -> list[str]:
|
def mask_keys(keys: list[str]) -> list[str]:
|
||||||
return [key[:8] + "..." if len(key) > 8 else "***" for key in keys]
|
return [key[:8] + "..." if len(key) > 8 else "***" for key in keys]
|
||||||
@@ -38,8 +37,9 @@ async def debug_settings() -> dict: # type: ignore[arg-type]
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/debug/create-test-miner", summary="Create a test miner for debugging")
|
@router.post("/debug/create-test-miner", summary="Create a test miner for debugging")
|
||||||
|
@rate_limit(rate=10, per=60)
|
||||||
async def create_test_miner(
|
async def create_test_miner(
|
||||||
session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())
|
request: Request, session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())
|
||||||
) -> dict[str, str]: # type: ignore[arg-type]
|
) -> dict[str, str]: # type: ignore[arg-type]
|
||||||
"""Create a test miner for debugging marketplace sync"""
|
"""Create a test miner for debugging marketplace sync"""
|
||||||
try:
|
try:
|
||||||
@@ -101,7 +101,8 @@ async def create_test_miner(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/test-key", summary="Test API key validation")
|
@router.get("/test-key", summary="Test API key validation")
|
||||||
async def test_key(api_key: str = Header(default=None, alias="X-Api-Key")) -> dict[str, str]: # type: ignore[arg-type]
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def test_key(request: Request, api_key: str = Header(default=None, alias="X-Api-Key")) -> dict[str, str]: # type: ignore[arg-type]
|
||||||
masked_key = api_key[:8] + "..." if api_key else "None"
|
masked_key = api_key[:8] + "..." if api_key else "None"
|
||||||
logger.debug(f"Received API key: {masked_key}")
|
logger.debug(f"Received API key: {masked_key}")
|
||||||
logger.debug(f"Allowed admin keys count: {len(settings.admin_api_keys)}")
|
logger.debug(f"Allowed admin keys count: {len(settings.admin_api_keys)}")
|
||||||
@@ -115,7 +116,7 @@ async def test_key(api_key: str = Header(default=None, alias="X-Api-Key")) -> di
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/stats", summary="Get coordinator stats")
|
@router.get("/stats", summary="Get coordinator stats")
|
||||||
@limiter.limit(lambda: settings.rate_limit_admin_stats)
|
@rate_limit(rate=100, per=60)
|
||||||
@cached(**get_cache_config("job_list")) # Cache admin stats for 1 minute
|
@cached(**get_cache_config("job_list")) # Cache admin stats for 1 minute
|
||||||
async def get_stats(
|
async def get_stats(
|
||||||
request: Request, session: Annotated[Session, Depends(get_session)], api_key: str = Header(default=None, alias="X-Api-Key")
|
request: Request, session: Annotated[Session, Depends(get_session)], api_key: str = Header(default=None, alias="X-Api-Key")
|
||||||
@@ -151,7 +152,8 @@ async def get_stats(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs", summary="List jobs")
|
@router.get("/jobs", summary="List jobs")
|
||||||
async def list_jobs(session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())) -> dict[str, list[dict]]: # type: ignore[arg-type]
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def list_jobs(request: Request, session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())) -> dict[str, list[dict]]: # type: ignore[arg-type]
|
||||||
from ..domain import Job
|
from ..domain import Job
|
||||||
|
|
||||||
jobs = session.execute(select(Job).order_by(Job.requested_at.desc()).limit(100)).all()
|
jobs = session.execute(select(Job).order_by(Job.requested_at.desc()).limit(100)).all()
|
||||||
@@ -170,7 +172,8 @@ async def list_jobs(session: Annotated[Session, Depends(get_session)], admin_key
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/miners", summary="List miners")
|
@router.get("/miners", summary="List miners")
|
||||||
async def list_miners(session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())) -> dict[str, list[dict]]: # type: ignore[arg-type]
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def list_miners(request: Request, session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())) -> dict[str, list[dict]]: # type: ignore[arg-type]
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
from ..domain import Miner
|
from ..domain import Miner
|
||||||
@@ -195,6 +198,7 @@ async def list_miners(session: Annotated[Session, Depends(get_session)], admin_k
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/status", summary="Get system status", response_model=None)
|
@router.get("/status", summary="Get system status", response_model=None)
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def get_system_status(
|
async def get_system_status(
|
||||||
request: Request, session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())
|
request: Request, session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())
|
||||||
) -> dict[str, any]: # type: ignore[arg-type]
|
) -> dict[str, any]: # type: ignore[arg-type]
|
||||||
@@ -263,7 +267,8 @@ async def get_system_status(
|
|||||||
|
|
||||||
# Agent endpoints temporarily added to admin router
|
# Agent endpoints temporarily added to admin router
|
||||||
@router.post("/agents/networks", response_model=dict, status_code=201)
|
@router.post("/agents/networks", response_model=dict, status_code=201)
|
||||||
async def create_agent_network(network_data: dict) -> dict:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def create_agent_network(request: Request, network_data: dict) -> dict:
|
||||||
"""Create a new agent network for collaborative processing"""
|
"""Create a new agent network for collaborative processing"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -300,7 +305,8 @@ async def create_agent_network(network_data: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/agents/executions/{execution_id}/receipt")
|
@router.get("/agents/executions/{execution_id}/receipt")
|
||||||
async def get_execution_receipt(execution_id: str) -> dict:
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_execution_receipt(request: Request, execution_id: str) -> dict:
|
||||||
"""Get verifiable receipt for completed execution"""
|
"""Get verifiable receipt for completed execution"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ Agent Integration and Deployment API Router for Verifiable AI Agent Orchestratio
|
|||||||
Provides REST API endpoints for production deployment and integration management
|
Provides REST API endpoints for production deployment and integration management
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -31,7 +32,9 @@ router = APIRouter(prefix="/agents/integration", tags=["Agent Integration"])
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/deployments/config", response_model=AgentDeploymentConfig)
|
@router.post("/deployments/config", response_model=AgentDeploymentConfig)
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_deployment_config(
|
async def create_deployment_config(
|
||||||
|
request: Request,
|
||||||
workflow_id: str,
|
workflow_id: str,
|
||||||
deployment_name: str,
|
deployment_name: str,
|
||||||
deployment_config: dict,
|
deployment_config: dict,
|
||||||
@@ -65,7 +68,9 @@ async def create_deployment_config(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/deployments/configs", response_model=list[AgentDeploymentConfig])
|
@router.get("/deployments/configs", response_model=list[AgentDeploymentConfig])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def list_deployment_configs(
|
async def list_deployment_configs(
|
||||||
|
request: Request,
|
||||||
workflow_id: str | None = None,
|
workflow_id: str | None = None,
|
||||||
status: DeploymentStatus | None = None,
|
status: DeploymentStatus | None = None,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
@@ -99,7 +104,9 @@ async def list_deployment_configs(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/deployments/configs/{config_id}", response_model=AgentDeploymentConfig)
|
@router.get("/deployments/configs/{config_id}", response_model=AgentDeploymentConfig)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_deployment_config(
|
async def get_deployment_config(
|
||||||
|
request: Request,
|
||||||
config_id: str,
|
config_id: str,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
current_user: str = Depends(require_admin_key()),
|
current_user: str = Depends(require_admin_key()),
|
||||||
@@ -126,7 +133,9 @@ async def get_deployment_config(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/deployments/{config_id}/deploy")
|
@router.post("/deployments/{config_id}/deploy")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def deploy_workflow(
|
async def deploy_workflow(
|
||||||
|
request: Request,
|
||||||
config_id: str,
|
config_id: str,
|
||||||
target_environment: str = "production",
|
target_environment: str = "production",
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
@@ -160,7 +169,9 @@ async def deploy_workflow(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/deployments/{config_id}/health")
|
@router.get("/deployments/{config_id}/health")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_deployment_health(
|
async def get_deployment_health(
|
||||||
|
request: Request,
|
||||||
config_id: str,
|
config_id: str,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
current_user: str = Depends(require_admin_key()),
|
current_user: str = Depends(require_admin_key()),
|
||||||
@@ -190,7 +201,9 @@ async def get_deployment_health(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/deployments/{config_id}/scale")
|
@router.post("/deployments/{config_id}/scale")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def scale_deployment(
|
async def scale_deployment(
|
||||||
|
request: Request,
|
||||||
config_id: str,
|
config_id: str,
|
||||||
target_instances: int,
|
target_instances: int,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
@@ -224,7 +237,9 @@ async def scale_deployment(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/deployments/{config_id}/rollback")
|
@router.post("/deployments/{config_id}/rollback")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def rollback_deployment(
|
async def rollback_deployment(
|
||||||
|
request: Request,
|
||||||
config_id: str,
|
config_id: str,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
current_user: str = Depends(require_admin_key()),
|
current_user: str = Depends(require_admin_key()),
|
||||||
@@ -255,7 +270,9 @@ async def rollback_deployment(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/deployments/instances", response_model=list[AgentDeploymentInstance])
|
@router.get("/deployments/instances", response_model=list[AgentDeploymentInstance])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def list_deployment_instances(
|
async def list_deployment_instances(
|
||||||
|
request: Request,
|
||||||
deployment_id: str | None = None,
|
deployment_id: str | None = None,
|
||||||
environment: str | None = None,
|
environment: str | None = None,
|
||||||
status: DeploymentStatus | None = None,
|
status: DeploymentStatus | None = None,
|
||||||
@@ -295,7 +312,9 @@ async def list_deployment_instances(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/deployments/instances/{instance_id}", response_model=AgentDeploymentInstance)
|
@router.get("/deployments/instances/{instance_id}", response_model=AgentDeploymentInstance)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_deployment_instance(
|
async def get_deployment_instance(
|
||||||
|
request: Request,
|
||||||
instance_id: str,
|
instance_id: str,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
current_user: str = Depends(require_admin_key()),
|
current_user: str = Depends(require_admin_key()),
|
||||||
@@ -326,7 +345,9 @@ async def get_deployment_instance(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/integrations/zk/{execution_id}")
|
@router.post("/integrations/zk/{execution_id}")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def integrate_with_zk_system(
|
async def integrate_with_zk_system(
|
||||||
|
request: Request,
|
||||||
execution_id: str,
|
execution_id: str,
|
||||||
verification_level: VerificationLevel = VerificationLevel.BASIC,
|
verification_level: VerificationLevel = VerificationLevel.BASIC,
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
@@ -360,7 +381,9 @@ async def integrate_with_zk_system(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/metrics/deployments/{deployment_id}")
|
@router.get("/metrics/deployments/{deployment_id}")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_deployment_metrics(
|
async def get_deployment_metrics(
|
||||||
|
request: Request,
|
||||||
deployment_id: str,
|
deployment_id: str,
|
||||||
time_range: str = "1h",
|
time_range: str = "1h",
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
session: Session = Depends(Annotated[Session, Depends(get_session)]),
|
||||||
@@ -391,7 +414,9 @@ async def get_deployment_metrics(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/production/deploy")
|
@router.post("/production/deploy")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def deploy_to_production(
|
async def deploy_to_production(
|
||||||
|
request: Request,
|
||||||
workflow_id: str,
|
workflow_id: str,
|
||||||
deployment_config: dict,
|
deployment_config: dict,
|
||||||
integration_config: dict | None = None,
|
integration_config: dict | None = None,
|
||||||
@@ -425,8 +450,9 @@ async def deploy_to_production(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/production/dashboard")
|
@router.get("/production/dashboard")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_production_dashboard(
|
async def get_production_dashboard(
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]), current_user: str = Depends(require_admin_key())
|
request: Request, session: Session = Depends(Annotated[Session, Depends(get_session)]), current_user: str = Depends(require_admin_key())
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get comprehensive production dashboard data"""
|
"""Get comprehensive production dashboard data"""
|
||||||
|
|
||||||
@@ -479,8 +505,9 @@ async def get_production_dashboard(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/production/health")
|
@router.get("/production/health")
|
||||||
|
@rate_limit(rate=1000, per=60)
|
||||||
async def get_production_health(
|
async def get_production_health(
|
||||||
session: Session = Depends(Annotated[Session, Depends(get_session)]), current_user: str = Depends(require_admin_key())
|
request: Request, session: Session = Depends(Annotated[Session, Depends(get_session)]), current_user: str = Depends(require_admin_key())
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get overall production health status"""
|
"""Get overall production health status"""
|
||||||
|
|
||||||
@@ -549,7 +576,9 @@ async def get_production_health(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/production/alerts")
|
@router.get("/production/alerts")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_production_alerts(
|
async def get_production_alerts(
|
||||||
|
request: Request,
|
||||||
severity: str | None = None,
|
severity: str | None = None,
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
current_user: str = Depends(require_admin_key()),
|
current_user: str = Depends(require_admin_key()),
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ from datetime import datetime, timezone
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from slowapi import Limiter
|
|
||||||
from slowapi.util import get_remote_address
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..config import settings
|
from ..config import settings
|
||||||
from ..custom_types import JobState
|
from ..custom_types import JobState
|
||||||
@@ -21,12 +20,11 @@ from ..utils.cache import cached, get_cache_config
|
|||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
limiter = Limiter(key_func=get_remote_address)
|
|
||||||
router = APIRouter(tags=["client"])
|
router = APIRouter(tags=["client"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jobs", response_model=JobView, status_code=status.HTTP_201_CREATED, summary="Submit a job")
|
@router.post("/jobs", response_model=JobView, status_code=status.HTTP_201_CREATED, summary="Submit a job")
|
||||||
@limiter.limit(lambda: settings.rate_limit_jobs_submit)
|
@rate_limit(rate=50, per=60)
|
||||||
async def submit_job(
|
async def submit_job(
|
||||||
req: JobCreate,
|
req: JobCreate,
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -55,8 +53,9 @@ async def submit_job(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/{job_id}", response_model=JobView, summary="Get job status")
|
@router.get("/jobs/{job_id}", response_model=JobView, summary="Get job status")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_job(
|
async def get_job(
|
||||||
job_id: str,
|
request: Request, job_id: str,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
client_id: str = Depends(require_client_key()),
|
client_id: str = Depends(require_client_key()),
|
||||||
) -> JobView: # type: ignore[arg-type]
|
) -> JobView: # type: ignore[arg-type]
|
||||||
@@ -69,8 +68,9 @@ async def get_job(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/{job_id}/result", response_model=JobResult, summary="Get job result")
|
@router.get("/jobs/{job_id}/result", response_model=JobResult, summary="Get job result")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_job_result(
|
async def get_job_result(
|
||||||
job_id: str,
|
request: Request, job_id: str,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
client_id: str = Depends(require_client_key()),
|
client_id: str = Depends(require_client_key()),
|
||||||
) -> JobResult: # type: ignore[arg-type]
|
) -> JobResult: # type: ignore[arg-type]
|
||||||
@@ -88,8 +88,9 @@ async def get_job_result(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/jobs/{job_id}/cancel", response_model=JobView, summary="Cancel job")
|
@router.post("/jobs/{job_id}/cancel", response_model=JobView, summary="Cancel job")
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def cancel_job(
|
async def cancel_job(
|
||||||
job_id: str,
|
request: Request, job_id: str,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
client_id: str = Depends(require_client_key()),
|
client_id: str = Depends(require_client_key()),
|
||||||
) -> JobView: # type: ignore[arg-type]
|
) -> JobView: # type: ignore[arg-type]
|
||||||
@@ -107,8 +108,9 @@ async def cancel_job(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/{job_id}/receipt", summary="Get latest signed receipt")
|
@router.get("/jobs/{job_id}/receipt", summary="Get latest signed receipt")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_job_receipt(
|
async def get_job_receipt(
|
||||||
job_id: str,
|
request: Request, job_id: str,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
client_id: str = Depends(require_client_key()),
|
client_id: str = Depends(require_client_key()),
|
||||||
) -> dict: # type: ignore[arg-type]
|
) -> dict: # type: ignore[arg-type]
|
||||||
@@ -123,8 +125,9 @@ async def get_job_receipt(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/{job_id}/receipts", summary="List signed receipts")
|
@router.get("/jobs/{job_id}/receipts", summary="List signed receipts")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def list_job_receipts(
|
async def list_job_receipts(
|
||||||
job_id: str,
|
request: Request, job_id: str,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
client_id: str = Depends(require_client_key()),
|
client_id: str = Depends(require_client_key()),
|
||||||
) -> dict: # type: ignore[arg-type]
|
) -> dict: # type: ignore[arg-type]
|
||||||
@@ -134,6 +137,7 @@ async def list_job_receipts(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs", summary="List jobs with filtering")
|
@router.get("/jobs", summary="List jobs with filtering")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
@cached(**get_cache_config("job_list")) # Cache job list for 30 seconds
|
@cached(**get_cache_config("job_list")) # Cache job list for 30 seconds
|
||||||
async def list_jobs(
|
async def list_jobs(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -164,6 +168,7 @@ async def list_jobs(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/jobs/history", summary="Get job history")
|
@router.get("/jobs/history", summary="Get job history")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
@cached(**get_cache_config("job_list")) # Cache job history for 30 seconds
|
@cached(**get_cache_config("job_list")) # Cache job history for 30 seconds
|
||||||
async def get_job_history(
|
async def get_job_history(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -216,6 +221,7 @@ async def get_job_history(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/blocks", summary="Get blockchain blocks")
|
@router.get("/blocks", summary="Get blockchain blocks")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_blocks(
|
async def get_blocks(
|
||||||
request: Request,
|
request: Request,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
@@ -252,7 +258,8 @@ async def get_blocks(
|
|||||||
|
|
||||||
# Temporary agent endpoints added to client router until agent router issue is resolved
|
# Temporary agent endpoints added to client router until agent router issue is resolved
|
||||||
@router.post("/agents/networks", response_model=dict, status_code=201)
|
@router.post("/agents/networks", response_model=dict, status_code=201)
|
||||||
async def create_agent_network(network_data: dict) -> dict:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def create_agent_network(request: Request, network_data: dict) -> dict:
|
||||||
"""Create a new agent network for collaborative processing"""
|
"""Create a new agent network for collaborative processing"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -286,7 +293,8 @@ async def create_agent_network(network_data: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/agents/executions/{execution_id}/receipt")
|
@router.get("/agents/executions/{execution_id}/receipt")
|
||||||
async def get_execution_receipt(execution_id: str) -> dict:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_execution_receipt(request: Request, execution_id: str) -> dict:
|
||||||
"""Get verifiable receipt for completed execution"""
|
"""Get verifiable receipt for completed execution"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ REST API for managing hermes developer profiles, SDKs, solutions, and hackathons
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -84,7 +86,8 @@ class HackathonCreateRequest(BaseModel):
|
|||||||
|
|
||||||
# Endpoints - Developer Ecosystem
|
# Endpoints - Developer Ecosystem
|
||||||
@router.post("/developers", response_model=DeveloperProfile)
|
@router.post("/developers", response_model=DeveloperProfile)
|
||||||
async def create_developer_profile(request: DeveloperProfileCreate, session: Annotated[Session, Depends(get_session)]) -> DeveloperProfile:
|
@rate_limit(rate=10, per=60)
|
||||||
|
async def create_developer_profile(request: DeveloperProfileCreate, request_http: Request, session: Annotated[Session, Depends(get_session)]) -> DeveloperProfile:
|
||||||
"""Register a new developer in the hermes ecosystem"""
|
"""Register a new developer in the hermes ecosystem"""
|
||||||
service = DeveloperEcosystemService(session)
|
service = DeveloperEcosystemService(session)
|
||||||
try:
|
try:
|
||||||
@@ -98,7 +101,8 @@ async def create_developer_profile(request: DeveloperProfileCreate, session: Ann
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/developers/{developer_id}", response_model=DeveloperProfile)
|
@router.get("/developers/{developer_id}", response_model=DeveloperProfile)
|
||||||
async def get_developer_profile(developer_id: str, session: Annotated[Session, Depends(get_session)]) -> DeveloperProfile:
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_developer_profile(developer_id: str, request: Request, session: Annotated[Session, Depends(get_session)]) -> DeveloperProfile:
|
||||||
"""Get a developer's profile and reputation"""
|
"""Get a developer's profile and reputation"""
|
||||||
service = DeveloperEcosystemService(session)
|
service = DeveloperEcosystemService(session)
|
||||||
profile = await service.get_developer_profile(developer_id)
|
profile = await service.get_developer_profile(developer_id)
|
||||||
@@ -108,7 +112,8 @@ async def get_developer_profile(developer_id: str, session: Annotated[Session, D
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/sdk/latest")
|
@router.get("/sdk/latest")
|
||||||
async def get_latest_sdk(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_latest_sdk(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Get information about the latest hermes SDK releases"""
|
"""Get information about the latest hermes SDK releases"""
|
||||||
service = DeveloperEcosystemService(session)
|
service = DeveloperEcosystemService(session)
|
||||||
return await service.get_sdk_release_info()
|
return await service.get_sdk_release_info()
|
||||||
@@ -116,7 +121,8 @@ async def get_latest_sdk(session: Annotated[Session, Depends(get_session)]) -> d
|
|||||||
|
|
||||||
# Endpoints - Marketplace Solutions
|
# Endpoints - Marketplace Solutions
|
||||||
@router.post("/solutions/publish", response_model=AgentSolution)
|
@router.post("/solutions/publish", response_model=AgentSolution)
|
||||||
async def publish_solution(request: SolutionPublishRequest, session: Annotated[Session, Depends(get_session)]) -> AgentSolution:
|
@rate_limit(rate=10, per=60)
|
||||||
|
async def publish_solution(request: SolutionPublishRequest, request_http: Request, session: Annotated[Session, Depends(get_session)]) -> AgentSolution:
|
||||||
"""Publish a new third-party agent solution to the marketplace"""
|
"""Publish a new third-party agent solution to the marketplace"""
|
||||||
service = ThirdPartySolutionService(session)
|
service = ThirdPartySolutionService(session)
|
||||||
try:
|
try:
|
||||||
@@ -128,9 +134,11 @@ async def publish_solution(request: SolutionPublishRequest, session: Annotated[S
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/solutions", response_model=list[AgentSolution])
|
@router.get("/solutions", response_model=list[AgentSolution])
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def list_solutions(
|
async def list_solutions(
|
||||||
category: str | None = None,
|
category: str | None = None,
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
|
request: Request,
|
||||||
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
||||||
) -> list[AgentSolution]:
|
) -> list[AgentSolution]:
|
||||||
"""List available third-party agent solutions"""
|
"""List available third-party agent solutions"""
|
||||||
@@ -139,8 +147,9 @@ async def list_solutions(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/solutions/{solution_id}/purchase")
|
@router.post("/solutions/{solution_id}/purchase")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def purchase_solution(
|
async def purchase_solution(
|
||||||
solution_id: str, session: Annotated[Session, Depends(get_session)], buyer_id: str = Body(embed=True)
|
solution_id: str, request: Request, session: Annotated[Session, Depends(get_session)], buyer_id: str = Body(embed=True)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Purchase or install a third-party solution"""
|
"""Purchase or install a third-party solution"""
|
||||||
service = ThirdPartySolutionService(session)
|
service = ThirdPartySolutionService(session)
|
||||||
@@ -155,9 +164,11 @@ async def purchase_solution(
|
|||||||
|
|
||||||
# Endpoints - Innovation Labs
|
# Endpoints - Innovation Labs
|
||||||
@router.post("/labs/propose", response_model=InnovationLab)
|
@router.post("/labs/propose", response_model=InnovationLab)
|
||||||
|
@rate_limit(rate=10, per=60)
|
||||||
async def propose_innovation_lab(
|
async def propose_innovation_lab(
|
||||||
researcher_id: str = Query(...),
|
researcher_id: str = Query(...),
|
||||||
request: LabProposalRequest = Body(...),
|
request: LabProposalRequest = Body(...),
|
||||||
|
request_http: Request,
|
||||||
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
||||||
) -> InnovationLab:
|
) -> InnovationLab:
|
||||||
"""Propose a new agent innovation lab or research program"""
|
"""Propose a new agent innovation lab or research program"""
|
||||||
@@ -170,8 +181,9 @@ async def propose_innovation_lab(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/labs/{lab_id}/join")
|
@router.post("/labs/{lab_id}/join")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def join_innovation_lab(
|
async def join_innovation_lab(
|
||||||
lab_id: str, session: Annotated[Session, Depends(get_session)], developer_id: str = Body(embed=True)
|
lab_id: str, request: Request, session: Annotated[Session, Depends(get_session)], developer_id: str = Body(embed=True)
|
||||||
) -> InnovationLab:
|
) -> InnovationLab:
|
||||||
"""Join an active innovation lab"""
|
"""Join an active innovation lab"""
|
||||||
service = InnovationLabService(session)
|
service = InnovationLabService(session)
|
||||||
@@ -183,8 +195,9 @@ async def join_innovation_lab(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/labs/{lab_id}/fund")
|
@router.post("/labs/{lab_id}/fund")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def fund_innovation_lab(
|
async def fund_innovation_lab(
|
||||||
lab_id: str, session: Annotated[Session, Depends(get_session)], amount: float = Body(embed=True)
|
lab_id: str, request: Request, session: Annotated[Session, Depends(get_session)], amount: float = Body(embed=True)
|
||||||
) -> InnovationLab:
|
) -> InnovationLab:
|
||||||
"""Provide funding to a proposed innovation lab"""
|
"""Provide funding to a proposed innovation lab"""
|
||||||
service = InnovationLabService(session)
|
service = InnovationLabService(session)
|
||||||
@@ -197,9 +210,11 @@ async def fund_innovation_lab(
|
|||||||
|
|
||||||
# Endpoints - Community Platform
|
# Endpoints - Community Platform
|
||||||
@router.post("/platform/posts", response_model=CommunityPost)
|
@router.post("/platform/posts", response_model=CommunityPost)
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_community_post(
|
async def create_community_post(
|
||||||
author_id: str = Query(...),
|
author_id: str = Query(...),
|
||||||
request: PostCreateRequest = Body(...),
|
request: PostCreateRequest = Body(...),
|
||||||
|
request_http: Request,
|
||||||
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
||||||
) -> CommunityPost:
|
) -> CommunityPost:
|
||||||
"""Create a new post in the community forum"""
|
"""Create a new post in the community forum"""
|
||||||
@@ -212,9 +227,11 @@ async def create_community_post(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/platform/feed", response_model=list[CommunityPost])
|
@router.get("/platform/feed", response_model=list[CommunityPost])
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def get_community_feed(
|
async def get_community_feed(
|
||||||
category: str | None = None,
|
category: str | None = None,
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
|
request: Request,
|
||||||
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
||||||
) -> list[CommunityPost]:
|
) -> list[CommunityPost]:
|
||||||
"""Get the latest community posts and discussions"""
|
"""Get the latest community posts and discussions"""
|
||||||
@@ -223,7 +240,8 @@ async def get_community_feed(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/platform/posts/{post_id}/upvote")
|
@router.post("/platform/posts/{post_id}/upvote")
|
||||||
async def upvote_community_post(post_id: str, session: Annotated[Session, Depends(get_session)]) -> CommunityPost:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def upvote_community_post(post_id: str, request: Request, session: Annotated[Session, Depends(get_session)]) -> CommunityPost:
|
||||||
"""Upvote a community post (rewards author reputation)"""
|
"""Upvote a community post (rewards author reputation)"""
|
||||||
service = CommunityPlatformService(session)
|
service = CommunityPlatformService(session)
|
||||||
try:
|
try:
|
||||||
@@ -235,9 +253,11 @@ async def upvote_community_post(post_id: str, session: Annotated[Session, Depend
|
|||||||
|
|
||||||
# Endpoints - Hackathons
|
# Endpoints - Hackathons
|
||||||
@router.post("/hackathons/create", response_model=Hackathon)
|
@router.post("/hackathons/create", response_model=Hackathon)
|
||||||
|
@rate_limit(rate=10, per=60)
|
||||||
async def create_hackathon(
|
async def create_hackathon(
|
||||||
organizer_id: str = Query(...),
|
organizer_id: str = Query(...),
|
||||||
request: HackathonCreateRequest = Body(...),
|
request: HackathonCreateRequest = Body(...),
|
||||||
|
request_http: Request,
|
||||||
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
session: Annotated[Session, Depends(get_session)] = Depends(get_session),
|
||||||
) -> Hackathon:
|
) -> Hackathon:
|
||||||
"""Create a new agent innovation hackathon (requires high reputation)"""
|
"""Create a new agent innovation hackathon (requires high reputation)"""
|
||||||
@@ -252,8 +272,9 @@ async def create_hackathon(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/hackathons/{hackathon_id}/register")
|
@router.post("/hackathons/{hackathon_id}/register")
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def register_for_hackathon(
|
async def register_for_hackathon(
|
||||||
hackathon_id: str, session: Annotated[Session, Depends(get_session)], developer_id: str = Body(embed=True)
|
hackathon_id: str, request: Request, session: Annotated[Session, Depends(get_session)], developer_id: str = Body(embed=True)
|
||||||
) -> Hackathon:
|
) -> Hackathon:
|
||||||
"""Register for an upcoming or ongoing hackathon"""
|
"""Register for an upcoming or ongoing hackathon"""
|
||||||
service = CommunityPlatformService(session)
|
service = CommunityPlatformService(session)
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..agent_identity.manager import AgentIdentityManager
|
from ..agent_identity.manager import AgentIdentityManager
|
||||||
from ..agent_identity.wallet_adapter_enhanced import (
|
from ..agent_identity.wallet_adapter_enhanced import (
|
||||||
SecurityLevel,
|
SecurityLevel,
|
||||||
@@ -45,7 +47,9 @@ def get_reputation_engine(session: Session = Depends(get_session)) -> CrossChain
|
|||||||
|
|
||||||
# Enhanced Wallet Adapter Endpoints
|
# Enhanced Wallet Adapter Endpoints
|
||||||
@router.post("/wallets/create", response_model=dict[str, Any])
|
@router.post("/wallets/create", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_enhanced_wallet(
|
async def create_enhanced_wallet(
|
||||||
|
request: Request,
|
||||||
owner_address: str,
|
owner_address: str,
|
||||||
chain_id: int,
|
chain_id: int,
|
||||||
security_config: dict[str, Any],
|
security_config: dict[str, Any],
|
||||||
@@ -87,7 +91,9 @@ async def create_enhanced_wallet(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/wallets/{wallet_address}/balance", response_model=dict[str, Any])
|
@router.get("/wallets/{wallet_address}/balance", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_wallet_balance(
|
async def get_wallet_balance(
|
||||||
|
request: Request,
|
||||||
wallet_address: str, chain_id: int, token_address: str | None = Query(None), session: Session = Depends(get_session)
|
wallet_address: str, chain_id: int, token_address: str | None = Query(None), session: Session = Depends(get_session)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get wallet balance with multi-token support"""
|
"""Get wallet balance with multi-token support"""
|
||||||
@@ -110,7 +116,9 @@ async def get_wallet_balance(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/wallets/{wallet_address}/transactions", response_model=dict[str, Any])
|
@router.post("/wallets/{wallet_address}/transactions", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def execute_wallet_transaction(
|
async def execute_wallet_transaction(
|
||||||
|
request: Request,
|
||||||
wallet_address: str,
|
wallet_address: str,
|
||||||
chain_id: int,
|
chain_id: int,
|
||||||
to_address: str,
|
to_address: str,
|
||||||
@@ -149,7 +157,9 @@ async def execute_wallet_transaction(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/wallets/{wallet_address}/transactions", response_model=list[dict[str, Any]])
|
@router.get("/wallets/{wallet_address}/transactions", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_wallet_transaction_history(
|
async def get_wallet_transaction_history(
|
||||||
|
request: Request,
|
||||||
wallet_address: str,
|
wallet_address: str,
|
||||||
chain_id: int,
|
chain_id: int,
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
@@ -178,7 +188,9 @@ async def get_wallet_transaction_history(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/wallets/{wallet_address}/sign", response_model=dict[str, Any])
|
@router.post("/wallets/{wallet_address}/sign", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def sign_message(
|
async def sign_message(
|
||||||
|
request: Request,
|
||||||
wallet_address: str, chain_id: int, message: str, session: Session = Depends(get_session)
|
wallet_address: str, chain_id: int, message: str, session: Session = Depends(get_session)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Sign a message with wallet"""
|
"""Sign a message with wallet"""
|
||||||
@@ -200,7 +212,9 @@ async def sign_message(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/wallets/verify-signature", response_model=dict[str, Any])
|
@router.post("/wallets/verify-signature", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def verify_signature(
|
async def verify_signature(
|
||||||
|
request: Request,
|
||||||
message: str, signature: str, address: str, chain_id: int, session: Session = Depends(get_session)
|
message: str, signature: str, address: str, chain_id: int, session: Session = Depends(get_session)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Verify a message signature"""
|
"""Verify a message signature"""
|
||||||
@@ -226,7 +240,9 @@ async def verify_signature(
|
|||||||
|
|
||||||
# Cross-Chain Bridge Endpoints
|
# Cross-Chain Bridge Endpoints
|
||||||
@router.post("/bridge/create-request", response_model=dict[str, Any])
|
@router.post("/bridge/create-request", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_bridge_request(
|
async def create_bridge_request(
|
||||||
|
request: Request,
|
||||||
user_address: str,
|
user_address: str,
|
||||||
source_chain_id: int,
|
source_chain_id: int,
|
||||||
target_chain_id: int,
|
target_chain_id: int,
|
||||||
@@ -268,7 +284,8 @@ async def create_bridge_request(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bridge/request/{bridge_request_id}", response_model=dict[str, Any])
|
@router.get("/bridge/request/{bridge_request_id}", response_model=dict[str, Any])
|
||||||
async def get_bridge_request_status(bridge_request_id: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_bridge_request_status(request: Request, bridge_request_id: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Get status of a bridge request"""
|
"""Get status of a bridge request"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -285,7 +302,9 @@ async def get_bridge_request_status(bridge_request_id: str, session: Session = D
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/bridge/request/{bridge_request_id}/cancel", response_model=dict[str, Any])
|
@router.post("/bridge/request/{bridge_request_id}/cancel", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def cancel_bridge_request(
|
async def cancel_bridge_request(
|
||||||
|
request: Request,
|
||||||
bridge_request_id: str, reason: str, session: Session = Depends(get_session)
|
bridge_request_id: str, reason: str, session: Session = Depends(get_session)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Cancel a bridge request"""
|
"""Cancel a bridge request"""
|
||||||
@@ -304,7 +323,9 @@ async def cancel_bridge_request(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bridge/statistics", response_model=dict[str, Any])
|
@router.get("/bridge/statistics", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_bridge_statistics(
|
async def get_bridge_statistics(
|
||||||
|
request: Request,
|
||||||
time_period_hours: int = Query(24, ge=1, le=8760), session: Session = Depends(get_session)
|
time_period_hours: int = Query(24, ge=1, le=8760), session: Session = Depends(get_session)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get bridge statistics"""
|
"""Get bridge statistics"""
|
||||||
@@ -323,7 +344,8 @@ async def get_bridge_statistics(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bridge/liquidity-pools", response_model=list[dict[str, Any]])
|
@router.get("/bridge/liquidity-pools", response_model=list[dict[str, Any]])
|
||||||
async def get_liquidity_pools(session: Session = Depends(get_session)) -> list[dict[str, Any]]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_liquidity_pools(request: Request, session: Session = Depends(get_session)) -> list[dict[str, Any]]:
|
||||||
"""Get all liquidity pool information"""
|
"""Get all liquidity pool information"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -341,7 +363,9 @@ async def get_liquidity_pools(session: Session = Depends(get_session)) -> list[d
|
|||||||
|
|
||||||
# Multi-Chain Transaction Manager Endpoints
|
# Multi-Chain Transaction Manager Endpoints
|
||||||
@router.post("/transactions/submit", response_model=dict[str, Any])
|
@router.post("/transactions/submit", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def submit_transaction(
|
async def submit_transaction(
|
||||||
|
request: Request,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
chain_id: int,
|
chain_id: int,
|
||||||
transaction_type: TransactionType,
|
transaction_type: TransactionType,
|
||||||
@@ -395,7 +419,8 @@ async def submit_transaction(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/transactions/{transaction_id}", response_model=dict[str, Any])
|
@router.get("/transactions/{transaction_id}", response_model=dict[str, Any])
|
||||||
async def get_transaction_status(transaction_id: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_transaction_status(request: Request, transaction_id: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Get detailed transaction status"""
|
"""Get detailed transaction status"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -416,7 +441,8 @@ async def get_transaction_status(transaction_id: str, session: Session = Depends
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/transactions/{transaction_id}/cancel", response_model=dict[str, Any])
|
@router.post("/transactions/{transaction_id}/cancel", response_model=dict[str, Any])
|
||||||
async def cancel_transaction(transaction_id: str, reason: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def cancel_transaction(request: Request, transaction_id: str, reason: str, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Cancel a transaction"""
|
"""Cancel a transaction"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -437,7 +463,9 @@ async def cancel_transaction(transaction_id: str, reason: str, session: Session
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/transactions/history", response_model=list[dict[str, Any]])
|
@router.get("/transactions/history", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_transaction_history(
|
async def get_transaction_history(
|
||||||
|
request: Request,
|
||||||
user_id: str | None = Query(None),
|
user_id: str | None = Query(None),
|
||||||
chain_id: int | None = Query(None),
|
chain_id: int | None = Query(None),
|
||||||
transaction_type: TransactionType | None = Query(None),
|
transaction_type: TransactionType | None = Query(None),
|
||||||
@@ -479,7 +507,9 @@ async def get_transaction_history(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/transactions/statistics", response_model=dict[str, Any])
|
@router.get("/transactions/statistics", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_transaction_statistics(
|
async def get_transaction_statistics(
|
||||||
|
request: Request,
|
||||||
time_period_hours: int = Query(24, ge=1, le=8760),
|
time_period_hours: int = Query(24, ge=1, le=8760),
|
||||||
chain_id: int | None = Query(None),
|
chain_id: int | None = Query(None),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
@@ -504,7 +534,9 @@ async def get_transaction_statistics(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/transactions/optimize-routing", response_model=dict[str, Any])
|
@router.post("/transactions/optimize-routing", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def optimize_transaction_routing(
|
async def optimize_transaction_routing(
|
||||||
|
request: Request,
|
||||||
transaction_type: TransactionType,
|
transaction_type: TransactionType,
|
||||||
amount: float,
|
amount: float,
|
||||||
from_chain: int,
|
from_chain: int,
|
||||||
@@ -535,7 +567,8 @@ async def optimize_transaction_routing(
|
|||||||
|
|
||||||
# Configuration and Status Endpoints
|
# Configuration and Status Endpoints
|
||||||
@router.get("/chains/supported", response_model=list[dict[str, Any]])
|
@router.get("/chains/supported", response_model=list[dict[str, Any]])
|
||||||
async def get_supported_chains() -> list[dict[str, Any]]:
|
@rate_limit(rate=500, per=60)
|
||||||
|
async def get_supported_chains(request: Request) -> list[dict[str, Any]]:
|
||||||
"""Get list of supported blockchain chains"""
|
"""Get list of supported blockchain chains"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -554,7 +587,8 @@ async def get_supported_chains() -> list[dict[str, Any]]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/chains/{chain_id}/info", response_model=dict[str, Any])
|
@router.get("/chains/{chain_id}/info", response_model=dict[str, Any])
|
||||||
async def get_chain_info(chain_id: int, session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=500, per=60)
|
||||||
|
async def get_chain_info(request: Request, chain_id: int, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Get information about a specific chain"""
|
"""Get information about a specific chain"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -576,7 +610,8 @@ async def get_chain_info(chain_id: int, session: Session = Depends(get_session))
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health", response_model=dict[str, Any])
|
@router.get("/health", response_model=dict[str, Any])
|
||||||
async def get_cross_chain_health(session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def get_cross_chain_health(request: Request, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Get cross-chain integration health status"""
|
"""Get cross-chain integration health status"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -614,7 +649,8 @@ async def get_cross_chain_health(session: Session = Depends(get_session)) -> dic
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/config", response_model=dict[str, Any])
|
@router.get("/config", response_model=dict[str, Any])
|
||||||
async def get_cross_chain_config(session: Session = Depends(get_session)) -> dict[str, Any]:
|
@rate_limit(rate=500, per=60)
|
||||||
|
async def get_cross_chain_config(request: Request, session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||||
"""Get cross-chain integration configuration"""
|
"""Get cross-chain integration configuration"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ REST API endpoints for the developer ecosystem including bounties, certification
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||||
from sqlmodel import Session, func, select
|
from sqlmodel import Session, func, select
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..domain.developer_platform import (
|
from ..domain.developer_platform import (
|
||||||
BountyStatus,
|
BountyStatus,
|
||||||
CertificationLevel,
|
CertificationLevel,
|
||||||
@@ -35,8 +37,9 @@ def get_governance_service(session: Session = Depends(get_session)) -> Governanc
|
|||||||
|
|
||||||
# Developer Management Endpoints
|
# Developer Management Endpoints
|
||||||
@router.post("/register", response_model=dict[str, Any])
|
@router.post("/register", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=10, per=60)
|
||||||
async def register_developer(
|
async def register_developer(
|
||||||
request: DeveloperCreate,
|
request: DeveloperCreate, request_http: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -61,8 +64,9 @@ async def register_developer(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/profile/{wallet_address}", response_model=dict[str, Any])
|
@router.get("/profile/{wallet_address}", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_developer_profile(
|
async def get_developer_profile(
|
||||||
wallet_address: str,
|
wallet_address: str, request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -93,9 +97,11 @@ async def get_developer_profile(
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/profile/{wallet_address}", response_model=dict[str, Any])
|
@router.put("/profile/{wallet_address}", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=50, per=60)
|
||||||
async def update_developer_profile(
|
async def update_developer_profile(
|
||||||
wallet_address: str,
|
wallet_address: str,
|
||||||
updates: dict[str, Any],
|
updates: dict[str, Any],
|
||||||
|
request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -119,9 +125,11 @@ async def update_developer_profile(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/leaderboard", response_model=list[dict[str, Any]])
|
@router.get("/leaderboard", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_leaderboard(
|
async def get_leaderboard(
|
||||||
limit: int = Query(100, ge=1, le=500, description="Maximum number of developers"),
|
limit: int = Query(100, ge=1, le=500, description="Maximum number of developers"),
|
||||||
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
||||||
|
request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
@@ -149,8 +157,9 @@ async def get_leaderboard(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/stats/{wallet_address}", response_model=dict[str, Any])
|
@router.get("/stats/{wallet_address}", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_developer_stats(
|
async def get_developer_stats(
|
||||||
wallet_address: str,
|
wallet_address: str, request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -168,8 +177,9 @@ async def get_developer_stats(
|
|||||||
|
|
||||||
# Bounty Management Endpoints
|
# Bounty Management Endpoints
|
||||||
@router.post("/bounties", response_model=dict[str, Any])
|
@router.post("/bounties", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_bounty(
|
async def create_bounty(
|
||||||
request: BountyCreate,
|
request: BountyCreate, request_http: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -195,10 +205,12 @@ async def create_bounty(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bounties", response_model=list[dict[str, Any]])
|
@router.get("/bounties", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def list_bounties(
|
async def list_bounties(
|
||||||
status: BountyStatus | None = Query(None, description="Filter by bounty status"),
|
status: BountyStatus | None = Query(None, description="Filter by bounty status"),
|
||||||
limit: int = Query(100, ge=1, le=500, description="Maximum number of bounties"),
|
limit: int = Query(100, ge=1, le=500, description="Maximum number of bounties"),
|
||||||
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
||||||
|
request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
@@ -228,8 +240,9 @@ async def list_bounties(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bounties/{bounty_id}", response_model=dict[str, Any])
|
@router.get("/bounties/{bounty_id}", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_bounty_details(
|
async def get_bounty_details(
|
||||||
bounty_id: str,
|
bounty_id: str, request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -246,9 +259,11 @@ async def get_bounty_details(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/bounties/{bounty_id}/submit", response_model=dict[str, Any])
|
@router.post("/bounties/{bounty_id}/submit", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def submit_bounty_solution(
|
async def submit_bounty_solution(
|
||||||
bounty_id: str,
|
bounty_id: str,
|
||||||
request: BountySubmissionCreate,
|
request: BountySubmissionCreate,
|
||||||
|
request_http: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -275,10 +290,12 @@ async def submit_bounty_solution(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bounties/my-submissions", response_model=list[dict[str, Any]])
|
@router.get("/bounties/my-submissions", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_my_submissions(
|
async def get_my_submissions(
|
||||||
developer_id: str,
|
developer_id: str,
|
||||||
limit: int = Query(100, ge=1, le=500, description="Maximum number of submissions"),
|
limit: int = Query(100, ge=1, le=500, description="Maximum number of submissions"),
|
||||||
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
offset: int = Query(0, ge=0, description="Offset for pagination"),
|
||||||
|
request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
@@ -308,11 +325,13 @@ async def get_my_submissions(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/bounties/{bounty_id}/review", response_model=dict[str, Any])
|
@router.post("/bounties/{bounty_id}/review", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def review_bounty_submission(
|
async def review_bounty_submission(
|
||||||
submission_id: str,
|
submission_id: str,
|
||||||
reviewer_address: str,
|
reviewer_address: str,
|
||||||
review_notes: str,
|
review_notes: str,
|
||||||
approved: bool = Query(True, description="Whether to approve the submission"),
|
approved: bool = Query(True, description="Whether to approve the submission"),
|
||||||
|
request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -344,8 +363,9 @@ async def review_bounty_submission(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/bounties/stats", response_model=dict[str, Any])
|
@router.get("/bounties/stats", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_bounty_statistics(
|
async def get_bounty_statistics(
|
||||||
session: Session = Depends(get_session), dev_service: DeveloperPlatformService = Depends(get_developer_platform_service)
|
request: Request, session: Session = Depends(get_session), dev_service: DeveloperPlatformService = Depends(get_developer_platform_service)
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get comprehensive bounty statistics"""
|
"""Get comprehensive bounty statistics"""
|
||||||
|
|
||||||
@@ -359,8 +379,9 @@ async def get_bounty_statistics(
|
|||||||
|
|
||||||
# Certification Management Endpoints
|
# Certification Management Endpoints
|
||||||
@router.post("/certifications", response_model=dict[str, Any])
|
@router.post("/certifications", response_model=dict[str, Any])
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def grant_certification(
|
async def grant_certification(
|
||||||
request: CertificationGrant,
|
request: CertificationGrant, request_http: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -388,8 +409,9 @@ async def grant_certification(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/certifications/{wallet_address}", response_model=list[dict[str, Any]])
|
@router.get("/certifications/{wallet_address}", response_model=list[dict[str, Any]])
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_developer_certifications(
|
async def get_developer_certifications(
|
||||||
wallet_address: str,
|
wallet_address: str, request: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
dev_service: DeveloperPlatformService = Depends(get_developer_platform_service),
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ REST API for developer ecosystem metrics and analytics
|
|||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
from ..auth import get_current_user
|
from ..auth import get_current_user
|
||||||
from ..domain.bounty import AgentMetrics, BountyStats, EcosystemMetrics
|
from ..domain.bounty import AgentMetrics, BountyStats, EcosystemMetrics
|
||||||
from ..services.ecosystem_service import EcosystemService
|
from ..services.ecosystem_service import EcosystemService
|
||||||
@@ -88,7 +89,9 @@ def get_ecosystem_service(session: Session = Depends(get_session)) -> EcosystemS
|
|||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
@router.get("/ecosystem/developer-earnings", response_model=DeveloperEarningsResponse)
|
@router.get("/ecosystem/developer-earnings", response_model=DeveloperEarningsResponse)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_developer_earnings(
|
async def get_developer_earnings(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service),
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service),
|
||||||
@@ -108,7 +111,9 @@ async def get_developer_earnings(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/agent-utilization", response_model=AgentUtilizationResponse)
|
@router.get("/ecosystem/agent-utilization", response_model=AgentUtilizationResponse)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_agent_utilization(
|
async def get_agent_utilization(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -127,7 +132,9 @@ async def get_agent_utilization(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/treasury-allocation", response_model=TreasuryAllocationResponse)
|
@router.get("/ecosystem/treasury-allocation", response_model=TreasuryAllocationResponse)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_treasury_allocation(
|
async def get_treasury_allocation(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -146,7 +153,9 @@ async def get_treasury_allocation(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/staking-metrics", response_model=StakingMetricsResponse)
|
@router.get("/ecosystem/staking-metrics", response_model=StakingMetricsResponse)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_staking_metrics(
|
async def get_staking_metrics(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -165,7 +174,9 @@ async def get_staking_metrics(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/bounty-analytics", response_model=BountyAnalyticsResponse)
|
@router.get("/ecosystem/bounty-analytics", response_model=BountyAnalyticsResponse)
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_bounty_analytics(
|
async def get_bounty_analytics(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -184,7 +195,9 @@ async def get_bounty_analytics(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/overview", response_model=EcosystemOverviewResponse)
|
@router.get("/ecosystem/overview", response_model=EcosystemOverviewResponse)
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def get_ecosystem_overview(
|
async def get_ecosystem_overview(
|
||||||
|
request: Request,
|
||||||
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
|
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -210,7 +223,9 @@ async def get_ecosystem_overview(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/metrics")
|
@router.get("/ecosystem/metrics")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_ecosystem_metrics(
|
async def get_ecosystem_metrics(
|
||||||
|
request: Request,
|
||||||
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
|
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
|
||||||
start_date: Optional[datetime] = None,
|
start_date: Optional[datetime] = None,
|
||||||
end_date: Optional[datetime] = None,
|
end_date: Optional[datetime] = None,
|
||||||
@@ -238,9 +253,9 @@ async def get_ecosystem_metrics(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/health-score")
|
@router.get("/ecosystem/health-score")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_ecosystem_health_score(
|
async def get_ecosystem_health_score(
|
||||||
session: Session = Depends(get_session),
|
request: Request, session: Session = Depends(get_session), ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Get overall ecosystem health score"""
|
"""Get overall ecosystem health score"""
|
||||||
try:
|
try:
|
||||||
@@ -258,7 +273,9 @@ async def get_ecosystem_health_score(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/growth-indicators")
|
@router.get("/ecosystem/growth-indicators")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_growth_indicators(
|
async def get_growth_indicators(
|
||||||
|
request: Request,
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
|
||||||
@@ -279,7 +296,9 @@ async def get_growth_indicators(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@router.get("/ecosystem/top-performers")
|
@router.get("/ecosystem/top-performers")
|
||||||
|
@rate_limit(rate=200, per=60)
|
||||||
async def get_top_performers(
|
async def get_top_performers(
|
||||||
|
request: Request,
|
||||||
category: str = Field(default="all", regex="^(developers|agents|stakers|all)$"),
|
category: str = Field(default="all", regex="^(developers|agents|stakers|all)$"),
|
||||||
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
|
||||||
limit: int = Field(default=50, ge=1, le=100),
|
limit: int = Field(default=50, ge=1, le=100),
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..schemas import (
|
from ..schemas import (
|
||||||
AddressListResponse,
|
AddressListResponse,
|
||||||
BlockListResponse,
|
BlockListResponse,
|
||||||
@@ -22,7 +24,9 @@ def _service(session: Annotated[Session, Depends(get_session)]) -> ExplorerServi
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/blocks", response_model=BlockListResponse, summary="List recent blocks")
|
@router.get("/blocks", response_model=BlockListResponse, summary="List recent blocks")
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def list_blocks(
|
async def list_blocks(
|
||||||
|
request: Request,
|
||||||
*,
|
*,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
limit: int = Query(default=20, ge=1, le=200),
|
limit: int = Query(default=20, ge=1, le=200),
|
||||||
@@ -36,7 +40,9 @@ async def list_blocks(
|
|||||||
response_model=TransactionListResponse,
|
response_model=TransactionListResponse,
|
||||||
summary="List recent transactions",
|
summary="List recent transactions",
|
||||||
)
|
)
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def list_transactions(
|
async def list_transactions(
|
||||||
|
request: Request,
|
||||||
*,
|
*,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
@@ -46,7 +52,9 @@ async def list_transactions(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/addresses", response_model=AddressListResponse, summary="List address summaries")
|
@router.get("/addresses", response_model=AddressListResponse, summary="List address summaries")
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def list_addresses(
|
async def list_addresses(
|
||||||
|
request: Request,
|
||||||
*,
|
*,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
@@ -56,7 +64,9 @@ async def list_addresses(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/receipts", response_model=ReceiptListResponse, summary="List job receipts")
|
@router.get("/receipts", response_model=ReceiptListResponse, summary="List job receipts")
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def list_receipts(
|
async def list_receipts(
|
||||||
|
request: Request,
|
||||||
*,
|
*,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
job_id: str | None = Query(default=None, description="Filter by job identifier"),
|
job_id: str | None = Query(default=None, description="Filter by job identifier"),
|
||||||
@@ -67,7 +77,9 @@ async def list_receipts(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/transactions/{tx_hash}", summary="Get transaction details by hash")
|
@router.get("/transactions/{tx_hash}", summary="Get transaction details by hash")
|
||||||
|
@rate_limit(rate=100, per=60)
|
||||||
async def get_transaction(
|
async def get_transaction(
|
||||||
|
request: Request,
|
||||||
*,
|
*,
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
tx_hash: str,
|
tx_hash: str,
|
||||||
|
|||||||
@@ -11,16 +11,19 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"], summary="GPU Multi-Modal Service Health")
|
@router.get("/health", tags=["health"], summary="GPU Multi-Modal Service Health")
|
||||||
async def gpu_multimodal_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def gpu_multimodal_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Health check for GPU Multi-Modal Service (Port 8010)
|
Health check for GPU Multi-Modal Service (Port 8010)
|
||||||
"""
|
"""
|
||||||
@@ -92,7 +95,8 @@ async def gpu_multimodal_health(session: Annotated[Session, Depends(get_session)
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health/deep", tags=["health"], summary="Deep GPU Multi-Modal Service Health")
|
@router.get("/health/deep", tags=["health"], summary="Deep GPU Multi-Modal Service Health")
|
||||||
async def gpu_multimodal_deep_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def gpu_multimodal_deep_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Deep health check with CUDA performance validation
|
Deep health check with CUDA performance validation
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
|
||||||
from ..services.hermes_enhanced import hermesEnhancedService
|
from ..services.hermes_enhanced import hermesEnhancedService
|
||||||
@@ -23,7 +25,8 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"], summary="hermes Enhanced Service Health")
|
@router.get("/health", tags=["health"], summary="hermes Enhanced Service Health")
|
||||||
async def hermes_enhanced_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def hermes_enhanced_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Health check for hermes Enhanced Service (Port 8007)
|
Health check for hermes Enhanced Service (Port 8007)
|
||||||
"""
|
"""
|
||||||
@@ -101,7 +104,8 @@ async def hermes_enhanced_health(session: Annotated[Session, Depends(get_session
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health/deep", tags=["health"], summary="Deep hermes Enhanced Service Health")
|
@router.get("/health/deep", tags=["health"], summary="Deep hermes Enhanced Service Health")
|
||||||
async def hermes_enhanced_deep_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def hermes_enhanced_deep_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Deep health check with hermes ecosystem validation
|
Deep health check with hermes ecosystem validation
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
from ..services.marketplace_enhanced import EnhancedMarketplaceService
|
from ..services.marketplace_enhanced import EnhancedMarketplaceService
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
@@ -24,7 +26,8 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"], summary="Enhanced Marketplace Service Health")
|
@router.get("/health", tags=["health"], summary="Enhanced Marketplace Service Health")
|
||||||
async def marketplace_enhanced_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def marketplace_enhanced_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Health check for Enhanced Marketplace Service (Port 8002)
|
Health check for Enhanced Marketplace Service (Port 8002)
|
||||||
"""
|
"""
|
||||||
@@ -104,7 +107,8 @@ async def marketplace_enhanced_health(session: Annotated[Session, Depends(get_se
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health/deep", tags=["health"], summary="Deep Enhanced Marketplace Service Health")
|
@router.get("/health/deep", tags=["health"], summary="Deep Enhanced Marketplace Service Health")
|
||||||
async def marketplace_enhanced_deep_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def marketplace_enhanced_deep_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Deep health check with marketplace feature validation
|
Deep health check with marketplace feature validation
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -88,7 +89,8 @@ class ScalingPolicyUpdate(BaseModel):
|
|||||||
|
|
||||||
# Endpoints: GPU Optimization
|
# Endpoints: GPU Optimization
|
||||||
@router.post("/gpu/allocate")
|
@router.post("/gpu/allocate")
|
||||||
async def allocate_gpu_resources(request: GPUAllocationRequest) -> dict[str, Any]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def allocate_gpu_resources(request: Request, gpu_request: GPUAllocationRequest) -> dict[str, Any]:
|
||||||
"""Request optimal GPU resource allocation for a marketplace task"""
|
"""Request optimal GPU resource allocation for a marketplace task"""
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -108,7 +110,8 @@ async def allocate_gpu_resources(request: GPUAllocationRequest) -> dict[str, Any
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/gpu/release")
|
@router.post("/gpu/release")
|
||||||
async def release_gpu_resources(request: GPUReleaseRequest) -> dict[str, str]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def release_gpu_resources(request: Request, gpu_request: GPUReleaseRequest) -> dict[str, str]:
|
||||||
"""Release previously allocated GPU resources"""
|
"""Release previously allocated GPU resources"""
|
||||||
success = gpu_optimizer.release_resources(request.job_id)
|
success = gpu_optimizer.release_resources(request.job_id)
|
||||||
if not success:
|
if not success:
|
||||||
@@ -117,14 +120,16 @@ async def release_gpu_resources(request: GPUReleaseRequest) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/gpu/status")
|
@router.get("/gpu/status")
|
||||||
async def get_gpu_status() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_gpu_status(request: Request) -> dict[str, Any]:
|
||||||
"""Get overall GPU fleet status and optimization metrics"""
|
"""Get overall GPU fleet status and optimization metrics"""
|
||||||
return gpu_optimizer.get_system_status()
|
return gpu_optimizer.get_system_status()
|
||||||
|
|
||||||
|
|
||||||
# Endpoints: Distributed Processing
|
# Endpoints: Distributed Processing
|
||||||
@router.post("/distributed/task")
|
@router.post("/distributed/task")
|
||||||
async def submit_distributed_task(request: DistributedTaskRequest) -> dict[str, str]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def submit_distributed_task(request: Request, task_request: DistributedTaskRequest) -> dict[str, str]:
|
||||||
"""Submit a task to the distributed processing framework"""
|
"""Submit a task to the distributed processing framework"""
|
||||||
task = DistributedTask(
|
task = DistributedTask(
|
||||||
task_id=None,
|
task_id=None,
|
||||||
@@ -140,7 +145,8 @@ async def submit_distributed_task(request: DistributedTaskRequest) -> dict[str,
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/distributed/task/{task_id}")
|
@router.get("/distributed/task/{task_id}")
|
||||||
async def get_distributed_task_status(task_id: str) -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_distributed_task_status(request: Request, task_id: str) -> dict[str, Any]:
|
||||||
"""Check the status and get results of a distributed task"""
|
"""Check the status and get results of a distributed task"""
|
||||||
status = await distributed_coordinator.get_task_status(task_id)
|
status = await distributed_coordinator.get_task_status(task_id)
|
||||||
if not status:
|
if not status:
|
||||||
@@ -149,7 +155,8 @@ async def get_distributed_task_status(task_id: str) -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/distributed/worker/register")
|
@router.post("/distributed/worker/register")
|
||||||
async def register_worker(request: WorkerRegistrationRequest) -> dict[str, str]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def register_worker(request: Request, worker_request: WorkerRegistrationRequest) -> dict[str, str]:
|
||||||
"""Register a new worker node in the cluster"""
|
"""Register a new worker node in the cluster"""
|
||||||
distributed_coordinator.register_worker(
|
distributed_coordinator.register_worker(
|
||||||
worker_id=request.worker_id,
|
worker_id=request.worker_id,
|
||||||
@@ -161,14 +168,16 @@ async def register_worker(request: WorkerRegistrationRequest) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/distributed/status")
|
@router.get("/distributed/status")
|
||||||
async def get_cluster_status() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_cluster_status(request: Request) -> dict[str, Any]:
|
||||||
"""Get overall distributed cluster health and load"""
|
"""Get overall distributed cluster health and load"""
|
||||||
return distributed_coordinator.get_cluster_status()
|
return distributed_coordinator.get_cluster_status()
|
||||||
|
|
||||||
|
|
||||||
# Endpoints: Caching
|
# Endpoints: Caching
|
||||||
@router.get("/cache/stats")
|
@router.get("/cache/stats")
|
||||||
async def get_cache_stats() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_cache_stats(request: Request) -> dict[str, Any]:
|
||||||
"""Get current caching performance statistics"""
|
"""Get current caching performance statistics"""
|
||||||
return {
|
return {
|
||||||
"status": "connected" if cache_optimizer.is_connected else "local_only",
|
"status": "connected" if cache_optimizer.is_connected else "local_only",
|
||||||
@@ -178,7 +187,8 @@ async def get_cache_stats() -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/cache/invalidate/{namespace}")
|
@router.post("/cache/invalidate/{namespace}")
|
||||||
async def invalidate_cache_namespace(namespace: str, background_tasks: BackgroundTasks) -> dict[str, str]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def invalidate_cache_namespace(request: Request, namespace: str, background_tasks: BackgroundTasks) -> dict[str, str]:
|
||||||
"""Invalidate a specific cache namespace (e.g., 'order_book')"""
|
"""Invalidate a specific cache namespace (e.g., 'order_book')"""
|
||||||
background_tasks.add_task(cache_optimizer.invalidate_namespace, namespace)
|
background_tasks.add_task(cache_optimizer.invalidate_namespace, namespace)
|
||||||
return {"success": True, "message": f"Invalidation for {namespace} queued"}
|
return {"success": True, "message": f"Invalidation for {namespace} queued"}
|
||||||
@@ -186,20 +196,23 @@ async def invalidate_cache_namespace(namespace: str, background_tasks: Backgroun
|
|||||||
|
|
||||||
# Endpoints: Monitoring
|
# Endpoints: Monitoring
|
||||||
@router.get("/monitor/dashboard")
|
@router.get("/monitor/dashboard")
|
||||||
async def get_monitoring_dashboard() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_monitoring_dashboard(request: Request) -> dict[str, Any]:
|
||||||
"""Get real-time performance dashboard data"""
|
"""Get real-time performance dashboard data"""
|
||||||
return marketplace_monitor.get_realtime_dashboard_data()
|
return marketplace_monitor.get_realtime_dashboard_data()
|
||||||
|
|
||||||
|
|
||||||
# Endpoints: Auto-scaling
|
# Endpoints: Auto-scaling
|
||||||
@router.get("/scaler/status")
|
@router.get("/scaler/status")
|
||||||
async def get_scaler_status() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def get_scaler_status(request: Request) -> dict[str, Any]:
|
||||||
"""Get current auto-scaler status and active rules"""
|
"""Get current auto-scaler status and active rules"""
|
||||||
return resource_scaler.get_status()
|
return resource_scaler.get_status()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/scaler/policy")
|
@router.post("/scaler/policy")
|
||||||
async def update_scaling_policy(policy_update: ScalingPolicyUpdate) -> dict[str, str]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def update_scaling_policy(request: Request, policy_update: ScalingPolicyUpdate) -> dict[str, str]:
|
||||||
"""Update auto-scaling thresholds and parameters dynamically"""
|
"""Update auto-scaling thresholds and parameters dynamically"""
|
||||||
current_policy = resource_scaler.policy
|
current_policy = resource_scaler.policy
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
"""Monitor router for AITBC Coordinator API."""
|
"""Monitor router for AITBC Coordinator API."""
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, Request
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
router = APIRouter(tags=["Monitor"])
|
router = APIRouter(tags=["Monitor"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/v1/dashboard", response_model=dict)
|
@router.get("/api/v1/dashboard", response_model=dict)
|
||||||
async def get_dashboard():
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_dashboard(request: Request):
|
||||||
"""Get monitoring dashboard data."""
|
"""Get monitoring dashboard data."""
|
||||||
return {
|
return {
|
||||||
"overall_status": "operational",
|
"overall_status": "operational",
|
||||||
@@ -26,7 +29,8 @@ async def get_dashboard():
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/status", response_model=dict)
|
@router.get("/status", response_model=dict)
|
||||||
async def get_status():
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_status(request: Request):
|
||||||
"""Get coordinator status."""
|
"""Get coordinator status."""
|
||||||
return {
|
return {
|
||||||
"status": "online",
|
"status": "online",
|
||||||
@@ -37,18 +41,21 @@ async def get_status():
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/miners", response_model=List[Dict])
|
@router.get("/miners", response_model=List[Dict])
|
||||||
async def get_miners():
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_miners(request: Request):
|
||||||
"""Get miners list."""
|
"""Get miners list."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dashboard", response_model=List[Dict])
|
@router.get("/dashboard", response_model=List[Dict])
|
||||||
async def get_history_dashboard():
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_history_dashboard(request: Request):
|
||||||
"""Get historical dashboard data."""
|
"""Get historical dashboard data."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@router.get("/jobs", response_model=List[Dict])
|
@router.get("/jobs", response_model=List[Dict])
|
||||||
async def get_jobs():
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_jobs(request: Request):
|
||||||
"""Get jobs list for history and metrics commands."""
|
"""Get jobs list for history and metrics commands."""
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import asyncio
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
from aitbc import get_logger, AITBCHTTPClient, NetworkError
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -63,7 +65,8 @@ SERVICES = {
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/dashboard", tags=["monitoring"], summary="Enhanced Services Dashboard")
|
@router.get("/dashboard", tags=["monitoring"], summary="Enhanced Services Dashboard")
|
||||||
async def monitoring_dashboard() -> dict[str, Any]:
|
@rate_limit(rate=200, per=60)
|
||||||
|
async def monitoring_dashboard(request: Request) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Unified monitoring dashboard for all enhanced services
|
Unified monitoring dashboard for all enhanced services
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..services.multimodal_agent import MultiModalAgentService
|
from ..services.multimodal_agent import MultiModalAgentService
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
|
|
||||||
@@ -20,7 +22,8 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health", tags=["health"], summary="Multi-Modal Agent Service Health")
|
@router.get("/health", tags=["health"], summary="Multi-Modal Agent Service Health")
|
||||||
async def multimodal_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def multimodal_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Health check for Multi-Modal Agent Service (Port 8002)
|
Health check for Multi-Modal Agent Service (Port 8002)
|
||||||
"""
|
"""
|
||||||
@@ -87,7 +90,8 @@ async def multimodal_health(session: Annotated[Session, Depends(get_session)]) -
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/health/deep", tags=["health"], summary="Deep Multi-Modal Service Health")
|
@router.get("/health/deep", tags=["health"], summary="Deep Multi-Modal Service Health")
|
||||||
async def multimodal_deep_health(session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def multimodal_deep_health(request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Deep health check with detailed multi-modal processing tests
|
Deep health check with detailed multi-modal processing tests
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import secrets
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
|
|
||||||
router = APIRouter(tags=["partners"])
|
router = APIRouter(tags=["partners"])
|
||||||
@@ -63,7 +65,8 @@ WEBHOOKS_DB = {}
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/partners/register", response_model=PartnerResponse)
|
@router.post("/partners/register", response_model=PartnerResponse)
|
||||||
async def register_partner(partner: PartnerRegister, session: Annotated[Session, Depends(get_session)]) -> PartnerResponse:
|
@rate_limit(rate=10, per=60)
|
||||||
|
async def register_partner(partner: PartnerRegister, request: Request, session: Annotated[Session, Depends(get_session)]) -> PartnerResponse:
|
||||||
"""Register a new partner application"""
|
"""Register a new partner application"""
|
||||||
|
|
||||||
# Generate credentials
|
# Generate credentials
|
||||||
@@ -105,7 +108,8 @@ async def register_partner(partner: PartnerRegister, session: Annotated[Session,
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/partners/{partner_id}")
|
@router.get("/partners/{partner_id}")
|
||||||
async def get_partner(partner_id: str, session: Annotated[Session, Depends(get_session)], api_key: str) -> dict[str, Any]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_partner(partner_id: str, request: Request, session: Annotated[Session, Depends(get_session)], api_key: str) -> dict[str, Any]:
|
||||||
"""Get partner information"""
|
"""Get partner information"""
|
||||||
|
|
||||||
# Verify API key
|
# Verify API key
|
||||||
@@ -125,8 +129,9 @@ async def get_partner(partner_id: str, session: Annotated[Session, Depends(get_s
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/partners/webhooks", response_model=WebhookResponse)
|
@router.post("/partners/webhooks", response_model=WebhookResponse)
|
||||||
|
@rate_limit(rate=20, per=60)
|
||||||
async def create_webhook(
|
async def create_webhook(
|
||||||
webhook: WebhookCreate, session: Annotated[Session, Depends(get_session)], api_key: str
|
webhook: WebhookCreate, request: Request, session: Annotated[Session, Depends(get_session)], api_key: str
|
||||||
) -> WebhookResponse:
|
) -> WebhookResponse:
|
||||||
"""Create a webhook subscription"""
|
"""Create a webhook subscription"""
|
||||||
|
|
||||||
@@ -175,7 +180,8 @@ async def create_webhook(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/partners/webhooks")
|
@router.get("/partners/webhooks")
|
||||||
async def list_webhooks(session: Annotated[Session, Depends(get_session)], api_key: str) -> list[WebhookResponse]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def list_webhooks(request: Request, session: Annotated[Session, Depends(get_session)], api_key: str) -> list[WebhookResponse]:
|
||||||
"""List partner webhooks"""
|
"""List partner webhooks"""
|
||||||
|
|
||||||
# Verify partner
|
# Verify partner
|
||||||
@@ -201,7 +207,8 @@ async def list_webhooks(session: Annotated[Session, Depends(get_session)], api_k
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/partners/webhooks/{webhook_id}")
|
@router.delete("/partners/webhooks/{webhook_id}")
|
||||||
async def delete_webhook(webhook_id: str, session: Annotated[Session, Depends(get_session)], api_key: str) -> dict[str, str]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def delete_webhook(webhook_id: str, request: Request, session: Annotated[Session, Depends(get_session)], api_key: str) -> dict[str, str]:
|
||||||
"""Delete a webhook"""
|
"""Delete a webhook"""
|
||||||
|
|
||||||
# Verify partner
|
# Verify partner
|
||||||
@@ -221,8 +228,9 @@ async def delete_webhook(webhook_id: str, session: Annotated[Session, Depends(ge
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/partners/analytics/usage")
|
@router.get("/partners/analytics/usage")
|
||||||
|
@rate_limit(rate=30, per=60)
|
||||||
async def get_usage_analytics(
|
async def get_usage_analytics(
|
||||||
session: Annotated[Session, Depends(get_session)], api_key: str, period: str = "24h"
|
request: Request, session: Annotated[Session, Depends(get_session)], api_key: str, period: str = "24h"
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Get API usage analytics"""
|
"""Get API usage analytics"""
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import uuid
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
|
|
||||||
from ..domain import User, Wallet
|
from ..domain import User, Wallet
|
||||||
from ..schemas import UserBalance, UserCreate, UserLogin, UserProfile
|
from ..schemas import UserBalance, UserCreate, UserLogin, UserProfile
|
||||||
from ..storage import get_session
|
from ..storage import get_session
|
||||||
@@ -56,7 +58,8 @@ def verify_session_token(token: str) -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/register", response_model=UserProfile)
|
@router.post("/register", response_model=UserProfile)
|
||||||
async def register_user(user_data: UserCreate, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=10, per=60)
|
||||||
|
async def register_user(user_data: UserCreate, request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Register a new user"""
|
"""Register a new user"""
|
||||||
|
|
||||||
# Check if user already exists
|
# Check if user already exists
|
||||||
@@ -97,7 +100,8 @@ async def register_user(user_data: UserCreate, session: Annotated[Session, Depen
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=UserProfile)
|
@router.post("/login", response_model=UserProfile)
|
||||||
async def login_user(login_data: UserLogin, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def login_user(login_data: UserLogin, request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Login user with wallet address"""
|
"""Login user with wallet address"""
|
||||||
|
|
||||||
# For demo, we'll create or get user by wallet address
|
# For demo, we'll create or get user by wallet address
|
||||||
@@ -144,7 +148,8 @@ async def login_user(login_data: UserLogin, session: Annotated[Session, Depends(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/users/me", response_model=UserProfile)
|
@router.get("/users/me", response_model=UserProfile)
|
||||||
async def get_current_user(token: str, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def get_current_user(token: str, request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Get current user profile"""
|
"""Get current user profile"""
|
||||||
|
|
||||||
user_id = verify_session_token(token)
|
user_id = verify_session_token(token)
|
||||||
@@ -165,7 +170,8 @@ async def get_current_user(token: str, session: Annotated[Session, Depends(get_s
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/users/{user_id}/balance", response_model=UserBalance)
|
@router.get("/users/{user_id}/balance", response_model=UserBalance)
|
||||||
async def get_user_balance(user_id: str, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_user_balance(user_id: str, request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Get user's AITBC balance"""
|
"""Get user's AITBC balance"""
|
||||||
|
|
||||||
wallet = session.execute(select(Wallet).where(Wallet.user_id == user_id)).first()
|
wallet = session.execute(select(Wallet).where(Wallet.user_id == user_id)).first()
|
||||||
@@ -182,7 +188,8 @@ async def get_user_balance(user_id: str, session: Annotated[Session, Depends(get
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/logout")
|
@router.post("/logout")
|
||||||
async def logout_user(token: str) -> dict[str, str]:
|
@rate_limit(rate=20, per=60)
|
||||||
|
async def logout_user(token: str, request: Request) -> dict[str, str]:
|
||||||
"""Logout user and invalidate session"""
|
"""Logout user and invalidate session"""
|
||||||
|
|
||||||
if token in user_sessions:
|
if token in user_sessions:
|
||||||
@@ -192,7 +199,8 @@ async def logout_user(token: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/users/{user_id}/transactions")
|
@router.get("/users/{user_id}/transactions")
|
||||||
async def get_user_transactions(user_id: str, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
@rate_limit(rate=50, per=60)
|
||||||
|
async def get_user_transactions(user_id: str, request: Request, session: Annotated[Session, Depends(get_session)]) -> dict[str, Any]:
|
||||||
"""Get user's transaction history"""
|
"""Get user's transaction history"""
|
||||||
|
|
||||||
# For demo, return empty list
|
# For demo, return empty list
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ Web Vitals API endpoint for collecting performance metrics
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from aitbc import get_logger
|
from aitbc import get_logger
|
||||||
|
from aitbc.rate_limiting import rate_limit
|
||||||
from ..auth import get_api_key
|
from ..auth import get_api_key
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -35,7 +37,8 @@ class WebVitalsMetric(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/web-vitals")
|
@router.post("/web-vitals")
|
||||||
async def collect_web_vitals(metric: WebVitalsMetric) -> dict[str, Any]:
|
@rate_limit(rate=100, per=60)
|
||||||
|
async def collect_web_vitals(request: Request, metric: WebVitalsMetric) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Collect Web Vitals performance metrics from the frontend.
|
Collect Web Vitals performance metrics from the frontend.
|
||||||
This endpoint receives Core Web Vitals (LCP, FID, CLS, TTFB, FCP) for monitoring.
|
This endpoint receives Core Web Vitals (LCP, FID, CLS, TTFB, FCP) for monitoring.
|
||||||
@@ -79,6 +82,7 @@ async def collect_web_vitals(metric: WebVitalsMetric) -> dict[str, Any]:
|
|||||||
|
|
||||||
# Health check for web vitals endpoint
|
# Health check for web vitals endpoint
|
||||||
@router.get("/web-vitals/health")
|
@router.get("/web-vitals/health")
|
||||||
async def web_vitals_health() -> dict[str, str]:
|
@rate_limit(rate=1000, per=60)
|
||||||
|
async def web_vitals_health(request: Request) -> dict[str, str]:
|
||||||
"""Health check for web vitals collection endpoint"""
|
"""Health check for web vitals collection endpoint"""
|
||||||
return {"status": "healthy", "service": "web-vitals"}
|
return {"status": "healthy", "service": "web-vitals"}
|
||||||
|
|||||||
@@ -167,12 +167,45 @@
|
|||||||
- Helper functions: is_feature_enabled(), get_feature_flag_manager()
|
- Helper functions: is_feature_enabled(), get_feature_flag_manager()
|
||||||
- Comprehensive tests: tests/test_feature_flags.py (30+ test cases, 404 lines)
|
- Comprehensive tests: tests/test_feature_flags.py (30+ test cases, 404 lines)
|
||||||
- Features: gradual rollouts, user whitelisting/blacklisting, percentage-based targeting, timestamp tracking
|
- Features: gradual rollouts, user whitelisting/blacklisting, percentage-based targeting, timestamp tracking
|
||||||
- [ ] Implement comprehensive observability
|
- [DONE] Implement comprehensive observability - COMPLETED
|
||||||
- [ ] Design contract upgrade pattern
|
- aitbc/metrics.py implements Prometheus metrics (Counter, Histogram, Gauge, Info)
|
||||||
|
- Metrics for: block processing, job processing, API requests, uptime, service info
|
||||||
|
- Decorators: @track_block_processing, @track_job_processing, @track_http_request
|
||||||
|
- Helper functions: update_block_height, update_jobs_in_queue, increment_service_restarts
|
||||||
|
- ASGI metrics endpoint via make_asgi_app()
|
||||||
|
- aitbc/monitoring.py implements MetricsCollector, PerformanceTimer, HealthChecker
|
||||||
|
- Health checks with overall status calculation (healthy, degraded, unhealthy)
|
||||||
|
- Alerting exists in apps/agent-coordinator/src/app/monitoring/alerting.py and apps/coordinator-api/src/app/utils/alerting.py
|
||||||
|
- Comprehensive tests: tests/test_metrics.py (30+ test cases, 251 lines), tests/test_monitoring.py (30+ test cases, 353 lines)
|
||||||
|
- Enhanced aitbc/aitbc_logging.py with structured JSON logging (StructuredFormatter, log_context, LogContext)
|
||||||
|
- Created aitbc/tracing.py for OpenTelemetry-based distributed tracing
|
||||||
|
- Tracing features: setup_tracing, instrument_fastapi, instrument_httpx, instrument_sqlalchemy
|
||||||
|
- Decorators: trace_function, trace_async_function for automatic instrumentation
|
||||||
|
- Context manager: trace_span for manual span creation
|
||||||
|
- Created aitbc/alerting.py for centralized alerting system (AlertManager, AlertRule, AlertChannel)
|
||||||
|
- Created metrics dashboard configuration at infra/monitoring/aitbc-dashboard.json
|
||||||
|
- All observability components tested and imports verified
|
||||||
|
- [DONE] Design contract upgrade pattern - COMPLETED
|
||||||
|
- apps/blockchain-node/src/aitbc_chain/contracts/upgrades.py implements comprehensive contract upgrade system (543 lines)
|
||||||
|
- Core components: UpgradeStatus enum, UpgradeType enum, ContractVersion dataclass, UpgradeProposal dataclass
|
||||||
|
- ContractUpgradeManager with proposal creation, stake-weighted governance voting, upgrade execution, rollback mechanism
|
||||||
|
- Features: voting deadlines (3-7 days), 60% approval requirement, 30% minimum participation, emergency upgrades (80% threshold)
|
||||||
|
- Rollback window (7 days), version history tracking, upgrade statistics
|
||||||
|
- Contract examples: guardian_contract.py (683 lines), agent_messaging_contract.py (520 lines)
|
||||||
|
- Global upgrade manager singleton pattern
|
||||||
|
- Security: proposer authorization, version validation, proposal deduplication
|
||||||
|
|
||||||
### Distribution & Binaries
|
### Distribution & Binaries
|
||||||
|
|
||||||
- [ ] Debian stable miner binary (build workflow exists, binary built but distribution mechanism pending)
|
- [DONE] Debian stable miner binary - COMPLETED
|
||||||
|
- Build workflow exists: .gitea/workflows/build-miner-binary.yml
|
||||||
|
- Binary built using PyInstaller with vLLM and Ollama support
|
||||||
|
- Package includes: binary, README.md, install.sh, verify-install.sh, miner.env.template, SHA256SUMS
|
||||||
|
- Distribution mechanism implemented: Gitea releases API integration
|
||||||
|
- Updated build workflow to create Gitea releases and upload assets automatically
|
||||||
|
- Updated README.md to reference Gitea releases instead of GitHub
|
||||||
|
- Binary and package uploaded to Gitea releases on tag push
|
||||||
|
- Checksum verification supported via SHA256SUMS file
|
||||||
- [ ] Binary distribution via GitHub Releases (deferred until v1 release - policy: no GitHub Releases before v1)
|
- [ ] Binary distribution via GitHub Releases (deferred until v1 release - policy: no GitHub Releases before v1)
|
||||||
|
|
||||||
### Quality Assurance
|
### Quality Assurance
|
||||||
|
|||||||
260
infra/monitoring/aitbc-dashboard.json
Normal file
260
infra/monitoring/aitbc-dashboard.json
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
{
|
||||||
|
"dashboard": {
|
||||||
|
"title": "AITBC System Dashboard",
|
||||||
|
"tags": ["aitbc", "blockchain", "ai"],
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 16,
|
||||||
|
"version": 0,
|
||||||
|
"refresh": "10s",
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Block Processing Duration",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, rate(block_processing_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "95th percentile",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, rate(block_processing_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "50th percentile",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "s",
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"xaxis": {
|
||||||
|
"mode": "time",
|
||||||
|
"name": "Time",
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Block Height",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "block_height",
|
||||||
|
"legendFormat": "Block Height",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": "Block Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Job Processing Duration",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, rate(job_processing_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "95th percentile",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, rate(job_processing_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "50th percentile",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "s",
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "Jobs in Queue",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "jobs_in_queue",
|
||||||
|
"legendFormat": "Queued Jobs",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": "Job Count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "HTTP Request Duration",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "95th percentile",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))",
|
||||||
|
"legendFormat": "50th percentile",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "s",
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "HTTP Requests by Status",
|
||||||
|
"type": "graph",
|
||||||
|
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "rate(http_requests_total[5m])",
|
||||||
|
"legendFormat": "{{method}} {{endpoint}} {{status}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "reqps",
|
||||||
|
"label": "Requests/sec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"title": "Job Success Rate",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 24},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "rate(jobs_total{status=\"completed\"}[5m]) / rate(jobs_total[5m]) * 100",
|
||||||
|
"legendFormat": "Success Rate",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"orientation": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"title": "Job Failure Rate",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 24},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "rate(jobs_total{status=\"failed\"}[5m]) / rate(jobs_total[5m]) * 100",
|
||||||
|
"legendFormat": "Failure Rate",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"orientation": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percent",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"title": "Service Uptime",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 24},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "service_uptime_seconds",
|
||||||
|
"legendFormat": "Uptime",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"orientation": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Service Restarts",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 24},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "service_restart_count",
|
||||||
|
"legendFormat": "Restarts",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"orientation": "auto"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "short"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user