veza/apps/web/src/mocks/handlers.ts
2026-02-07 20:36:48 +01:00

1681 lines
44 KiB
TypeScript

import { http, HttpResponse } from 'msw';
/**
* FE-API-019: MSW Mock Handlers
* Mock request handlers for development and testing
*/
export const handlers = [
// External image services
http.get('https://picsum.photos/*', async () => {
return new HttpResponse(
'<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="gray"/></svg>',
{ headers: { 'Content-Type': 'image/svg+xml' } }
);
}),
http.get('https://i.pravatar.cc/*', async () => {
return new HttpResponse(
'<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="gray"/></svg>',
{ headers: { 'Content-Type': 'image/svg+xml' } }
);
}),
http.get('https://api.dicebear.com/*', async () => {
return new HttpResponse(
'<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="gray"/></svg>',
{ headers: { 'Content-Type': 'image/svg+xml' } }
);
}),
http.get('https://www.transparenttextures.com/*', async () => {
return new HttpResponse(
'<svg width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="gray"/></svg>',
{ headers: { 'Content-Type': 'image/svg+xml' } }
);
}),
// Auth endpoints
http.get('*/api/v1/csrf-token', () => {
return HttpResponse.json({
success: true,
data: { csrf_token: 'mock-csrf-token' }
});
}),
http.post('*/api/v1/auth/login', async () => {
return HttpResponse.json({
user: {
id: 1,
username: 'StorybookUser',
email: 'user@example.com',
created_at: '2024-01-01T00:00:00Z',
avatar_url: 'https://i.pravatar.cc/150?u=1',
},
token: {
access_token: 'mock_access_token_generic',
refresh_token: 'mock_refresh_token_generic',
expires_in: 3600,
},
});
}),
http.post('*/api/v1/auth/refresh', async () => {
return HttpResponse.json({
success: true,
data: {
access_token: 'new_access_token_123',
refresh_token: 'new_refresh_token_123',
}
});
}),
// auth/me: backend returns { success, data: { user } }; apiClient unwraps to data, so authService expects response.data.user
http.get('*/api/v1/auth/me', () => {
return HttpResponse.json({
success: true,
data: {
user: {
id: 1,
username: 'StorybookUser',
email: 'user@example.com',
first_name: 'Story',
last_name: 'User',
avatar_url: 'https://i.pravatar.cc/150?u=1',
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
role: 'user',
is_verified: true,
},
},
});
}),
http.post('*/api/v1/auth/register', async () => {
return HttpResponse.json({
success: true,
data: {
access_token: 'mock_access_token_register',
refresh_token: 'mock_refresh_token_register',
user: {
id: 1,
username: 'StorybookUser',
email: 'user@example.com',
created_at: '2024-01-01T00:00:00Z',
}
}
});
}),
http.post('*/api/v1/auth/logout', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/auth/password/reset-request', () => {
return HttpResponse.json({ success: true, message: 'Reset email sent' });
}),
http.post('*/api/v1/auth/password/reset', () => {
return HttpResponse.json({ success: true, message: 'Password reset successful' });
}),
http.get('*/api/v1/auth/check-username', () => {
return HttpResponse.json({ success: true, available: true });
}),
http.post('*/api/v1/auth/verify-email', () => {
return HttpResponse.json({ success: true, message: 'Email verified' });
}),
http.post('*/api/v1/auth/resend-verification', () => {
return HttpResponse.json({ success: true, message: 'Verification email resent' });
}),
http.get('*/api/v1/auth/2fa/status', () => {
return HttpResponse.json({
success: true,
data: { enabled: false, method: null }
});
}),
http.post('*/api/v1/auth/2fa/setup', () => {
return HttpResponse.json({
success: true,
data: {
secret: 'MOCKSECRET123',
qr_code: 'data:image/png;base64,mockqrcode',
backup_codes: ['CODE1', 'CODE2']
}
});
}),
http.post('*/api/v1/auth/2fa/verify', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/auth/2fa/disable', () => {
return HttpResponse.json({ success: true });
}),
// Logs endpoint
http.get('*/api/v1/audit/logs', () => {
return HttpResponse.json({
success: true,
data: {
logs: [
{
id: 'log-1',
action: 'user.login',
user_id: 'user-1',
resource: 'auth',
details: { ip: '127.0.0.1' },
timestamp: '2024-01-01T00:00:00Z',
user: { id: 'user-1', username: 'TestUser' }
},
{
id: 'log-2',
action: 'track.create',
user_id: 'user-1',
resource: 'track',
details: { track_id: 'track-1' },
timestamp: '2024-01-02T00:00:00Z',
user: { id: 'user-1', username: 'TestUser' }
}
],
total: 2,
page: 1,
limit: 20
}
});
}),
http.get('*/api/v1/audit/stats', () => {
return HttpResponse.json({
success: true,
data: {
total_users: 12500,
total_revenue: 45000,
active_sessions: 1200,
pending_reports: 8,
trends: { users: 5, revenue: 10, sessions: -2, reports: 0 }
}
});
}),
http.post('*/api/v1/logs/frontend', () => {
return HttpResponse.json({ success: true });
}),
// Dashboard: aggregated endpoint used by dashboardService.getDashboardData()
http.get('*/api/v1/dashboard', () => {
return HttpResponse.json({
success: true,
data: {
stats: {
tracks_played: 42,
messages_sent: 12,
favorites: 8,
active_friends: 3,
period: '30d',
},
recent_activity: [
{
id: 'act-1',
type: 'track_upload',
title: 'Track uploaded',
description: 'New track added',
timestamp: '2024-01-15T10:00:00Z',
},
],
library_preview: {
items: [],
total_count: 0,
has_more: false,
},
},
});
}),
// Sessions stats: used by sessionsApi.getSessionStats()
http.get('*/api/v1/sessions/stats', () => {
return HttpResponse.json({
success: true,
data: {
user_id: 'user-1',
stats: { total_active: 1, unique_users: 1 },
},
});
}),
// Roles: used by admin/roles views
http.get('*/api/v1/roles', () => {
return HttpResponse.json({
success: true,
data: {
items: [
{ id: '1', name: 'admin', description: 'Administrator' },
{ id: '2', name: 'user', description: 'User' },
],
pagination: { total: 2, page: 1, limit: 20, total_pages: 1 },
},
});
}),
http.get('*/api/v1/roles/:id', () => {
return HttpResponse.json({
success: true,
data: { id: '1', name: 'admin', description: 'Administrator' },
});
}),
http.get('*/api/v1/users', () => {
return HttpResponse.json({
success: true,
data: {
items: [
{
id: 'user-1',
username: 'StorybookUser',
email: 'user@example.com',
role: 'admin',
avatar_url: 'https://i.pravatar.cc/150?u=1',
created_at: '2024-01-01T00:00:00Z',
status: 'active'
},
{
id: 'user-2',
username: 'AnotherUser',
email: 'user2@example.com',
role: 'user',
avatar_url: 'https://i.pravatar.cc/150?u=2',
created_at: '2024-01-02T00:00:00Z',
status: 'banned'
}
],
pagination: {
total: 2,
page: 1,
limit: 20,
total_pages: 1
}
}
});
}),
http.get('*/api/v1/monitoring/metrics', () => {
return HttpResponse.json({
success: true,
data: {
cpu: { usage: 15, history: [10, 12, 15, 14, 15] },
memory: { usage: 45, total: 16000, history: [40, 42, 45, 44, 45] },
active_connections: 120,
requests_per_second: 50
}
});
}),
// Social endpoints
http.get('*/api/v1/social/feed', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'feed-1',
content: 'Just dropped a new track! Check it out 🎵',
created_at: '2024-01-01T12:00:00Z',
user_id: 'user-1'
},
{
id: 'feed-2',
content: 'Working on some sick beats tonight',
created_at: '2024-01-01T10:00:00Z',
user_id: 'user-2'
}
]
});
}),
http.post('*/api/v1/social/posts', async ({ request }) => {
const body = await request.json() as any;
return HttpResponse.json({
success: true,
data: {
id: `post-${Date.now()}`,
content: body.content,
created_at: new Date().toISOString(),
user_id: 'user-1',
like_count: 0,
comment_count: 0
}
});
}),
http.get('*/api/v1/social/posts/user/:userId', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'post-1',
content: 'My latest production',
created_at: '2024-01-01T12:00:00Z',
user_id: 'user-1',
like_count: 10,
comment_count: 5
}
]
});
}),
http.get('*/api/v1/social/groups', () => {
return HttpResponse.json({
groups: [
{
id: 'g1',
name: 'Electronic Music Producers',
members: 1200,
isPrivate: false,
userRole: 'member',
description: 'A community for electronic music producers',
coverUrl: 'https://picsum.photos/800/400'
},
{
id: 'g2',
name: 'Beat Makers',
members: 850,
isPrivate: false,
userRole: 'none',
description: 'Share your beats and get feedback',
coverUrl: 'https://picsum.photos/801/400'
}
]
});
}),
// Search page: SearchResults shape (tracks, artists, playlists) after unwrap
http.get('*/api/v1/search', () => {
return HttpResponse.json({
success: true,
data: {
tracks: [
{
id: 'track-1',
title: 'Neon Signal',
artist: 'Void Producer',
cover_art_path: 'https://picsum.photos/200',
created_at: '2024-01-15T12:00:00Z',
},
{
id: 'track-2',
title: 'Deep Frequency',
artist: 'Echo Artist',
created_at: '2024-01-14T10:00:00Z',
},
],
artists: [
{
id: 'artist-1',
username: 'ProducerOne',
avatar_url: 'https://i.pravatar.cc/150?u=producer1',
followers_count: 120,
},
],
playlists: [
{
id: 'playlist-1',
title: 'Curated Mix',
description: 'Hand-picked tracks',
cover_url: 'https://picsum.photos/300',
},
],
},
});
}),
// Commerce & Marketplace endpoints
http.post('*/api/v1/marketplace/products', async ({ request }) => {
const body = (await request.json()) as { title: string; description?: string };
return HttpResponse.json(
{
product: {
id: 'prod-new',
title: body.title ?? 'New Product',
description: body.description ?? '',
price: 29.99,
currency: 'USD',
category: 'Sample Pack',
tags: [],
status: 'active',
owner_id: 'user-1',
license_type: 'personal',
metadata: {},
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
},
{ status: 201 },
);
}),
http.get('*/api/v1/marketplace/products', () => {
return HttpResponse.json([
{
id: 'prod-1',
title: 'Cyberpunk Drum Kit',
type: 'pack',
price: 29.99,
currency: 'USD',
rating: 4.5,
review_count: 120,
coverUrl: 'https://picsum.photos/300/300',
author: 'Neon Audio',
description: 'High-quality cyberpunk drums',
tags: ['drums', 'cyberpunk', 'electronic']
},
{
id: 'prod-2',
title: 'Lo-Fi Sample Pack',
type: 'pack',
price: 19.99,
currency: 'USD',
rating: 4.8,
review_count: 85,
coverUrl: 'https://picsum.photos/301/300',
author: 'Chill Beats',
description: 'Perfect for lo-fi hip hop',
tags: ['lofi', 'samples', 'chill']
}
]);
}),
http.get('*/api/v1/marketplace/wishlist', () => {
return HttpResponse.json({
items: [
{
id: 'wish-1',
product_id: 'prod-1',
product: {
id: 'prod-1',
title: 'Cyberpunk Drum Kit',
price: 29.99,
coverUrl: 'https://picsum.photos/300/300',
author: 'Neon Audio'
},
added_at: '2024-01-01T00:00:00Z'
}
],
total: 1
});
}),
http.get('*/api/v1/commerce/cart', () => {
return HttpResponse.json({
items: [
{
id: 'cart-1',
product_id: 'prod-1',
quantity: 1,
product: {
id: 'prod-1',
title: 'Cyberpunk Drum Kit',
price: 29.99,
coverUrl: 'https://picsum.photos/300/300'
}
}
],
subtotal: 29.99,
tax: 2.40,
total: 32.39
});
}),
http.get('*/api/v1/marketplace/orders', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'order-1',
status: 'completed',
total: 29.99,
created_at: '2024-01-01T00:00:00Z',
items: [
{
product_id: 'prod-1',
title: 'Cyberpunk Drum Kit',
price: 29.99
}
]
}
]
});
}),
// Education endpoints
http.get('*/api/v1/education/courses', () => {
return HttpResponse.json([
{
id: 'course-1',
title: 'Music Production Fundamentals',
level: 'Beginner',
duration: '5h 30m',
progress: 0,
instructor: 'John Doe',
thumbnailUrl: 'https://picsum.photos/400/250',
price: 49.99,
rating: 4.8,
studentCount: 1200,
tags: ['Production', 'Beginner']
},
{
id: 'course-2',
title: 'Advanced Mixing Techniques',
level: 'Advanced',
duration: '8h 15m',
progress: 0,
instructor: 'Jane Smith',
thumbnailUrl: 'https://picsum.photos/401/250',
price: 79.99,
rating: 4.9,
studentCount: 850,
tags: ['Mixing', 'Advanced']
}
]);
}),
http.get('*/api/v1/education/enrollments', () => {
return HttpResponse.json([
{
id: 'enroll-1',
course_id: 'course-1',
progress: 45,
lastAccessed: '2024-01-01T12:00:00Z',
course: {
id: 'course-1',
title: 'Music Production Fundamentals',
thumbnailUrl: 'https://picsum.photos/400/250'
}
}
]);
}),
// Gamification endpoints
http.get('*/api/v1/gamification/achievements', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'ach-1',
name: 'First Upload',
description: 'Upload your first track',
icon: '🎵',
progress: 1,
maxProgress: 1,
xpReward: 50,
category: 'creation',
unlocked: true
},
{
id: 'ach-2',
name: 'Social Butterfly',
description: 'Follow 10 users',
icon: '🦋',
progress: 5,
maxProgress: 10,
xpReward: 100,
category: 'social',
unlocked: false
}
]
});
}),
http.get('*/api/v1/gamification/leaderboard', () => {
return HttpResponse.json([
{
rank: 1,
userId: 'user-1',
username: 'TopProducer',
avatar: 'https://i.pravatar.cc/100?u=top',
level: 50,
xp: 125000,
trend: 0
},
{
rank: 2,
userId: 'user-2',
username: 'BeatMaster',
avatar: 'https://i.pravatar.cc/100?u=beat',
level: 45,
xp: 98000,
trend: 1
}
]);
}),
http.get('*/api/v1/gamification/xp/:userId', () => {
return HttpResponse.json({
success: true,
data: {
current: 4250,
next: 5000,
level: 12,
rank: 420,
totalEarned: 15400
}
});
}),
// Developer & Webhooks endpoints
http.get('*/api/v1/webhooks', () => {
return HttpResponse.json([
{
id: 'webhook-1',
url: 'https://example.com/webhook',
events: ['track.created', 'track.updated'],
active: true,
created_at: '2024-01-01T00:00:00Z'
}
]);
}),
http.get('*/api/v1/api-keys', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'key-1',
name: 'Production API Key',
key: 'pk_live_****************************',
created_at: '2024-01-01T00:00:00Z',
last_used: '2024-01-05T10:30:00Z'
}
]
});
}),
// Security & Settings endpoints (SessionsPage: sessions list shape matches backend)
http.get('*/api/v1/auth/sessions', () => {
return HttpResponse.json({
success: true,
data: {
sessions: [
{
id: 'session-1',
ip_address: '192.168.1.1',
user_agent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
created_at: '2024-01-05T10:00:00Z',
last_activity: '2024-01-05T12:00:00Z',
is_current: true,
},
{
id: 'session-2',
ip_address: '192.168.1.2',
user_agent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
created_at: '2024-01-04T08:00:00Z',
last_activity: '2024-01-04T08:00:00Z',
is_current: false,
},
],
count: 2,
},
});
}),
http.delete('*/api/v1/auth/sessions', () => {
return HttpResponse.json({ success: true, data: { message: 'ok' } });
}),
http.delete('*/api/v1/auth/sessions/*', () => {
return HttpResponse.json({ success: true, data: { message: 'ok' } });
}),
http.get('*/api/v1/auth/2fa', () => {
return HttpResponse.json({
success: true,
data: {
enabled: false,
method: null
}
});
}),
// Streaming endpoints
http.get('*/api/v1/streaming/bitrate-options', () => {
return HttpResponse.json({
success: true,
data: [
{ id: 'auto', label: 'Auto', bitrate: 0 },
{ id: 'high', label: 'High (320kbps)', bitrate: 320000 },
{ id: 'medium', label: 'Medium (128kbps)', bitrate: 128000 },
{ id: 'low', label: 'Low (64kbps)', bitrate: 64000 }
]
});
}),
http.get('*/api/v1/streaming/stats', () => {
return HttpResponse.json({
success: true,
data: {
bufferHealth: 0.8,
bitrate: 128000,
droppedFrames: 0,
latency: 25
}
});
}),
// Products endpoint (Fixing property access crashes)
http.get('*/api/v1/products*', () => {
return HttpResponse.json({
data: [
{
id: 'prod-1',
title: 'Mock Product',
price: 29.99,
currency: 'USD',
author: 'Mock Author',
coverUrl: 'https://picsum.photos/300',
rating: 4.5,
reviewCount: 10,
isHot: true
}
]
});
}),
// Explicit Track List (before wildcard)
http.get('*/api/v1/tracks', () => {
return HttpResponse.json({
data: [
{
id: 'track-1',
title: 'Storybook Track',
artist: 'Test Artist',
duration: 240,
cover_url: 'https://picsum.photos/200',
coverUrl: 'https://picsum.photos/200',
genre: 'Pop',
created_at: '2024-01-01T00:00:00Z',
play_count: 100,
like_count: 10,
download_count: 5,
waveform_url: 'https://example.com/waveform.json',
comments_count: 5,
is_liked: false,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
},
{
id: 'track-2',
title: 'Another Track',
artist: 'Test Artist',
duration: 180,
cover_url: 'https://picsum.photos/201',
coverUrl: 'https://picsum.photos/201',
genre: 'Rock',
created_at: '2024-01-02T00:00:00Z',
play_count: 50,
like_count: 5,
download_count: 2,
waveform_url: 'https://example.com/waveform.json',
comments_count: 2,
is_liked: true,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
}
],
total: 2,
page: 1,
limit: 20
});
}),
// Track History
http.get('*/api/v1/tracks/:id/history', () => {
return HttpResponse.json({
success: true,
data: {
history: [
{
id: 'hist-1',
track_id: 'track-1',
user_id: 'user-1',
action: 'created',
created_at: '2024-01-01T00:00:00Z'
},
{
id: 'hist-2',
track_id: 'track-1',
user_id: 'user-1',
action: 'updated',
old_value: '{"title": "Old Title"}',
new_value: '{"title": "New Title"}',
created_at: '2024-01-02T00:00:00Z'
}
],
total: 2,
limit: 50,
offset: 0
}
});
}),
http.get('*/api/v1/tracks*', () => {
return HttpResponse.json({
data: [
{
id: 'track-1',
title: 'Storybook Track',
artist: 'Test Artist',
duration: 240,
cover_url: 'https://picsum.photos/200',
coverUrl: 'https://picsum.photos/200',
genre: 'Pop',
created_at: '2024-01-01T00:00:00Z',
play_count: 100,
like_count: 10,
download_count: 5,
waveform_url: 'https://example.com/waveform.json',
comments_count: 5,
is_liked: false,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
},
{
id: 'track-2',
title: 'Another Track',
artist: 'Test Artist',
duration: 180,
cover_url: 'https://picsum.photos/201',
coverUrl: 'https://picsum.photos/201',
genre: 'Rock',
created_at: '2024-01-02T00:00:00Z',
play_count: 50,
like_count: 5,
download_count: 2,
waveform_url: 'https://example.com/waveform.json',
comments_count: 2,
is_liked: true,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
}
],
total: 2
});
}),
// Search tracks
http.get('*/api/v1/tracks/search', ({ request }) => {
return HttpResponse.json({
data: [
{
id: 'track-1',
title: 'Search Result Track 1',
artist: 'Test Artist',
duration: 240,
cover_url: 'https://picsum.photos/200',
coverUrl: 'https://picsum.photos/200',
genre: 'Pop',
created_at: '2024-01-01T00:00:00Z',
play_count: 100,
like_count: 10,
download_count: 5,
waveform_url: 'https://example.com/waveform.json',
comments_count: 5,
is_liked: false,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
},
{
id: 'track-2',
title: 'Search Result Track 2',
artist: 'Test Artist',
duration: 180,
cover_url: 'https://picsum.photos/201',
coverUrl: 'https://picsum.photos/201',
genre: 'Rock',
created_at: '2024-01-02T00:00:00Z',
play_count: 50,
like_count: 5,
download_count: 2,
waveform_url: 'https://example.com/waveform.json',
comments_count: 2,
is_liked: true,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
}
}
],
total: 2,
page: 1,
limit: 20
});
}),
// Upload Quota
http.get('*/api/v1/users/:userId/upload-quota', () => {
return HttpResponse.json({
success: true,
data: {
quota: {
tracks_count: 5,
tracks_limit: 10,
storage_used: 52428800, // 50MB
storage_limit: 104857600 // 100MB
}
}
});
}),
// Playback heatmap — response.data.heatmap after unwrap
http.get('*/api/v1/tracks/:id/playback/heatmap', () => {
const segments = [
{ start_time: 0, end_time: 5, listen_count: 10, skip_count: 0, intensity: 1.0, average_play_time: 5 },
{ start_time: 5, end_time: 10, listen_count: 8, skip_count: 2, intensity: 0.8, average_play_time: 4 },
{ start_time: 10, end_time: 15, listen_count: 5, skip_count: 3, intensity: 0.5, average_play_time: 2.5 },
{ start_time: 15, end_time: 20, listen_count: 12, skip_count: 0, intensity: 0.9, average_play_time: 4.5 },
{ start_time: 20, end_time: 25, listen_count: 3, skip_count: 1, intensity: 0.3, average_play_time: 1.5 },
];
return HttpResponse.json({
success: true,
data: {
heatmap: {
track_id: '123',
track_duration: 180,
segment_size: 5,
total_sessions: 38,
segments,
max_intensity: 1.0,
generated_at: new Date().toISOString(),
},
},
});
}),
// Playback dashboard (analytics) — response.data.dashboard in client
http.get('*/api/v1/tracks/:id/playback/dashboard', () => {
const timeSeries = Array.from({ length: 14 }, (_, i) => {
const d = new Date();
d.setDate(d.getDate() - (13 - i));
const date = d.toISOString().slice(0, 10);
return {
date,
sessions: 10 + i * 2,
total_play_time: (10 + i) * 180,
average_play_time: 120 + i * 5,
average_completion: 70 + i,
};
});
return HttpResponse.json({
dashboard: {
stats: {
total_sessions: 156,
total_play_time: 28400,
average_play_time: 182,
total_pauses: 42,
average_pauses: 0.27,
total_seeks: 18,
average_seeks: 0.12,
average_completion: 78.5,
completion_rate: 72,
},
trends: {
sessions_trend: 12.5,
play_time_trend: 8.2,
completion_trend: -2.1,
average_play_time: 175,
average_completion: 76,
total_sessions_7days: 48,
total_sessions_30days: 156,
},
time_series: timeSeries,
},
});
}),
// Track Stats
http.get('*/api/v1/tracks/:id/stats', () => {
return HttpResponse.json({
success: true,
data: {
stats: {
total_plays: 1000,
unique_listeners: 500,
average_duration: 150,
completion_rate: 80,
views: 2000,
likes: 100,
comments: 20,
total_play_time: 50000,
downloads: 50
}
}
});
}),
// Single track endpoint
http.get('*/api/v1/tracks/:id', ({ params }) => {
return HttpResponse.json({
id: params.id,
title: 'Storybook Track',
artist: 'Test Artist',
duration: 240,
cover_url: 'https://picsum.photos/200',
coverUrl: 'https://picsum.photos/200',
genre: 'Pop',
created_at: '2024-01-01T00:00:00Z',
play_count: 100,
like_count: 10,
download_count: 5,
waveform_url: 'https://example.com/waveform.json',
comments_count: 5,
description: 'A test track for Storybook',
bpm: 120,
key: 'Cm',
is_liked: false,
user: {
id: 'user-1',
username: 'ArtistUser',
avatar_url: 'https://i.pravatar.cc/150?u=artist'
},
stats: {
plays: 100,
likes: 10,
downloads: 5,
comments: 5
}
});
}),
// Track comments
http.get('*/api/v1/tracks/:id/comments', () => {
return HttpResponse.json({
comments: [
{
id: 'comment-1',
track_id: 'track-1',
user_id: 'user-2',
content: 'Great track!',
created_at: '2024-01-03T00:00:00Z',
updated_at: '2024-01-03T00:00:00Z',
is_edited: false,
user: {
id: 'user-2',
username: 'Commenter',
avatar: 'https://i.pravatar.cc/150?u=2'
},
replies: []
}
],
total: 1,
page: 1,
limit: 20
});
}),
http.post('*/api/v1/tracks/:id/comments', async ({ request }) => {
const body = await request.json() as any;
return HttpResponse.json({
comment: {
id: `new-comment-${Date.now()}`,
track_id: 'track-1',
user_id: 'user-1',
content: body.content,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
is_edited: false,
user: {
id: 'user-1',
username: 'StorybookUser',
avatar: 'https://i.pravatar.cc/150?u=1'
},
replies: []
}
});
}),
http.get('*/api/v1/comments/:id/replies', () => {
return HttpResponse.json({
replies: [],
total: 0,
page: 1,
limit: 20,
});
}),
http.put('*/api/v1/comments/:id', async ({ request, params }) => {
const body = (await request.json()) as { content?: string };
return HttpResponse.json({
comment: {
id: params.id,
track_id: 'track-1',
user_id: 'user-1',
content: body.content ?? '',
is_edited: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
user: {
id: 'user-1',
username: 'StorybookUser',
avatar: 'https://i.pravatar.cc/150?u=1'
},
replies: []
}
});
}),
http.get('*/api/v1/playlists*', () => {
return HttpResponse.json({
data: [
{
id: 'pl-1',
title: 'Mock Playlist',
name: 'Mock Playlist',
cover_url: 'https://picsum.photos/300',
track_count: 5,
play_count: 50,
like_count: 20,
share_count: 5,
tags: ['Chill', 'Lo-Fi']
}
]
});
}),
// Notifications (notificationService expects data: { notifications, total?, unread_count? } after unwrap)
http.get('*/api/v1/notifications', () => {
return HttpResponse.json({
success: true,
data: {
notifications: [
{
id: 'notif-1',
user_id: 'user-1',
type: 'new_message',
title: 'New message',
content: 'Someone sent you a message',
read: false,
created_at: '2024-01-04T00:00:00Z',
link: '/chat/1',
},
{
id: 'notif-2',
user_id: 'user-1',
type: 'track_uploaded',
title: 'New track',
content: 'A creator you follow uploaded a track',
read: true,
created_at: '2024-01-03T12:00:00Z',
},
],
total: 2,
unread_count: 1,
},
});
}),
// Add more generic handlers as needed...
// Users endpoints
http.get('*/api/v1/users/search', () => {
return HttpResponse.json({
success: true,
data: {
items: [
{
id: 'user-1',
username: 'StorybookUser',
avatar_url: 'https://i.pravatar.cc/150?u=1',
}
],
total: 1
}
});
}),
http.get('*/api/v1/users/settings', () => {
return HttpResponse.json({
success: true,
data: {
email_notifications: true,
privacy_level: 'public',
theme: 'dark'
}
});
}),
http.put('*/api/v1/users/settings', () => {
return HttpResponse.json({
success: true,
data: {
email_notifications: true,
privacy_level: 'public',
theme: 'dark'
}
});
}),
http.get('*/api/v1/users/by-username/:username', ({ params }) => {
if (params.username === 'notfound') {
return HttpResponse.json({ message: 'User not found' }, { status: 404 });
}
return HttpResponse.json({
profile: {
id: '123',
username: params.username,
first_name: 'Story',
last_name: 'User',
avatar_url: `https://i.pravatar.cc/150?u=${params.username}`,
bio: 'Music enthusiast',
location: 'Paris, France',
birthdate: null,
gender: null,
created_at: '2024-01-01T00:00:00Z',
followers_count: 42,
following_count: 10,
},
});
}),
http.get('*/api/v1/users/:id', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
id: params.id,
username: 'StorybookUser',
email: 'user@example.com',
avatar_url: `https://i.pravatar.cc/150?u=${params.id}`,
created_at: '2024-01-01T00:00:00Z',
role: 'user',
bio: 'Music enthusiast',
location: 'Paris, France',
website: 'https://example.com'
}
});
}),
http.put('*/api/v1/users/:id', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
id: params.id,
username: 'UpdatedUser',
email: 'user@example.com'
}
});
}),
http.delete('*/api/v1/users/:id', () => {
return HttpResponse.json({ success: true });
}),
http.get('*/api/v1/users/:id/completion', () => {
return HttpResponse.json({
success: true,
data: {
percentage: 80,
missing: ['bio', 'website'],
},
});
}),
http.post('*/api/v1/users/:id/follow', () => {
return HttpResponse.json({ success: true });
}),
http.delete('*/api/v1/users/:id/follow', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/users/:id/block', () => {
return HttpResponse.json({ success: true });
}),
http.delete('*/api/v1/users/:id/block', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/users/:id/avatar', () => {
return HttpResponse.json({
avatar_url: 'https://i.pravatar.cc/150?u=new',
});
}),
http.delete('*/api/v1/users/:id/avatar', () => {
return HttpResponse.json({ success: true });
}),
http.get('*/api/v1/users/:id/likes', () => {
return HttpResponse.json({
success: true,
data: {
tracks: [],
total: 0
}
});
}),
// Tracks endpoints (Specific methods)
http.post('*/api/v1/tracks', () => {
return HttpResponse.json({
success: true,
data: {
id: `track-${Date.now()}`,
title: 'New Uploaded Track',
status: 'processing'
}
});
}),
http.put('*/api/v1/tracks/:id', ({ params }) => {
return HttpResponse.json({
success: true,
track: {
id: params.id,
title: 'Updated Track Title',
artist: 'Updated Artist'
}
});
}),
http.delete('*/api/v1/tracks/:id', () => {
return HttpResponse.json({ success: true });
}),
http.get('*/api/v1/tracks/:id/download', () => {
return new HttpResponse(
new ArrayBuffer(1024),
{ headers: { 'Content-Type': 'audio/mpeg' } }
);
}),
http.post('*/api/v1/tracks/:id/like', () => {
return HttpResponse.json({ success: true });
}),
http.delete('*/api/v1/tracks/:id/like', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/tracks/:id/share', () => {
return HttpResponse.json({
success: true,
share: {
token: 'share-token-123',
url: 'https://veza.com/share/123'
}
});
}),
http.get('*/api/v1/tracks/:id/comments', () => {
return HttpResponse.json({
success: true,
data: {
comments: [],
total: 0
}
});
}),
http.post('*/api/v1/tracks/:id/comments', () => {
return HttpResponse.json({
success: true,
data: {
id: 'comment-new',
content: 'Great track!',
created_at: new Date().toISOString()
}
});
}),
http.delete('*/api/v1/comments/:id', () => {
return HttpResponse.json({ success: true });
}),
// Playlists endpoints
http.get('*/api/v1/playlists', () => {
return HttpResponse.json({
success: true,
data: {
items: [
{
id: 'pl-1',
name: 'My Playlist',
track_count: 10,
cover_url: 'https://picsum.photos/300'
}
],
total: 1
}
});
}),
http.post('*/api/v1/playlists', () => {
return HttpResponse.json({
success: true,
data: {
id: 'pl-new',
name: 'New Playlist'
}
});
}),
http.get('*/api/v1/playlists/:id', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
id: params.id,
name: 'Playlist Detail',
description: 'A mock playlist',
tracks: [],
owner: { id: 'user-1', username: 'Owner' }
}
});
}),
http.put('*/api/v1/playlists/:id', () => {
return HttpResponse.json({
success: true,
data: { name: 'Updated Playlist' }
});
}),
http.delete('*/api/v1/playlists/:id', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/playlists/:id/tracks', () => {
return HttpResponse.json({ success: true });
}),
http.delete('*/api/v1/playlists/:id/tracks/:trackId', () => {
return HttpResponse.json({ success: true });
}),
http.get('*/api/v1/playlists/:id/collaborators', () => {
return HttpResponse.json({
success: true,
data: []
});
}),
http.get('*/api/v1/playlists/search', () => {
return HttpResponse.json({
playlists: [
{
id: 'pl-search-1',
user_id: 'u1',
title: 'Summer Vibes',
description: 'Sun-soaked tracks',
is_public: true,
track_count: 12,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
},
{
id: 'pl-search-2',
user_id: 'u1',
title: 'Workout Mix',
description: 'High energy',
is_public: false,
track_count: 8,
created_at: '2024-01-02T00:00:00Z',
updated_at: '2024-01-02T00:00:00Z',
},
],
total: 2,
page: 1,
});
}),
// Chat endpoints
http.post('*/api/v1/chat/token', () => {
return HttpResponse.json({
success: true,
token: 'mock-chat-token'
});
}),
http.get('*/api/v1/chat/stats', () => {
return HttpResponse.json({
success: true,
data: {
online_users: 42,
active_rooms: 5
}
});
}),
http.get('*/api/v1/conversations', () => {
return HttpResponse.json({
success: true,
data: [
{
id: 'conv-1',
name: 'General Chat',
last_message: 'Hello world',
updated_at: new Date().toISOString()
}
]
});
}),
http.get('*/api/v1/conversations/:id', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
id: params.id,
messages: []
}
});
}),
// Notification extra endpoints
http.get('*/api/v1/notifications/unread-count', () => {
return HttpResponse.json({
success: true,
data: { count: 3 }
});
}),
http.post('*/api/v1/notifications/:id/read', () => {
return HttpResponse.json({ success: true });
}),
http.post('*/api/v1/notifications/read-all', () => {
return HttpResponse.json({ success: true });
}),
// Inventory / Gear (STORYBOOK_CONTRACT: mock for gear view stories)
http.get('*/api/v1/inventory/gear', () => {
return HttpResponse.json({
success: true,
data: {
items: [
{
id: '1',
name: 'Prophet-6',
category: 'Synth',
brand: 'Sequential',
model: 'Prophet-6 Desktop',
serialNumber: 'SQ-P6-99281',
purchaseDate: '2023-01-15',
purchasePrice: 2499,
currency: 'USD',
status: 'Active',
condition: 'Mint',
vendor: 'Sweetwater',
orderNumber: 'SW-8821002',
warrantyExpire: '2025-01-15',
warrantyType: 'Manufacturer',
supportContact: 'support@sequential.com',
image: 'https://picsum.photos/id/100/400/400',
specs: { Polyphony: '6 Voices', Oscillators: '2 Discrete VCOs', Filter: 'Low-pass + High-pass', Sequencer: '64-step' },
documents: [
{ name: 'User Manual', type: 'manual', url: '#', size: '4.2 MB' },
{ name: 'Purchase Receipt', type: 'receipt', url: '#', size: '150 KB' },
],
},
{
id: '2',
name: 'Apollo Twin X',
category: 'Interface',
brand: 'Universal Audio',
model: 'Twin X Duo',
serialNumber: 'UA-TWX-2210',
purchaseDate: '2022-11-20',
purchasePrice: 999,
currency: 'USD',
status: 'Active',
condition: 'Good',
vendor: 'Thomann',
warrantyExpire: '2023-11-20',
warrantyType: 'Manufacturer',
image: 'https://picsum.photos/id/101/400/400',
specs: { Inputs: '2 Mic/Line', Outputs: '4 Line', Connection: 'Thunderbolt 3', DSP: 'Duo Core' },
documents: [{ name: 'Firmware v1.2', type: 'firmware', url: '#', size: '120 MB' }],
maintenanceHistory: [{ id: 'm1', date: '2023-05-10', type: 'Cleaning', notes: 'Potentiometer de-oxidizing', cost: 0 }],
},
{
id: '3',
name: 'SM7B',
category: 'Microphone',
brand: 'Shure',
model: 'SM7B Dynamic',
serialNumber: 'SH-SM7-004',
purchaseDate: '2021-05-10',
purchasePrice: 399,
currency: 'USD',
status: 'Maintenance',
condition: 'Fair',
vendor: 'Guitar Center',
warrantyExpire: '2023-05-10',
warrantyType: 'None',
image: 'https://picsum.photos/id/102/400/400',
notes: 'XLR connector feels loose. Sent for repair.',
maintenanceHistory: [{ id: 'm2', date: '2024-02-15', type: 'Repair', notes: 'XLR Jack Replacement', cost: 45, provider: 'Local Shop' }],
},
],
},
});
}),
// Catch-all for API to prevent network leaks (Phase 1: Stabilization)
http.all('*/api/v1/*', ({ request }) => {
console.warn('[MSW] Intercepted unhandled API request:', request.method, request.url);
return HttpResponse.json({ success: true, message: 'Mocked fallback response from Storybook' });
}),
];