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( '', { headers: { 'Content-Type': 'image/svg+xml' } } ); }), http.get('https://i.pravatar.cc/*', async () => { return new HttpResponse( '', { headers: { 'Content-Type': 'image/svg+xml' } } ); }), http.get('https://api.dicebear.com/*', async () => { return new HttpResponse( '', { headers: { 'Content-Type': 'image/svg+xml' } } ); }), http.get('https://www.transparenttextures.com/*', async () => { return new HttpResponse( '', { 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: [] } }); }), // Playlists wildcard removed - use specific handlers below (get playlists, :id, search, recommendations, share) // 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', title: 'My Playlist', track_count: 10, cover_url: 'https://picsum.photos/300', }, ], total: 1, }, }); }), http.post('*/api/v1/playlists', async ({ request }) => { const body = (await request.json()) as { title?: string }; return HttpResponse.json({ success: true, data: { id: 'pl-new', name: body?.title ?? 'New Playlist', title: body?.title ?? 'New Playlist', track_count: 0, like_count: 0, }, }); }), http.get('*/api/v1/playlists/recommendations', () => { return HttpResponse.json({ success: true, data: [ { id: 'pl-rec-1', title: 'Recommended Playlist', name: 'Recommended Playlist', track_count: 8, cover_url: 'https://picsum.photos/300', }, ], }); }), http.get('*/api/v1/playlists/search', ({ request }) => { const url = new URL(request.url); const query = url.searchParams.get('query') ?? ''; if (query === 'NonExistentPlaylist') { return HttpResponse.json({ success: true, data: [] }); } return HttpResponse.json({ success: true, data: [ { 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', }, ], }); }), http.get('*/api/v1/playlists/:id', ({ params }) => { return HttpResponse.json({ success: true, data: { id: params.id, name: 'Playlist Detail', title: '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, data: {} }); }), http.delete('*/api/v1/playlists/:id/tracks/:trackId', () => { return HttpResponse.json({ success: true }); }), http.get('*/api/v1/playlists/recommendations', () => { return HttpResponse.json({ success: true, data: [ { id: 'pl-rec-1', title: 'Recommended Playlist', name: 'Recommended Playlist', track_count: 8, cover_url: 'https://picsum.photos/300', }, ], }); }), http.post('*/api/v1/playlists/:id/share', ({ params }) => { return HttpResponse.json({ success: true, data: { share_url: `https://veza.example/playlists/${params.id}/share/abc123`, }, }); }), http.get('*/api/v1/playlists/:id/collaborators', () => { return HttpResponse.json({ success: true, data: [] }); }), // 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' }); }), ];