""" Dashboard Domain Service Business logic for dashboard operations. """ from typing import Optional, List from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession from src.shared.logging import get_logger from src.domains.dashboard.models import QuickLink, DashboardWidget from src.domains.dashboard.schemas import ( QuickLinkCreate, QuickLinkUpdate, QuickLinkResponse, DashboardWidgetCreate, DashboardWidgetUpdate, DashboardWidgetResponse, ) logger = get_logger(__name__) class DashboardService: """Service for dashboard operations""" # ========================================================================= # Quick Links # ========================================================================= async def get_quick_links( self, session: AsyncSession, user_id: Optional[str] = None, include_global: bool = True, category: Optional[str] = None, visible_only: bool = True, ) -> List[QuickLink]: """ Get quick links for a user Args: session: Database session user_id: User ID to filter by (None for global only) include_global: Whether to include global links (user_id=None) category: Optional category filter visible_only: Only return visible links """ conditions = [] if user_id: if include_global: from sqlalchemy import or_ conditions.append(or_(QuickLink.user_id == user_id, QuickLink.user_id.is_(None))) else: conditions.append(QuickLink.user_id == user_id) else: conditions.append(QuickLink.user_id.is_(None)) if category: conditions.append(QuickLink.category == category) if visible_only: conditions.append(QuickLink.is_visible == True) stmt = select(QuickLink).where(*conditions).order_by(QuickLink.position, QuickLink.id) result = await session.execute(stmt) return list(result.scalars().all()) async def get_quick_link( self, session: AsyncSession, link_id: int, user_id: Optional[str] = None, ) -> Optional[QuickLink]: """Get a specific quick link by ID""" conditions = [QuickLink.id == link_id] if user_id: from sqlalchemy import or_ conditions.append(or_(QuickLink.user_id == user_id, QuickLink.user_id.is_(None))) stmt = select(QuickLink).where(*conditions) result = await session.execute(stmt) return result.scalar_one_or_none() async def create_quick_link( self, session: AsyncSession, data: QuickLinkCreate, user_id: Optional[str] = None, ) -> QuickLink: """Create a new quick link""" # Get max position for this user stmt = select(QuickLink.position).where( QuickLink.user_id == user_id if user_id else QuickLink.user_id.is_(None) ).order_by(QuickLink.position.desc()).limit(1) result = await session.execute(stmt) max_pos = result.scalar_one_or_none() or -1 link = QuickLink( title=data.title, url=data.url, icon=data.icon, description=data.description, category=data.category, position=data.position if data.position > 0 else max_pos + 1, is_visible=data.is_visible, color=data.color, background_color=data.background_color, user_id=user_id, ) session.add(link) await session.commit() await session.refresh(link) logger.info(f"Created quick link: {link.title} (id={link.id}, user={user_id})") return link async def update_quick_link( self, session: AsyncSession, link_id: int, data: QuickLinkUpdate, user_id: Optional[str] = None, ) -> Optional[QuickLink]: """Update a quick link""" link = await self.get_quick_link(session, link_id, user_id) if not link: return None # Only allow updating own links or global links for admins if link.user_id and link.user_id != user_id: return None update_data = data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(link, field, value) await session.commit() await session.refresh(link) logger.info(f"Updated quick link: {link.title} (id={link.id})") return link async def delete_quick_link( self, session: AsyncSession, link_id: int, user_id: Optional[str] = None, ) -> bool: """Delete a quick link""" link = await self.get_quick_link(session, link_id, user_id) if not link: return False # Only allow deleting own links if link.user_id and link.user_id != user_id: return False await session.delete(link) await session.commit() logger.info(f"Deleted quick link: id={link_id}") return True async def reorder_quick_links( self, session: AsyncSession, link_ids: List[int], user_id: Optional[str] = None, ) -> int: """Reorder quick links by updating positions""" reordered = 0 for position, link_id in enumerate(link_ids): conditions = [QuickLink.id == link_id] if user_id: from sqlalchemy import or_ conditions.append(or_(QuickLink.user_id == user_id, QuickLink.user_id.is_(None))) stmt = update(QuickLink).where(*conditions).values(position=position) result = await session.execute(stmt) reordered += result.rowcount await session.commit() logger.info(f"Reordered {reordered} quick links for user {user_id}") return reordered # ========================================================================= # Dashboard Widgets # ========================================================================= async def get_widgets( self, session: AsyncSession, user_id: Optional[str] = None, include_defaults: bool = True, visible_only: bool = True, ) -> List[DashboardWidget]: """Get dashboard widgets for a user""" conditions = [] if user_id: if include_defaults: from sqlalchemy import or_ conditions.append(or_(DashboardWidget.user_id == user_id, DashboardWidget.user_id.is_(None))) else: conditions.append(DashboardWidget.user_id == user_id) else: conditions.append(DashboardWidget.user_id.is_(None)) if visible_only: conditions.append(DashboardWidget.is_visible == True) stmt = select(DashboardWidget).where(*conditions).order_by( DashboardWidget.position_y, DashboardWidget.position_x ) result = await session.execute(stmt) return list(result.scalars().all()) async def get_widget( self, session: AsyncSession, widget_id: int, user_id: Optional[str] = None, ) -> Optional[DashboardWidget]: """Get a specific widget by ID""" conditions = [DashboardWidget.id == widget_id] if user_id: from sqlalchemy import or_ conditions.append(or_(DashboardWidget.user_id == user_id, DashboardWidget.user_id.is_(None))) stmt = select(DashboardWidget).where(*conditions) result = await session.execute(stmt) return result.scalar_one_or_none() async def create_widget( self, session: AsyncSession, data: DashboardWidgetCreate, user_id: Optional[str] = None, ) -> DashboardWidget: """Create a new dashboard widget""" widget = DashboardWidget( widget_type=data.widget_type, position_x=data.position_x, position_y=data.position_y, width=data.width, height=data.height, config=data.config, is_visible=data.is_visible, user_id=user_id, ) session.add(widget) await session.commit() await session.refresh(widget) logger.info(f"Created widget: {widget.widget_type} (id={widget.id}, user={user_id})") return widget async def update_widget( self, session: AsyncSession, widget_id: int, data: DashboardWidgetUpdate, user_id: Optional[str] = None, ) -> Optional[DashboardWidget]: """Update a dashboard widget""" widget = await self.get_widget(session, widget_id, user_id) if not widget: return None if widget.user_id and widget.user_id != user_id: return None update_data = data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(widget, field, value) await session.commit() await session.refresh(widget) logger.info(f"Updated widget: id={widget.id}") return widget async def delete_widget( self, session: AsyncSession, widget_id: int, user_id: Optional[str] = None, ) -> bool: """Delete a dashboard widget""" widget = await self.get_widget(session, widget_id, user_id) if not widget: return False if widget.user_id and widget.user_id != user_id: return False await session.delete(widget) await session.commit() logger.info(f"Deleted widget: id={widget_id}") return True # Singleton instance _dashboard_service: Optional[DashboardService] = None def get_dashboard_service() -> DashboardService: """Get singleton dashboard service instance""" global _dashboard_service if _dashboard_service is None: _dashboard_service = DashboardService() return _dashboard_service