diff --git a/app/api/v1/dashboard.py b/app/api/v1/dashboard.py index 4c66358..fc46da6 100644 --- a/app/api/v1/dashboard.py +++ b/app/api/v1/dashboard.py @@ -56,6 +56,41 @@ async def get_tts_statistics( ) from e +@router.get("/top-users") +async def get_top_users( + _current_user: Annotated[User, Depends(get_current_user)], + dashboard_service: Annotated[DashboardService, Depends(get_dashboard_service)], + metric_type: Annotated[ + str, + Query( + description="Metric type: sounds_played, credits_used, tracks_added, tts_added, playlists_created", + ), + ], + period: Annotated[ + str, + Query( + description="Time period (today, 1_day, 1_week, 1_month, 1_year, all_time)", + ), + ] = "all_time", + limit: Annotated[ + int, + Query(description="Number of top users to return", ge=1, le=100), + ] = 10, +) -> list[dict[str, Any]]: + """Get top users by metric for a specific period.""" + try: + return await dashboard_service.get_top_users( + metric_type=metric_type, + period=period, + limit=limit, + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to fetch top users: {e!s}", + ) from e + + @router.get("/top-sounds") async def get_top_sounds( _current_user: Annotated[User, Depends(get_current_user)], diff --git a/app/core/dependencies.py b/app/core/dependencies.py index 7e7d515..51fa3cc 100644 --- a/app/core/dependencies.py +++ b/app/core/dependencies.py @@ -9,6 +9,7 @@ from app.core.database import get_db from app.core.logging import get_logger from app.models.user import User from app.repositories.sound import SoundRepository +from app.repositories.user import UserRepository from app.services.auth import AuthService from app.services.dashboard import DashboardService from app.services.oauth import OAuthService @@ -193,4 +194,5 @@ async def get_dashboard_service( ) -> DashboardService: """Get the dashboard service.""" sound_repository = SoundRepository(session) - return DashboardService(sound_repository) + user_repository = UserRepository(session) + return DashboardService(sound_repository, user_repository) diff --git a/app/repositories/user.py b/app/repositories/user.py index c7292a6..afefdfb 100644 --- a/app/repositories/user.py +++ b/app/repositories/user.py @@ -1,5 +1,6 @@ """User repository.""" +from datetime import datetime from enum import Enum from typing import Any @@ -11,6 +12,11 @@ from sqlmodel.ext.asyncio.session import AsyncSession from app.core.logging import get_logger from app.models.plan import Plan from app.models.user import User +from app.models.sound_played import SoundPlayed +from app.models.credit_transaction import CreditTransaction +from app.models.playlist import Playlist +from app.models.sound import Sound +from app.models.tts import TTS from app.repositories.base import BaseRepository logger = get_logger(__name__) @@ -217,3 +223,110 @@ class UserRepository(BaseRepository[User]): except Exception: logger.exception("Failed to check if email exists: %s", email) raise + + async def get_top_users( + self, + metric_type: str, + date_filter: datetime | None = None, + limit: int = 10, + ) -> list[dict[str, Any]]: + """Get top users by different metrics.""" + try: + if metric_type == "sounds_played": + # Get users with most sounds played + query = ( + select( + User.id, + User.name, + func.count(SoundPlayed.id).label("count") + ) + .join(SoundPlayed, User.id == SoundPlayed.user_id) + .group_by(User.id, User.name) + ) + if date_filter: + query = query.where(SoundPlayed.created_at >= date_filter) + + elif metric_type == "credits_used": + # Get users with most credits used (negative transactions) + query = ( + select( + User.id, + User.name, + func.sum(func.abs(CreditTransaction.amount)).label("count") + ) + .join(CreditTransaction, User.id == CreditTransaction.user_id) + .where(CreditTransaction.amount < 0) + .group_by(User.id, User.name) + ) + if date_filter: + query = query.where(CreditTransaction.created_at >= date_filter) + + elif metric_type == "tracks_added": + # Get users with most EXT sounds added + query = ( + select( + User.id, + User.name, + func.count(Sound.id).label("count") + ) + .join(Sound, User.id == Sound.user_id) + .where(Sound.type == "EXT") + .group_by(User.id, User.name) + ) + if date_filter: + query = query.where(Sound.created_at >= date_filter) + + elif metric_type == "tts_added": + # Get users with most TTS sounds added + query = ( + select( + User.id, + User.name, + func.count(TTS.id).label("count") + ) + .join(TTS, User.id == TTS.user_id) + .group_by(User.id, User.name) + ) + if date_filter: + query = query.where(TTS.created_at >= date_filter) + + elif metric_type == "playlists_created": + # Get users with most playlists created + query = ( + select( + User.id, + User.name, + func.count(Playlist.id).label("count") + ) + .join(Playlist, User.id == Playlist.user_id) + .group_by(User.id, User.name) + ) + if date_filter: + query = query.where(Playlist.created_at >= date_filter) + + else: + msg = f"Unknown metric type: {metric_type}" + raise ValueError(msg) + + # Add ordering and limit + query = query.order_by(func.count().desc()).limit(limit) + + result = await self.session.exec(query) + rows = result.all() + + return [ + { + "id": row[0], + "name": row[1], + "count": int(row[2]), + } + for row in rows + ] + + except Exception: + logger.exception( + "Failed to get top users for metric=%s, date_filter=%s", + metric_type, + date_filter, + ) + raise diff --git a/app/services/dashboard.py b/app/services/dashboard.py index 62dfd54..b2845b7 100644 --- a/app/services/dashboard.py +++ b/app/services/dashboard.py @@ -5,6 +5,7 @@ from typing import Any from app.core.logging import get_logger from app.repositories.sound import SoundRepository +from app.repositories.user import UserRepository logger = get_logger(__name__) @@ -12,9 +13,10 @@ logger = get_logger(__name__) class DashboardService: """Service for dashboard statistics and analytics.""" - def __init__(self, sound_repository: SoundRepository) -> None: + def __init__(self, sound_repository: SoundRepository, user_repository: UserRepository) -> None: """Initialize the dashboard service.""" self.sound_repository = sound_repository + self.user_repository = user_repository async def get_soundboard_statistics(self) -> dict[str, Any]: """Get comprehensive soundboard statistics.""" @@ -100,6 +102,40 @@ class DashboardService: logger.exception("Failed to get TTS statistics") raise + async def get_top_users( + self, + metric_type: str, + period: str = "all_time", + limit: int = 10, + ) -> list[dict[str, Any]]: + """Get top users by different metrics for a specific period.""" + try: + # Calculate the date filter based on period + date_filter = self._get_date_filter(period) + + # Get top users from repository + top_users = await self.user_repository.get_top_users( + metric_type=metric_type, + date_filter=date_filter, + limit=limit, + ) + + return [ + { + "id": user["id"], + "name": user["name"], + "count": user["count"], + } + for user in top_users + ] + except Exception: + logger.exception( + "Failed to get top users for metric=%s, period=%s", + metric_type, + period, + ) + raise + def _get_date_filter(self, period: str) -> datetime | None: # noqa: PLR0911 """Calculate the date filter based on the period.""" now = datetime.now(UTC)