feat: Add endpoint and service method to retrieve top users by various metrics
Some checks failed
Backend CI / lint (push) Failing after 9s
Backend CI / test (push) Failing after 1m36s

This commit is contained in:
JSC
2025-09-27 21:52:00 +02:00
parent d9697c2dd7
commit 95e166eefb
4 changed files with 188 additions and 2 deletions

View File

@@ -56,6 +56,41 @@ async def get_tts_statistics(
) from e ) 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") @router.get("/top-sounds")
async def get_top_sounds( async def get_top_sounds(
_current_user: Annotated[User, Depends(get_current_user)], _current_user: Annotated[User, Depends(get_current_user)],

View File

@@ -9,6 +9,7 @@ from app.core.database import get_db
from app.core.logging import get_logger from app.core.logging import get_logger
from app.models.user import User from app.models.user import User
from app.repositories.sound import SoundRepository from app.repositories.sound import SoundRepository
from app.repositories.user import UserRepository
from app.services.auth import AuthService from app.services.auth import AuthService
from app.services.dashboard import DashboardService from app.services.dashboard import DashboardService
from app.services.oauth import OAuthService from app.services.oauth import OAuthService
@@ -193,4 +194,5 @@ async def get_dashboard_service(
) -> DashboardService: ) -> DashboardService:
"""Get the dashboard service.""" """Get the dashboard service."""
sound_repository = SoundRepository(session) sound_repository = SoundRepository(session)
return DashboardService(sound_repository) user_repository = UserRepository(session)
return DashboardService(sound_repository, user_repository)

View File

@@ -1,5 +1,6 @@
"""User repository.""" """User repository."""
from datetime import datetime
from enum import Enum from enum import Enum
from typing import Any from typing import Any
@@ -11,6 +12,11 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.logging import get_logger from app.core.logging import get_logger
from app.models.plan import Plan from app.models.plan import Plan
from app.models.user import User 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 from app.repositories.base import BaseRepository
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -217,3 +223,110 @@ class UserRepository(BaseRepository[User]):
except Exception: except Exception:
logger.exception("Failed to check if email exists: %s", email) logger.exception("Failed to check if email exists: %s", email)
raise 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

View File

@@ -5,6 +5,7 @@ from typing import Any
from app.core.logging import get_logger from app.core.logging import get_logger
from app.repositories.sound import SoundRepository from app.repositories.sound import SoundRepository
from app.repositories.user import UserRepository
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -12,9 +13,10 @@ logger = get_logger(__name__)
class DashboardService: class DashboardService:
"""Service for dashboard statistics and analytics.""" """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.""" """Initialize the dashboard service."""
self.sound_repository = sound_repository self.sound_repository = sound_repository
self.user_repository = user_repository
async def get_soundboard_statistics(self) -> dict[str, Any]: async def get_soundboard_statistics(self) -> dict[str, Any]:
"""Get comprehensive soundboard statistics.""" """Get comprehensive soundboard statistics."""
@@ -100,6 +102,40 @@ class DashboardService:
logger.exception("Failed to get TTS statistics") logger.exception("Failed to get TTS statistics")
raise 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 def _get_date_filter(self, period: str) -> datetime | None: # noqa: PLR0911
"""Calculate the date filter based on the period.""" """Calculate the date filter based on the period."""
now = datetime.now(UTC) now = datetime.now(UTC)