From f8700fd7ed19298383e882edd8c3dd5cb15c75ff Mon Sep 17 00:00:00 2001 From: Fusion Date: Tue, 19 May 2026 11:38:25 +0200 Subject: [PATCH] fix: dashboard endpoint and SSH key Fernet key generation - Create missing /dashboard/summary endpoint that frontend expects - Fix SSH key Fernet key generation to use proper base64 encoding (was using raw session secret slice which failed validation) --- apps/api/src/api/dashboard.py | 52 +++++++++++++++++++++++++++++++++++ apps/api/src/api/ssh_keys.py | 11 ++++++-- apps/api/src/main.py | 2 ++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/api/dashboard.py diff --git a/apps/api/src/api/dashboard.py b/apps/api/src/api/dashboard.py new file mode 100644 index 0000000..10d89f4 --- /dev/null +++ b/apps/api/src/api/dashboard.py @@ -0,0 +1,52 @@ +import uuid + +from fastapi import APIRouter, Depends +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession + +from src.auth.dependencies import get_current_user_id, get_db_session +from src.models.git_repository import GitRepository +from src.models.project import Project +from src.models.ssh_key import SSHKey + +router = APIRouter(prefix="/dashboard", tags=["dashboard"]) + + +@router.get("/summary") +async def get_dashboard_summary( + user_id: uuid.UUID = Depends(get_current_user_id), + session: AsyncSession = Depends(get_db_session), +) -> dict: + # Count user's projects + projects_result = await session.execute( + select(func.count()).select_from(Project).where(Project.owner_id == user_id) + ) + projects_count = projects_result.scalar() or 0 + + # Count user's repositories + repos_result = await session.execute( + select(func.count()).select_from(GitRepository).where(GitRepository.owner_id == user_id) + ) + repos_count = repos_result.scalar() or 0 + + # Count user's SSH keys + ssh_keys_result = await session.execute( + select(func.count()).select_from(SSHKey).where(SSHKey.user_id == user_id) + ) + ssh_keys_count = ssh_keys_result.scalar() or 0 + + # Get recent activity (latest 5 projects) + recent_projects = await session.execute( + select(Project) + .where(Project.owner_id == user_id) + .order_by(Project.created_at.desc()) + .limit(5) + ) + recent_activity = [f"Created project: {p.name}" for p in recent_projects.scalars().all()] + + return { + "projects": projects_count, + "repositories": repos_count, + "sshKeys": ssh_keys_count, + "recentActivity": recent_activity, + } diff --git a/apps/api/src/api/ssh_keys.py b/apps/api/src/api/ssh_keys.py index ff91adb..1ea68b6 100644 --- a/apps/api/src/api/ssh_keys.py +++ b/apps/api/src/api/ssh_keys.py @@ -25,9 +25,16 @@ async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User: def _get_fernet() -> Fernet: + """Generate a valid Fernet key from the session secret.""" + import base64 + import hashlib + settings = Settings() - key = settings.session_secret[:32].ljust(32, "=") - return Fernet(key.encode()) + # Derive a 32-byte key from the session secret using SHA256 + key_bytes = hashlib.sha256(settings.session_secret.encode()).digest() + # Base64 encode it for Fernet (must be 32 url-safe base64-encoded bytes) + key = base64.urlsafe_b64encode(key_bytes) + return Fernet(key) def generate_ssh_key_pair() -> tuple[str, str]: diff --git a/apps/api/src/main.py b/apps/api/src/main.py index 1bd8e8a..5016526 100644 --- a/apps/api/src/main.py +++ b/apps/api/src/main.py @@ -7,6 +7,7 @@ from fastapi.staticfiles import StaticFiles from sqlalchemy import select, text from src.api.auth import router as auth_router +from src.api.dashboard import router as dashboard_router from src.api.git_repositories import router as git_repositories_router from src.api.projects import router as projects_router from src.api.ssh_keys import router as ssh_keys_router @@ -156,6 +157,7 @@ async def health_check(): return {"status": "unhealthy", "database": "disconnected", "error": str(exc)} app.include_router(auth_router) +app.include_router(dashboard_router) app.include_router(projects_router) app.include_router(users_router) app.include_router(ssh_keys_router)