Compare commits
2 Commits
7b59a8216a
...
95e166eefb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95e166eefb | ||
|
|
d9697c2dd7 |
@@ -41,6 +41,56 @@ async def get_track_statistics(
|
|||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tts-statistics")
|
||||||
|
async def get_tts_statistics(
|
||||||
|
_current_user: Annotated[User, Depends(get_current_user)],
|
||||||
|
dashboard_service: Annotated[DashboardService, Depends(get_dashboard_service)],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Get TTS statistics."""
|
||||||
|
try:
|
||||||
|
return await dashboard_service.get_tts_statistics()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to fetch TTS statistics: {e!s}",
|
||||||
|
) 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)],
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -201,8 +201,8 @@ class SoundRepository(BaseRepository[Sound]):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def get_soundboard_statistics(self) -> dict[str, int | float]:
|
async def get_soundboard_statistics(self, sound_type: str = "SDB") -> dict[str, int | float]:
|
||||||
"""Get statistics for SDB type sounds."""
|
"""Get statistics for sounds of a specific type."""
|
||||||
try:
|
try:
|
||||||
statement = select(
|
statement = select(
|
||||||
func.count(Sound.id).label("count"),
|
func.count(Sound.id).label("count"),
|
||||||
@@ -211,7 +211,7 @@ class SoundRepository(BaseRepository[Sound]):
|
|||||||
func.sum(
|
func.sum(
|
||||||
Sound.size + func.coalesce(Sound.normalized_size, 0),
|
Sound.size + func.coalesce(Sound.normalized_size, 0),
|
||||||
).label("total_size"),
|
).label("total_size"),
|
||||||
).where(Sound.type == "SDB")
|
).where(Sound.type == sound_type)
|
||||||
|
|
||||||
result = await self.session.exec(statement)
|
result = await self.session.exec(statement)
|
||||||
row = result.first()
|
row = result.first()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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."""
|
||||||
@@ -85,6 +87,55 @@ class DashboardService:
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def get_tts_statistics(self) -> dict[str, Any]:
|
||||||
|
"""Get comprehensive TTS statistics."""
|
||||||
|
try:
|
||||||
|
stats = await self.sound_repository.get_soundboard_statistics("TTS")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"sound_count": stats["count"],
|
||||||
|
"total_play_count": stats["total_plays"],
|
||||||
|
"total_duration": stats["total_duration"],
|
||||||
|
"total_size": stats["total_size"],
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
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
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user