Bloc A - Code mort: - Suppression Studio (components, views, features) - Suppression gamification + services mock (projectService, storageService, gamificationService) - Mise à jour Sidebar, Navbar, locales Bloc B - Frontend: - Suppression modal.tsx deprecated, Modal.stories (doublon Dialog) - Feature flags: PLAYLIST_SEARCH, PLAYLIST_RECOMMENDATIONS, ROLE_MANAGEMENT = true - Suppression 19 tests orphelins, retrait exclusions vitest.config Bloc C - Backend: - Extraction routes_auth.go depuis router.go Bloc D - Rust: - Suppression security_legacy.rs (code mort, patterns déjà dans security/)
1736 lines
47 KiB
TypeScript
1736 lines
47 KiB
TypeScript
import { http, HttpResponse } from 'msw';
|
|
import { ghostHandlers } from './handlers-ghost';
|
|
|
|
/**
|
|
* 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({
|
|
success: true,
|
|
data: {
|
|
groups: [
|
|
{
|
|
id: 'g1',
|
|
name: 'Electronic Music Producers',
|
|
member_count: 1200,
|
|
is_public: true,
|
|
description: 'A community for electronic music producers',
|
|
avatar_url: 'https://picsum.photos/800/400',
|
|
},
|
|
{
|
|
id: 'g2',
|
|
name: 'Beat Makers',
|
|
member_count: 850,
|
|
is_public: true,
|
|
description: 'Share your beats and get feedback',
|
|
avatar_url: 'https://picsum.photos/801/400',
|
|
},
|
|
],
|
|
total: 2,
|
|
},
|
|
});
|
|
}),
|
|
http.get('*/api/v1/social/groups/:id', ({ params }) => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
id: params.id,
|
|
name: 'Electronic Music Producers',
|
|
member_count: 1200,
|
|
is_public: true,
|
|
description: 'A community for electronic music producers',
|
|
avatar_url: 'https://picsum.photos/800/400',
|
|
},
|
|
});
|
|
}),
|
|
http.post('*/api/v1/social/groups', async ({ request }) => {
|
|
const body = (await request.json()) as { name: string; description?: string; is_public?: boolean };
|
|
return HttpResponse.json(
|
|
{
|
|
success: true,
|
|
data: {
|
|
id: `g-${Date.now()}`,
|
|
name: body.name,
|
|
description: body.description ?? '',
|
|
is_public: body.is_public ?? true,
|
|
member_count: 1,
|
|
avatar_url: 'https://picsum.photos/id/10/800/400',
|
|
},
|
|
},
|
|
{ status: 201 },
|
|
);
|
|
}),
|
|
http.post('*/api/v1/social/groups/:id/join', () => {
|
|
return HttpResponse.json({ success: true, data: { message: 'Joined group successfully' } });
|
|
}),
|
|
http.delete('*/api/v1/social/groups/:id/leave', () => {
|
|
return HttpResponse.json({ success: true, data: { message: 'Left group successfully' } });
|
|
}),
|
|
|
|
// 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({
|
|
success: true,
|
|
data: {
|
|
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',
|
|
type: 'sample_pack',
|
|
currency: 'USD',
|
|
},
|
|
added_at: '2024-01-01T00:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}),
|
|
http.post('*/api/v1/marketplace/wishlist', () => {
|
|
return HttpResponse.json({ success: true, data: {} }, { status: 201 });
|
|
}),
|
|
http.delete('*/api/v1/marketplace/wishlist/*', () => {
|
|
return HttpResponse.json({ success: true, data: { message: 'Removed from wishlist' } });
|
|
}),
|
|
|
|
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 — backend at /api/v1/education/courses/list (Storybook/MSW mock)
|
|
http.get('*/api/v1/education/courses/list', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: [
|
|
{
|
|
id: 'course-1',
|
|
title: 'Music Production Fundamentals',
|
|
description: 'Learn the basics of music production',
|
|
instructor: 'John Doe',
|
|
category: 'Production',
|
|
level: 'beginner',
|
|
duration: 19800000000000, // 5h30m in nanoseconds
|
|
price: 49.99,
|
|
thumbnail: 'https://picsum.photos/seed/edu1/400/225',
|
|
tags: ['Production', 'Beginner'],
|
|
},
|
|
{
|
|
id: 'course-2',
|
|
title: 'Advanced Mixing Techniques',
|
|
description: 'Master professional mixing',
|
|
instructor: 'Jane Smith',
|
|
category: 'Mixing',
|
|
level: 'advanced',
|
|
duration: 29700000000000, // 8h15m in nanoseconds
|
|
price: 79.99,
|
|
thumbnail: 'https://picsum.photos/seed/edu2/400/225',
|
|
tags: ['Mixing', 'Advanced'],
|
|
},
|
|
],
|
|
});
|
|
}),
|
|
|
|
// Ghost features (Education catalog fallback, Gamification) are in handlers-ghost.ts
|
|
|
|
// 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', () => {
|
|
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 };
|
|
const title = body?.title ?? 'New Playlist';
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlist: {
|
|
id: 'pl-new',
|
|
name: title,
|
|
title,
|
|
track_count: 0,
|
|
like_count: 0,
|
|
user_id: 'user-1',
|
|
description: '',
|
|
is_public: true,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
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,
|
|
data: { 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, data: { read: true } });
|
|
}),
|
|
|
|
http.post('*/api/v1/notifications/read-all', () => {
|
|
return HttpResponse.json({ success: true, data: { read: 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' }],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}),
|
|
|
|
// Live streams (STORYBOOK_CONTRACT: mock for live view)
|
|
http.get('*/api/v1/live/streams', ({ request }) => {
|
|
const url = new URL(request.url);
|
|
const isLive = url.searchParams.get('is_live');
|
|
const streams = [
|
|
{
|
|
id: '1',
|
|
title: 'Late Night DnB Production 🎧 | Feedback Session',
|
|
streamer: 'Neuro_Glitch',
|
|
viewers: 1240,
|
|
thumbnailUrl: 'https://picsum.photos/id/140/800/450',
|
|
tags: ['Production', 'Ableton', 'DnB'],
|
|
isLive: true,
|
|
category: 'Production',
|
|
},
|
|
];
|
|
const filtered =
|
|
isLive === 'true'
|
|
? streams.filter((s) => s.isLive)
|
|
: isLive === 'false'
|
|
? streams.filter((s) => !s.isLive)
|
|
: streams;
|
|
return HttpResponse.json({ success: true, data: { streams: filtered } });
|
|
}),
|
|
http.get('*/api/v1/live/streams/:id', ({ params }) => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
stream: {
|
|
id: params.id,
|
|
title: 'Late Night DnB Production 🎧 | Feedback Session',
|
|
streamer: 'Neuro_Glitch',
|
|
viewers: 1240,
|
|
thumbnailUrl: 'https://picsum.photos/id/140/800/450',
|
|
tags: ['Production', 'Ableton', 'DnB'],
|
|
isLive: true,
|
|
category: 'Production',
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
// Ghost feature handlers (Education, Gamification — no backend)
|
|
...ghostHandlers,
|
|
|
|
// 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' });
|
|
}),
|
|
];
|