Backend: - F141: GET /discover/playlists/editorial for editorial playlists - F143: GET /playlists/shared/:token (public, no auth) - F145: POST /playlists/import (JSON), GET /playlists/:id/export/m3u - F136: GET /playlists/favoris (creates Favoris playlist if needed) - Repo: GetFavorisByUserID, service GetOrCreateFavorisPlaylist Frontend: - SharedPlaylistPage at /playlists/shared/:token (public route) - Editorial playlists section in DiscoverPage - Export M3U in ExportPlaylistButton dropdown - Import JSON via ImportPlaylistButton (PlaylistListPage) - Favoris sidebar link, FavorisRedirectPage, AddToFavorisButton on tracks Roadmap: v0.10.4 marked DONE
259 lines
7.5 KiB
TypeScript
259 lines
7.5 KiB
TypeScript
/**
|
|
* MSW handlers for playlists endpoints
|
|
*/
|
|
|
|
import { http, HttpResponse } from 'msw';
|
|
|
|
export const handlersPlaylists = [
|
|
http.get('*/api/v1/playlists', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlists: [
|
|
{ id: 'pl-1', name: 'My Playlist', title: 'My Playlist', track_count: 10, cover_url: 'https://picsum.photos/300' },
|
|
],
|
|
total: 1,
|
|
page: 1,
|
|
limit: 20,
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.post('*/api/v1/playlists/import', async ({ request }) => {
|
|
const body = (await request.json()) as { playlist?: { title?: string }; tracks?: unknown[] };
|
|
const title = body?.playlist?.title ?? 'Imported Playlist';
|
|
const trackCount = body?.tracks?.length ?? 0;
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlist: {
|
|
id: 'pl-imported-1',
|
|
name: title,
|
|
title,
|
|
track_count: trackCount,
|
|
like_count: 0,
|
|
user_id: 'user-1',
|
|
description: '',
|
|
is_public: true,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
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 q = url.searchParams.get('q') ?? url.searchParams.get('query') ?? '';
|
|
const page = parseInt(url.searchParams.get('page') ?? '1', 10);
|
|
const limit = parseInt(url.searchParams.get('limit') ?? '20', 10);
|
|
const playlists =
|
|
q === 'NonExistentPlaylist'
|
|
? []
|
|
: [
|
|
{ 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' },
|
|
];
|
|
const total = playlists.length;
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlists,
|
|
total,
|
|
page,
|
|
limit,
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/playlists/favoris', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlist: {
|
|
id: 'pl-favoris-1',
|
|
user_id: 'user-1',
|
|
name: 'Favoris',
|
|
title: 'Favoris',
|
|
description: '',
|
|
is_public: false,
|
|
track_count: 0,
|
|
is_default_favorites: true,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/playlists/shared/:token', ({ params }) => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
playlist: {
|
|
id: 'pl-shared-1',
|
|
user_id: 'user-1',
|
|
name: 'Shared Playlist',
|
|
title: 'Shared Playlist',
|
|
description: 'A shared playlist',
|
|
is_public: true,
|
|
track_count: 2,
|
|
tracks: [
|
|
{
|
|
id: 'pt-1',
|
|
playlist_id: 'pl-shared-1',
|
|
track_id: 'tr-1',
|
|
position: 1,
|
|
added_at: new Date().toISOString(),
|
|
track: { id: 'tr-1', title: 'Track One', duration_ms: 180000 },
|
|
},
|
|
{
|
|
id: 'pt-2',
|
|
playlist_id: 'pl-shared-1',
|
|
track_id: 'tr-2',
|
|
position: 2,
|
|
added_at: new Date().toISOString(),
|
|
track: { id: 'tr-2', title: 'Track Two', duration_ms: 240000 },
|
|
},
|
|
],
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
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', () => HttpResponse.json({ success: true })),
|
|
|
|
http.post('*/api/v1/playlists/:id/tracks', () => HttpResponse.json({ success: true, data: {} })),
|
|
|
|
http.delete('*/api/v1/playlists/:id/tracks/:trackId', () => HttpResponse.json({ success: true })),
|
|
|
|
http.post('*/api/v1/playlists/:id/duplicate', async ({ params, request }) => {
|
|
const body = (await request.json()) as { new_title?: string; new_description?: string; is_public?: boolean };
|
|
const newTitle = body?.new_title ?? `Playlist (copie)`;
|
|
return HttpResponse.json({
|
|
success: true,
|
|
message: 'playlist duplicated successfully',
|
|
playlist: {
|
|
id: `pl-dup-${params.id}`,
|
|
name: newTitle,
|
|
title: newTitle,
|
|
track_count: 0,
|
|
like_count: 0,
|
|
user_id: 'user-1',
|
|
description: body?.new_description ?? '',
|
|
is_public: body?.is_public ?? true,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
});
|
|
}),
|
|
|
|
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: [],
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/playlists/:id/export/:format', ({ params }) => {
|
|
const format = params.format as string;
|
|
if (format === 'json') {
|
|
return new HttpResponse(
|
|
JSON.stringify({
|
|
playlist: { id: params.id, title: 'Mock Playlist', tracks: [] },
|
|
}),
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Content-Disposition': `attachment; filename="playlist_${params.id}.json"`,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
if (format === 'csv') {
|
|
return new HttpResponse('title,artist\nTrack 1,Artist 1\n', {
|
|
headers: {
|
|
'Content-Type': 'text/csv',
|
|
'Content-Disposition': `attachment; filename="playlist_${params.id}.csv"`,
|
|
},
|
|
});
|
|
}
|
|
return HttpResponse.json({ error: 'Invalid format' }, { status: 400 });
|
|
}),
|
|
|
|
http.get('*/api/v1/playlists/:id/analytics', () => {
|
|
return HttpResponse.json({
|
|
success: true,
|
|
data: {
|
|
stats: {
|
|
plays: 120,
|
|
shares: 5,
|
|
likes: 42,
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
];
|