- Completed Action 2.1.1.1: Designed dashboard endpoint contract - Created DASHBOARD_ENDPOINT_CONTRACT.md with complete specification - Defined GET /api/v1/dashboard endpoint consolidating 4+ API calls - Response structure: stats, recent_activity, library_preview - Query parameters: activity_limit, library_limit, stats_period - Documented data sources, error handling, performance considerations - Migration strategy outlined for phased rollout - Ready for backend implementation (Action 2.1.1.2)
6.3 KiB
Dashboard Aggregation Endpoint Contract
Date: 2025-01-27
Action: 2.1.1.1 - Design dashboard endpoint contract
Status: ✅ Complete
Overview
This document defines the contract for a new aggregated dashboard endpoint that consolidates multiple API calls into a single request, reducing over-fetching and improving performance.
Current Implementation
The dashboard currently makes 4+ separate API calls:
- GET /audit/stats - Dashboard statistics
- GET /audit/activity - Recent activity logs
- Social feed API - Recent posts/feed items
- GET /tracks (with limit: 5) - Library preview
Problems:
- Multiple round trips increase latency
- Over-fetching of data
- Race conditions possible
- No atomic data consistency
Proposed Endpoint
Endpoint Specification
Method: GET
Path: /api/v1/dashboard
Authentication: Required (Bearer token)
Response Format: Wrapped format { success: true, data: DashboardResponse }
Request Parameters
Query Parameters (all optional):
activity_limit(integer, default: 10) - Number of recent activity items to returnlibrary_limit(integer, default: 5) - Number of library items to return in previewstats_period(string, default: "30d") - Time period for statistics ("7d", "30d", "90d", "all")
Response Structure
interface DashboardResponse {
stats: DashboardStats;
recent_activity: RecentActivity[];
library_preview: LibraryPreview;
}
interface DashboardStats {
tracks_played: number;
messages_sent: number;
favorites: number;
active_friends: number;
tracks_played_change?: string; // Percentage change (e.g., "+12%", "-5%")
messages_sent_change?: string;
favorites_change?: string;
active_friends_change?: string;
period: string; // "7d", "30d", "90d", "all"
}
interface RecentActivity {
id: string; // UUID
type: 'track_upload' | 'message_received' | 'favorite_added' |
'playlist_created' | 'comment_added' | 'post';
title: string; // Human-readable title
description?: string; // Optional description/details
timestamp: string; // ISO8601 timestamp
icon?: string; // Optional icon URL or identifier
metadata?: Record<string, any>; // Additional context (track_id, user_id, etc.)
}
interface LibraryPreview {
items: TrackPreview[];
total_count: number; // Total tracks in user's library
has_more: boolean; // Whether there are more items beyond limit
}
interface TrackPreview {
id: string; // UUID
title: string;
artist: string;
duration: number; // Seconds
cover_art_path?: string;
play_count: number;
like_count: number;
created_at: string; // ISO8601 timestamp
}
Response Example
{
"success": true,
"data": {
"stats": {
"tracks_played": 1234,
"messages_sent": 567,
"favorites": 89,
"active_friends": 45,
"tracks_played_change": "+12%",
"messages_sent_change": "+8%",
"favorites_change": "+23%",
"active_friends_change": "+5%",
"period": "30d"
},
"recent_activity": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "track_upload",
"title": "Nouvelle piste ajoutée",
"description": "My New Track.mp3",
"timestamp": "2025-01-27T10:30:00Z",
"metadata": {
"track_id": "660e8400-e29b-41d4-a716-446655440001",
"file_name": "My New Track.mp3"
}
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"type": "message_received",
"title": "Message reçu de @username",
"timestamp": "2025-01-27T09:15:00Z",
"metadata": {
"conversation_id": "770e8400-e29b-41d4-a716-446655440003",
"from_user_id": "880e8400-e29b-41d4-a716-446655440004"
}
}
],
"library_preview": {
"items": [
{
"id": "990e8400-e29b-41d4-a716-446655440005",
"title": "My Favorite Track",
"artist": "Artist Name",
"duration": 240,
"cover_art_path": "/covers/track-123.jpg",
"play_count": 42,
"like_count": 7,
"created_at": "2025-01-20T14:30:00Z"
}
],
"total_count": 156,
"has_more": true
}
}
}
Data Sources
Stats Calculation
tracks_played: Sum of play actions from audit logs + track play counts
messages_sent: Count of message actions from audit logs
favorites: Count of favorite/like actions from audit logs
active_friends: Count of unique users interacted with in period
Change percentages: Compare current period to previous period (e.g., last 30d vs previous 30d)
Recent Activity
Combines:
- Audit logs (
/audit/activity) - User actions (uploads, plays, favorites, etc.) - Social feed - Recent posts from followed users
- Chat messages - Recent messages received
- Comments - Comments on user's tracks/playlists
Sorted by timestamp descending, limited by activity_limit.
Library Preview
Returns user's most recently added or most played tracks, limited by library_limit.
Error Handling
401 Unauthorized: User not authenticated
500 Internal Server Error: Backend error during aggregation
Error responses follow standard format:
{
"success": false,
"error": {
"code": 500,
"message": "Failed to fetch dashboard data",
"timestamp": "2025-01-27T10:30:00Z"
}
}
Performance Considerations
- Caching: Response can be cached for 30-60 seconds (user-specific)
- Database queries: Use efficient joins and indexes
- Parallel fetching: Fetch stats, activity, and library in parallel
- Timeouts: Aggregate endpoint should complete within 2 seconds
Migration Strategy
- Phase 1: Implement backend endpoint (Action 2.1.1.2)
- Phase 2: Update frontend to use new endpoint (Action 2.1.1.3)
- Phase 3: Remove old API calls (Actions 2.1.1.4-2.1.1.6)
- Phase 4: Add caching (Action 2.1.1.7)
Validation
✅ Contract documented
✅ Response structure defined
✅ Data sources identified
✅ Error handling specified
✅ Performance considerations noted
⏭️ Next: Action 2.1.1.2 - Implement backend dashboard handler