mirror of
https://github.com/sstent/AICyclingCoach.git
synced 2026-01-25 16:41:58 +00:00
sync
This commit is contained in:
@@ -11,6 +11,7 @@ from .routes import rule as rule_routes
|
||||
from .routes import plan as plan_routes
|
||||
from .routes import workouts as workout_routes
|
||||
from .routes import prompts as prompt_routes
|
||||
from .routes import dashboard as dashboard_routes
|
||||
from .config import settings
|
||||
|
||||
app = FastAPI(
|
||||
@@ -46,6 +47,7 @@ app.include_router(rule_routes.router)
|
||||
app.include_router(plan_routes.router)
|
||||
app.include_router(workout_routes.router, prefix="/workouts", tags=["workouts"])
|
||||
app.include_router(prompt_routes.router, prefix="/prompts", tags=["prompts"])
|
||||
app.include_router(dashboard_routes.router, prefix="/api/dashboard", tags=["dashboard"])
|
||||
|
||||
async def check_migration_status():
|
||||
"""Check if database migrations are up to date."""
|
||||
|
||||
52
backend/app/routes/dashboard.py
Normal file
52
backend/app/routes/dashboard.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.models.workout import Workout
|
||||
from app.models.plan import Plan
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from sqlalchemy import select, desc
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/dashboard")
|
||||
async def get_dashboard_data(db: AsyncSession = Depends(get_db)):
|
||||
"""Get consolidated dashboard data"""
|
||||
try:
|
||||
# Recent workouts (last 7 days)
|
||||
workout_result = await db.execute(
|
||||
select(Workout)
|
||||
.where(Workout.start_time >= datetime.now() - timedelta(days=7))
|
||||
.order_by(desc(Workout.start_time))
|
||||
.limit(5)
|
||||
)
|
||||
recent_workouts = [w.to_dict() for w in workout_result.scalars().all()]
|
||||
|
||||
# Current active plan
|
||||
plan_result = await db.execute(
|
||||
select(Plan)
|
||||
.where(Plan.active == True)
|
||||
.order_by(desc(Plan.created_at))
|
||||
)
|
||||
current_plan = plan_result.scalar_one_or_none()
|
||||
|
||||
# Sync status
|
||||
sync_result = await db.execute(
|
||||
select(GarminSyncLog)
|
||||
.order_by(desc(GarminSyncLog.created_at))
|
||||
.limit(1)
|
||||
)
|
||||
last_sync = sync_result.scalar_one_or_none()
|
||||
|
||||
return {
|
||||
"recent_workouts": recent_workouts,
|
||||
"current_plan": current_plan.to_dict() if current_plan else None,
|
||||
"last_sync": last_sync.to_dict() if last_sync else None,
|
||||
"metrics": {
|
||||
"weekly_volume": sum(w.duration_seconds for w in recent_workouts) / 3600,
|
||||
"plan_progress": current_plan.progress if current_plan else 0
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Dashboard data error: {str(e)}")
|
||||
9
backend/app/routes/health.py
Normal file
9
backend/app/routes/health.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
from app.services.health_monitor import HealthMonitor
|
||||
|
||||
router = APIRouter()
|
||||
monitor = HealthMonitor()
|
||||
|
||||
@router.get("/health")
|
||||
async def get_health():
|
||||
return monitor.check_system_health()
|
||||
80
backend/app/services/health_monitor.py
Normal file
80
backend/app/services/health_monitor.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from sqlalchemy import text
|
||||
from app.database import get_db
|
||||
from app.models.garmin_sync_log import GarminSyncLog, SyncStatus
|
||||
import requests
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HealthMonitor:
|
||||
def __init__(self):
|
||||
self.warning_thresholds = {
|
||||
'cpu_percent': 80,
|
||||
'memory_percent': 75,
|
||||
'disk_percent': 85
|
||||
}
|
||||
|
||||
def check_system_health(self) -> Dict[str, Any]:
|
||||
"""Check vital system metrics and log warnings"""
|
||||
metrics = {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'cpu': psutil.cpu_percent(),
|
||||
'memory': psutil.virtual_memory().percent,
|
||||
'disk': psutil.disk_usage('/').percent,
|
||||
'services': self._check_service_health()
|
||||
}
|
||||
|
||||
self._log_anomalies(metrics)
|
||||
return metrics
|
||||
|
||||
def _check_service_health(self) -> Dict[str, str]:
|
||||
"""Check critical application services"""
|
||||
return {
|
||||
'database': self._check_database(),
|
||||
'garmin_sync': self._check_garmin_sync(),
|
||||
'ai_service': self._check_ai_service()
|
||||
}
|
||||
|
||||
def _check_database(self) -> str:
|
||||
try:
|
||||
with get_db() as db:
|
||||
db.execute(text("SELECT 1"))
|
||||
return "ok"
|
||||
except Exception as e:
|
||||
logger.error(f"Database check failed: {str(e)}")
|
||||
return "down"
|
||||
|
||||
def _check_garmin_sync(self) -> str:
|
||||
try:
|
||||
last_sync = GarminSyncLog.get_latest()
|
||||
if last_sync and last_sync.status == SyncStatus.FAILED:
|
||||
return "warning"
|
||||
return "ok"
|
||||
except Exception as e:
|
||||
logger.error(f"Garmin sync check failed: {str(e)}")
|
||||
return "down"
|
||||
|
||||
def _check_ai_service(self) -> str:
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{settings.AI_SERVICE_URL}/ping",
|
||||
timeout=5,
|
||||
headers={"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}"}
|
||||
)
|
||||
return "ok" if response.ok else "down"
|
||||
except Exception as e:
|
||||
logger.error(f"AI service check failed: {str(e)}")
|
||||
return "down"
|
||||
|
||||
def _log_anomalies(self, metrics: Dict[str, Any]):
|
||||
alerts = []
|
||||
for metric, value in metrics.items():
|
||||
if metric in self.warning_thresholds and value > self.warning_thresholds[metric]:
|
||||
alerts.append(f"{metric} {value}%")
|
||||
|
||||
if alerts:
|
||||
logger.warning(f"System thresholds exceeded: {', '.join(alerts)}")
|
||||
@@ -1,6 +1,6 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.services.garmin import GarminService, GarminAPIError
|
||||
from app.services.garmin import GarminService, GarminAPIError, GarminAuthError
|
||||
from app.models.workout import Workout
|
||||
from app.models.garmin_sync_log import GarminSyncLog
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
Reference in New Issue
Block a user