import { createPlaylist, getPlaylist, getPlaylistsByUser, getPublicPlaylists, getPlaylistByShareToken, updatePlaylist, deletePlaylist, setPlaylistTracks, addTracksToPlaylist, removeTrackFromPlaylist, generatePlaylistShareToken, removePlaylistShareToken, findUserById, } from "../db"; import { getOrCreateUser, userHasPermission } from "./helpers"; // GET /api/playlists - List user's + shared playlists export function handleListPlaylists(req: Request, server: any): Response { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const myPlaylists = getPlaylistsByUser(user.id); const sharedPlaylists = getPublicPlaylists(user.id); return Response.json({ mine: myPlaylists, shared: sharedPlaylists, }); } // POST /api/playlists - Create new playlist export async function handleCreatePlaylist(req: Request, server: any): Promise { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } if (user.is_guest) { return Response.json({ error: "Guests cannot create playlists" }, { status: 403 }); } let body: { name: string; description?: string }; try { body = await req.json(); } catch { return Response.json({ error: "Invalid JSON" }, { status: 400 }); } if (!body.name?.trim()) { return Response.json({ error: "Name required" }, { status: 400 }); } const playlist = createPlaylist(body.name.trim(), user.id, body.description?.trim() || ""); return Response.json(playlist, { status: 201 }); } // GET /api/playlists/:id - Get playlist details export function handleGetPlaylist(req: Request, server: any, playlistId: string): Response { const { user } = getOrCreateUser(req, server); const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } // Check access: owner, public, or has share token const url = new URL(req.url); const shareToken = url.searchParams.get("token"); if ( playlist.ownerId !== user?.id && !playlist.isPublic && playlist.shareToken !== shareToken ) { return Response.json({ error: "Access denied" }, { status: 403 }); } // Include owner username const owner = findUserById(playlist.ownerId); return Response.json({ ...playlist, ownerName: owner?.username || "Unknown", }); } // PATCH /api/playlists/:id - Update playlist export async function handleUpdatePlaylist(req: Request, server: any, playlistId: string): Promise { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } if (playlist.ownerId !== user.id && !user.is_admin) { return Response.json({ error: "Not your playlist" }, { status: 403 }); } let body: { name?: string; description?: string; isPublic?: boolean }; try { body = await req.json(); } catch { return Response.json({ error: "Invalid JSON" }, { status: 400 }); } updatePlaylist(playlistId, { name: body.name?.trim(), description: body.description?.trim(), isPublic: body.isPublic, }); return Response.json({ ok: true }); } // DELETE /api/playlists/:id - Delete playlist export function handleDeletePlaylist(req: Request, server: any, playlistId: string): Response { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } if (playlist.ownerId !== user.id && !user.is_admin) { return Response.json({ error: "Not your playlist" }, { status: 403 }); } deletePlaylist(playlistId); return Response.json({ ok: true }); } // PATCH /api/playlists/:id/tracks - Modify tracks export async function handleModifyPlaylistTracks(req: Request, server: any, playlistId: string): Promise { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } if (playlist.ownerId !== user.id && !user.is_admin) { return Response.json({ error: "Not your playlist" }, { status: 403 }); } let body: { add?: string[]; remove?: number[]; set?: string[] }; try { body = await req.json(); } catch { return Response.json({ error: "Invalid JSON" }, { status: 400 }); } // If 'set' is provided, replace entire track list if (body.set !== undefined) { setPlaylistTracks(playlistId, body.set); return Response.json({ ok: true }); } // Remove tracks by position (do removes first, in reverse order) if (body.remove?.length) { const positions = [...body.remove].sort((a, b) => b - a); for (const pos of positions) { removeTrackFromPlaylist(playlistId, pos); } } // Add tracks if (body.add?.length) { addTracksToPlaylist(playlistId, body.add); } return Response.json({ ok: true }); } // POST /api/playlists/:id/share - Generate share token export function handleSharePlaylist(req: Request, server: any, playlistId: string): Response { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } if (playlist.ownerId !== user.id && !user.is_admin) { return Response.json({ error: "Not your playlist" }, { status: 403 }); } const token = generatePlaylistShareToken(playlistId); return Response.json({ shareToken: token }); } // DELETE /api/playlists/:id/share - Remove sharing export function handleUnsharePlaylist(req: Request, server: any, playlistId: string): Response { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const playlist = getPlaylist(playlistId); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } if (playlist.ownerId !== user.id && !user.is_admin) { return Response.json({ error: "Not your playlist" }, { status: 403 }); } removePlaylistShareToken(playlistId); return Response.json({ ok: true }); } // GET /api/playlists/shared/:token - Get shared playlist by token export function handleGetSharedPlaylist(req: Request, server: any, token: string): Response { const playlist = getPlaylistByShareToken(token); if (!playlist) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } const owner = findUserById(playlist.ownerId); return Response.json({ ...playlist, ownerName: owner?.username || "Unknown", }); } // POST /api/playlists/shared/:token/copy - Copy shared playlist to own export function handleCopySharedPlaylist(req: Request, server: any, token: string): Response { const { user } = getOrCreateUser(req, server); if (!user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } if (user.is_guest) { return Response.json({ error: "Guests cannot copy playlists" }, { status: 403 }); } const original = getPlaylistByShareToken(token); if (!original) { return Response.json({ error: "Playlist not found" }, { status: 404 }); } const copy = createPlaylist(`${original.name} (Copy)`, user.id, original.description); setPlaylistTracks(copy.id, original.trackIds); return Response.json(copy, { status: 201 }); }