- Backend: is_public in Profile, UpdateProfile; strip SocialLinks for private - Settings: ProfileVisibilityCard toggle in Privacy tab - UserProfilePage: show 'Profil privé' when viewing private profile
480 lines
17 KiB
TypeScript
480 lines
17 KiB
TypeScript
/**
|
|
* MSW handlers for search, notifications, users, chat, streaming, products, inventory, live, queue
|
|
*/
|
|
|
|
import { http, HttpResponse } from 'msw';
|
|
|
|
const mockQueueItems: Array<{
|
|
id: string;
|
|
queue_id: string;
|
|
track_id: string;
|
|
position: number;
|
|
added_at: string;
|
|
track: { id: string; title: string; artist?: string; duration?: number; cover_art_path?: string };
|
|
}> = [];
|
|
|
|
function createQueueHandlers() {
|
|
return [
|
|
http.get('*/api/v1/queue', () => {
|
|
const items = [...mockQueueItems].sort((a, b) => a.position - b.position);
|
|
const queue = {
|
|
id: 'queue-mock-1',
|
|
user_id: 'user-1',
|
|
current_track_id: items[0]?.track_id,
|
|
current_position: 0,
|
|
is_playing: false,
|
|
shuffle: false,
|
|
repeat_mode: 'off',
|
|
volume: 100,
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
return HttpResponse.json({ success: true, data: { queue, items } });
|
|
}),
|
|
http.put('*/api/v1/queue', async ({ request }) => {
|
|
const body = (await request.json()) as { item_order?: string[] };
|
|
if (body.item_order) {
|
|
body.item_order.forEach((id, i) => {
|
|
const item = mockQueueItems.find((m) => m.id === id);
|
|
if (item) item.position = i;
|
|
});
|
|
}
|
|
const items = [...mockQueueItems].sort((a, b) => a.position - b.position);
|
|
const queue = {
|
|
id: 'queue-mock-1',
|
|
user_id: 'user-1',
|
|
current_position: 0,
|
|
is_playing: false,
|
|
shuffle: false,
|
|
repeat_mode: 'off',
|
|
volume: 100,
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
return HttpResponse.json({ success: true, data: { queue, items } });
|
|
}),
|
|
http.post('*/api/v1/queue/items', async ({ request }) => {
|
|
const body = (await request.json()) as { track_id: string };
|
|
const trackId = body.track_id;
|
|
const maxPos = mockQueueItems.length ? Math.max(...mockQueueItems.map((m) => m.position)) + 1 : 0;
|
|
const item = {
|
|
id: `qi-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
queue_id: 'queue-mock-1',
|
|
track_id: trackId,
|
|
position: maxPos,
|
|
added_at: new Date().toISOString(),
|
|
track: {
|
|
id: trackId,
|
|
title: `Track ${trackId.slice(0, 8)}`,
|
|
artist: 'Mock Artist',
|
|
duration: 180,
|
|
cover_art_path: 'https://picsum.photos/200',
|
|
},
|
|
};
|
|
mockQueueItems.push(item);
|
|
return HttpResponse.json({ success: true, data: { item } }, { status: 201 });
|
|
}),
|
|
http.delete('*/api/v1/queue/items/:id', ({ params }) => {
|
|
const idx = mockQueueItems.findIndex((m) => m.id === params.id);
|
|
if (idx >= 0) mockQueueItems.splice(idx, 1);
|
|
return HttpResponse.json({ success: true, data: { message: 'item removed' } });
|
|
}),
|
|
http.delete('*/api/v1/queue', () => {
|
|
mockQueueItems.length = 0;
|
|
return HttpResponse.json({ success: true, data: { message: 'queue cleared' } });
|
|
}),
|
|
];
|
|
}
|
|
|
|
export const handlersMisc = [
|
|
http.get('*/api/v1/analytics', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
total_plays: 1200,
|
|
total_revenue: 450.5,
|
|
followers: 85,
|
|
profile_views: 0,
|
|
trends: { plays: 8.2, revenue: 12.5, followers: 2.1, views: 0 },
|
|
sparklines: {
|
|
plays: [40, 35, 50, 60, 55, 70, 80, 75, 90],
|
|
revenue: [10, 12, 15, 14, 18, 20, 22, 25, 28],
|
|
followers: [20, 21, 21, 22, 22, 23, 23, 24, 24],
|
|
views: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
},
|
|
tracks: {
|
|
total_tracks: 12,
|
|
total_plays: 1200,
|
|
top_tracks: [
|
|
{ id: 't1', title: 'Neon Nights', plays: 420, play_count: 420, change: 12, revenue: 45.5 },
|
|
{ id: 't2', title: 'Cyber City', plays: 310, play_count: 310, change: -5, revenue: 28.2 },
|
|
],
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
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' }],
|
|
},
|
|
});
|
|
}),
|
|
|
|
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 },
|
|
});
|
|
}),
|
|
|
|
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 }],
|
|
});
|
|
}),
|
|
|
|
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,
|
|
},
|
|
});
|
|
}),
|
|
|
|
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 } });
|
|
}),
|
|
|
|
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({
|
|
notifications: { email_notifications: true, push_notifications: true, marketing_emails: false, new_follower: true, new_comment: true, new_like: true, playlist_update: true },
|
|
privacy: { allow_search_indexing: true, show_activity: true },
|
|
content: { explicit_content: false, autoplay: true },
|
|
preferences: { language: 'en', timezone: 'UTC', theme: 'dark' },
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/users/:userId/settings', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
notifications: { email_notifications: true, push_notifications: true, marketing_emails: false, new_follower: true, new_comment: true, new_like: true, playlist_update: true },
|
|
privacy: { allow_search_indexing: true, show_activity: true },
|
|
content: { explicit_content: false, autoplay: true },
|
|
preferences: { language: 'en', timezone: 'UTC', theme: 'dark' },
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.put('*/api/v1/users/settings', () => {
|
|
return HttpResponse.json({ success: true });
|
|
}),
|
|
|
|
http.put('*/api/v1/users/:userId/settings', () => {
|
|
return HttpResponse.json({ success: true });
|
|
}),
|
|
|
|
http.get('*/api/v1/users/by-username/:username', ({ params }) => {
|
|
if (params.username === 'notfound') {
|
|
return HttpResponse.json({ message: 'User not found' }, { status: 404 });
|
|
}
|
|
const isPrivate = params.username === 'privateuser';
|
|
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}`,
|
|
banner_url: params.username === 'demo' ? 'https://picsum.photos/1200/400?random=1' : null,
|
|
bio: 'Music enthusiast',
|
|
location: 'Paris, France',
|
|
birthdate: null,
|
|
gender: null,
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
followers_count: 42,
|
|
following_count: 10,
|
|
social_links:
|
|
params.username === 'demo'
|
|
? {
|
|
twitter: 'https://twitter.com/veza_demo',
|
|
youtube: 'https://youtube.com/@veza_channel',
|
|
instagram: 'https://instagram.com/veza_profile',
|
|
}
|
|
: undefined,
|
|
is_public: !isPrivate,
|
|
},
|
|
});
|
|
}),
|
|
|
|
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',
|
|
is_public: true,
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.put('*/api/v1/users/:id', async ({ params, request }) => {
|
|
const body = (await request.json()) as { is_public?: boolean };
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
id: params.id,
|
|
username: 'UpdatedUser',
|
|
email: 'user@example.com',
|
|
is_public: body?.is_public ?? true,
|
|
},
|
|
});
|
|
}),
|
|
|
|
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', () => HttpResponse.json({ success: true })),
|
|
http.delete('*/api/v1/users/:id/follow', () => HttpResponse.json({ success: true })),
|
|
http.post('*/api/v1/users/:id/block', () => HttpResponse.json({ success: true })),
|
|
http.delete('*/api/v1/users/:id/block', () => HttpResponse.json({ success: true })),
|
|
http.post('*/api/v1/users/:id/avatar', () => HttpResponse.json({ avatar_url: 'https://i.pravatar.cc/150?u=new' })),
|
|
http.delete('*/api/v1/users/:id/avatar', () => HttpResponse.json({ success: true })),
|
|
http.get('*/api/v1/users/:id/likes', () => {
|
|
return HttpResponse.json({ success: true, data: { tracks: [], total: 0 } });
|
|
}),
|
|
|
|
http.get('*/api/v1/users/:userId/upload-quota', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
quota: {
|
|
tracks_count: 5,
|
|
tracks_limit: 10,
|
|
storage_used: 52428800,
|
|
storage_limit: 104857600,
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
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: [] },
|
|
});
|
|
}),
|
|
|
|
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', image: 'https://picsum.photos/id/100/400/400' },
|
|
{ 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', image: 'https://picsum.photos/id/101/400/400' },
|
|
{ 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', image: 'https://picsum.photos/id/102/400/400' },
|
|
],
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.post('*/api/v1/inventory/gear', async ({ request }) => {
|
|
const body = (await request.json()) as Record<string, unknown>;
|
|
const item = {
|
|
id: `gear-${Date.now()}`,
|
|
name: body.name ?? 'New Gear',
|
|
category: body.category ?? 'Synth',
|
|
brand: body.brand ?? '',
|
|
model: body.model ?? '',
|
|
serialNumber: body.serialNumber ?? '',
|
|
purchaseDate: body.purchaseDate ?? '',
|
|
purchasePrice: body.purchasePrice ?? 0,
|
|
currency: body.currency ?? 'USD',
|
|
status: body.status ?? 'Active',
|
|
condition: body.condition ?? 'Good',
|
|
vendor: body.vendor ?? '',
|
|
image: 'https://picsum.photos/id/100/400/400',
|
|
};
|
|
return HttpResponse.json({ success: true, data: { item } }, { status: 201 });
|
|
}),
|
|
|
|
http.put('*/api/v1/inventory/gear/:id', async ({ request, params }) => {
|
|
const body = (await request.json()) as Record<string, unknown>;
|
|
const item = {
|
|
id: params.id,
|
|
name: body.name ?? 'Updated',
|
|
category: body.category ?? 'Synth',
|
|
brand: body.brand ?? '',
|
|
model: body.model ?? '',
|
|
serialNumber: body.serialNumber ?? '',
|
|
purchaseDate: body.purchaseDate ?? '',
|
|
purchasePrice: body.purchasePrice ?? 0,
|
|
currency: body.currency ?? 'USD',
|
|
status: body.status ?? 'Active',
|
|
condition: body.condition ?? 'Good',
|
|
vendor: body.vendor ?? '',
|
|
image: 'https://picsum.photos/id/100/400/400',
|
|
};
|
|
return HttpResponse.json({ success: true, data: { item } });
|
|
}),
|
|
|
|
http.delete('*/api/v1/inventory/gear/:id', () => {
|
|
return HttpResponse.json({ success: true, message: 'gear item deleted' });
|
|
}),
|
|
|
|
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 🎧', 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 🎧',
|
|
streamer: 'Neuro_Glitch',
|
|
viewers: 1240,
|
|
thumbnailUrl: 'https://picsum.photos/id/140/800/450',
|
|
tags: ['Production', 'Ableton', 'DnB'],
|
|
isLive: true,
|
|
category: 'Production',
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.post('*/api/v1/live/streams', async ({ request }) => {
|
|
const body = (await request.json()) as Record<string, unknown>;
|
|
const stream = {
|
|
id: `stream-${Date.now()}`,
|
|
title: (body.title as string) ?? 'New Stream',
|
|
streamer: 'You',
|
|
viewers: 0,
|
|
thumbnailUrl: 'https://picsum.photos/id/140/800/450',
|
|
tags: (body.tags as string[]) ?? [],
|
|
isLive: false,
|
|
category: 'Production',
|
|
};
|
|
return HttpResponse.json({ success: true, data: { stream } }, { status: 201 });
|
|
}),
|
|
|
|
// Queue API (v0.102) — in-memory mock for Storybook/tests
|
|
...createQueueHandlers(),
|
|
|
|
// Developer API Keys (v0.102 Lot C)
|
|
http.get('*/api/v1/developer/api-keys', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
keys: [
|
|
{
|
|
id: 'key-1',
|
|
name: 'Production Server',
|
|
prefix: 'vza_abc1...',
|
|
scopes: ['read', 'write'],
|
|
last_used_at: '2024-02-18T12:00:00Z',
|
|
created_at: '2024-01-15T10:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}),
|
|
http.post('*/api/v1/developer/api-keys', async ({ request }) => {
|
|
const body = (await request.json()) as { name: string; scopes?: string[] };
|
|
const key = `vza_${Math.random().toString(36).slice(2, 10)}_${Math.random().toString(36).slice(2, 24)}`;
|
|
return HttpResponse.json(
|
|
{
|
|
success: true,
|
|
data: {
|
|
id: `key-${Date.now()}`,
|
|
name: body.name ?? 'New Key',
|
|
prefix: key.slice(0, 8) + '...',
|
|
scopes: body.scopes ?? ['read'],
|
|
created_at: new Date().toISOString(),
|
|
key,
|
|
},
|
|
},
|
|
{ status: 201 },
|
|
);
|
|
}),
|
|
http.delete('*/api/v1/developer/api-keys/:id', () => {
|
|
return HttpResponse.json({ success: true, data: { message: 'API key revoked' } });
|
|
}),
|
|
];
|