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
@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)],

View File

@@ -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)

View File

@@ -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

View File

@@ -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)