""" Dashboard Controller Provides API endpoints for dashboard management including quick links. """ from fastapi import APIRouter, HTTPException, Depends, Query from typing import Dict, Optional from sqlalchemy.ext.asyncio import AsyncSession from src.shared.base import BaseController from src.shared.database import get_async_session from src.shared.logging import get_logger from src.domains.auth.oidc import get_current_user, get_optional_user from src.domains.dashboard.service import get_dashboard_service from src.domains.dashboard.schemas import ( QuickLinkCreate, QuickLinkUpdate, QuickLinkResponse, QuickLinkListResponse, QuickLinkReorderRequest, QuickLinkReorderResponse, DashboardWidgetCreate, DashboardWidgetUpdate, DashboardWidgetResponse, DashboardWidgetListResponse, ) logger = get_logger(__name__) class DashboardController(BaseController): """ Controller for dashboard operations Provides endpoints for: - Quick links CRUD - Quick links reordering - Dashboard widgets management """ def __init__(self): super().__init__(prefix="/dashboard", tags=["Dashboard"]) def create_router(self) -> APIRouter: """Create and configure the router""" router = APIRouter(prefix=self.prefix, tags=self.tags) service = get_dashboard_service() # ===================================================================== # Quick Links # ===================================================================== @router.get( "/quick-links", response_model=QuickLinkListResponse, summary="List quick links" ) async def list_quick_links( category: Optional[str] = Query(None, description="Filter by category"), include_global: bool = Query(True, description="Include global links"), visible_only: bool = Query(True, description="Only visible links"), session: AsyncSession = Depends(get_async_session), user: Optional[Dict] = Depends(get_optional_user), ): """ List quick links for the current user Returns user-specific links plus global links (if include_global=True). """ user_id = user.get("sub") if user else None links = await service.get_quick_links( session=session, user_id=user_id, include_global=include_global, category=category, visible_only=visible_only, ) return QuickLinkListResponse( links=[QuickLinkResponse.model_validate(link, from_attributes=True) for link in links], total=len(links) ) @router.get( "/quick-links/{link_id}", response_model=QuickLinkResponse, summary="Get a quick link" ) async def get_quick_link( link_id: int, session: AsyncSession = Depends(get_async_session), user: Optional[Dict] = Depends(get_optional_user), ): """Get a specific quick link by ID""" user_id = user.get("sub") if user else None link = await service.get_quick_link(session, link_id, user_id) if not link: raise HTTPException(status_code=404, detail="Quick link not found") return QuickLinkResponse.model_validate(link, from_attributes=True) @router.post( "/quick-links", response_model=QuickLinkResponse, status_code=201, summary="Create a quick link" ) async def create_quick_link( data: QuickLinkCreate, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """ Create a new quick link for the current user Links are user-specific by default. Admins can create global links by setting user_id to null. """ user_id = user.get("sub") link = await service.create_quick_link(session, data, user_id) logger.info(f"Quick link created: {link.title} by user {user.get('preferred_username')}") return QuickLinkResponse.model_validate(link, from_attributes=True) @router.put( "/quick-links/{link_id}", response_model=QuickLinkResponse, summary="Update a quick link" ) async def update_quick_link( link_id: int, data: QuickLinkUpdate, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """Update an existing quick link""" user_id = user.get("sub") link = await service.update_quick_link(session, link_id, data, user_id) if not link: raise HTTPException(status_code=404, detail="Quick link not found or not authorized") return QuickLinkResponse.model_validate(link, from_attributes=True) @router.delete( "/quick-links/{link_id}", status_code=204, summary="Delete a quick link" ) async def delete_quick_link( link_id: int, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """Delete a quick link""" user_id = user.get("sub") success = await service.delete_quick_link(session, link_id, user_id) if not success: raise HTTPException(status_code=404, detail="Quick link not found or not authorized") return None @router.post( "/quick-links/reorder", response_model=QuickLinkReorderResponse, summary="Reorder quick links" ) async def reorder_quick_links( data: QuickLinkReorderRequest, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """ Reorder quick links by providing link IDs in desired order The position of each link will be set to its index in the provided list. """ user_id = user.get("sub") reordered = await service.reorder_quick_links(session, data.link_ids, user_id) return QuickLinkReorderResponse( success=True, message=f"Reordered {reordered} links", reordered_count=reordered ) # ===================================================================== # Dashboard Widgets # ===================================================================== @router.get( "/widgets", response_model=DashboardWidgetListResponse, summary="List dashboard widgets" ) async def list_widgets( include_defaults: bool = Query(True, description="Include default widgets"), visible_only: bool = Query(True, description="Only visible widgets"), session: AsyncSession = Depends(get_async_session), user: Optional[Dict] = Depends(get_optional_user), ): """List dashboard widgets for the current user""" user_id = user.get("sub") if user else None widgets = await service.get_widgets( session=session, user_id=user_id, include_defaults=include_defaults, visible_only=visible_only, ) return DashboardWidgetListResponse( widgets=[DashboardWidgetResponse.model_validate(w, from_attributes=True) for w in widgets], total=len(widgets) ) @router.get( "/widgets/{widget_id}", response_model=DashboardWidgetResponse, summary="Get a dashboard widget" ) async def get_widget( widget_id: int, session: AsyncSession = Depends(get_async_session), user: Optional[Dict] = Depends(get_optional_user), ): """Get a specific dashboard widget by ID""" user_id = user.get("sub") if user else None widget = await service.get_widget(session, widget_id, user_id) if not widget: raise HTTPException(status_code=404, detail="Widget not found") return DashboardWidgetResponse.model_validate(widget, from_attributes=True) @router.post( "/widgets", response_model=DashboardWidgetResponse, status_code=201, summary="Create a dashboard widget" ) async def create_widget( data: DashboardWidgetCreate, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """Create a new dashboard widget""" user_id = user.get("sub") widget = await service.create_widget(session, data, user_id) logger.info(f"Widget created: {widget.widget_type} by user {user.get('preferred_username')}") return DashboardWidgetResponse.model_validate(widget, from_attributes=True) @router.put( "/widgets/{widget_id}", response_model=DashboardWidgetResponse, summary="Update a dashboard widget" ) async def update_widget( widget_id: int, data: DashboardWidgetUpdate, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """Update an existing dashboard widget""" user_id = user.get("sub") widget = await service.update_widget(session, widget_id, data, user_id) if not widget: raise HTTPException(status_code=404, detail="Widget not found or not authorized") return DashboardWidgetResponse.model_validate(widget, from_attributes=True) @router.delete( "/widgets/{widget_id}", status_code=204, summary="Delete a dashboard widget" ) async def delete_widget( widget_id: int, session: AsyncSession = Depends(get_async_session), user: Dict = Depends(get_current_user), ): """Delete a dashboard widget""" user_id = user.get("sub") success = await service.delete_widget(session, widget_id, user_id) if not success: raise HTTPException(status_code=404, detail="Widget not found or not authorized") return None return router # Create controller instance dashboard_controller = DashboardController()