271 lines
8.1 KiB
TypeScript
271 lines
8.1 KiB
TypeScript
import {
|
|
createPlaylist,
|
|
getPlaylist,
|
|
getPlaylistsByUser,
|
|
getPublicPlaylists,
|
|
getPlaylistByShareToken,
|
|
updatePlaylist,
|
|
deletePlaylist,
|
|
setPlaylistTracks,
|
|
addTracksToPlaylist,
|
|
removeTrackFromPlaylist,
|
|
removeTracksFromPlaylist,
|
|
movePlaylistTracks,
|
|
insertTracksToPlaylistAt,
|
|
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<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 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<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 });
|
|
}
|
|
|
|
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<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 });
|
|
}
|
|
|
|
let body: { add?: string[]; remove?: number[]; set?: string[]; move?: number[]; to?: number; insertAt?: number };
|
|
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 });
|
|
}
|
|
|
|
// Move/reorder tracks
|
|
if (body.move?.length && typeof body.to === "number") {
|
|
movePlaylistTracks(playlistId, body.move, body.to);
|
|
return Response.json({ ok: true });
|
|
}
|
|
|
|
// Remove tracks by position (bulk operation)
|
|
if (body.remove?.length) {
|
|
removeTracksFromPlaylist(playlistId, body.remove);
|
|
}
|
|
|
|
// Add tracks (at specific position or at end)
|
|
if (body.add?.length) {
|
|
if (typeof body.insertAt === "number") {
|
|
insertTracksToPlaylistAt(playlistId, body.add, body.insertAt);
|
|
} else {
|
|
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 });
|
|
}
|