blastoise/routes/index.ts

241 lines
7.5 KiB
TypeScript

import { getStatus as getYtdlpStatus } from "../ytdlp";
import { config } from "../config";
import { state } from "../state";
import { getOrCreateUser } from "./helpers";
// Auth routes
import {
handleSignup,
handleLogin,
handleLogout,
handleGetMe,
handleKickOthers,
handleListUsers,
handleGrantPermission,
handleRevokePermission,
} from "./auth";
// Channel routes
import {
handleListChannels,
handleCreateChannel,
handleDeleteChannel,
handleGetChannel,
handleJump,
handleSeek,
handleModifyQueue,
handleSetMode,
handleRenameChannel,
} from "./channels";
// Track routes
import {
handleGetLibrary,
handleUpload,
handleGetTrack,
} from "./tracks";
// Fetch routes (yt-dlp)
import {
handleFetch,
handleFetchConfirm,
handleGetFetchQueue,
handleCancelFetchItem,
} from "./fetch";
// Playlist routes
import {
handleListPlaylists,
handleCreatePlaylist,
handleGetPlaylist,
handleUpdatePlaylist,
handleDeletePlaylist,
handleModifyPlaylistTracks,
handleSharePlaylist,
handleUnsharePlaylist,
handleGetSharedPlaylist,
handleCopySharedPlaylist,
} from "./playlists";
// Static file serving
import { handleStatic } from "./static";
export function createRouter() {
return async function fetch(req: Request, server: any): Promise<Response | undefined> {
const url = new URL(req.url);
const path = url.pathname;
// WebSocket upgrade for channels
if (path.match(/^\/api\/channels\/([^/]+)\/ws$/)) {
const id = path.split("/")[3];
if (!state.channels.has(id)) return new Response("Channel not found", { status: 404 });
const { user } = getOrCreateUser(req, server);
const ok = server.upgrade(req, { data: { channelId: id, userId: user?.id ?? null, username: user?.username ?? 'Guest' } });
if (ok) return undefined;
return new Response("WebSocket upgrade failed", { status: 500 });
}
// API: server status (public)
if (path === "/api/status") {
return Response.json({
name: "MusicRoom",
version: "1.0.0",
allowGuests: config.allowGuests,
allowSignups: true,
channelCount: state.channels.size,
defaultPermissions: config.defaultPermissions,
ytdlp: getYtdlpStatus()
});
}
// Channel routes
if (path === "/api/channels" && req.method === "GET") {
return handleListChannels(req, server);
}
if (path === "/api/channels" && req.method === "POST") {
return handleCreateChannel(req, server);
}
const channelDeleteMatch = path.match(/^\/api\/channels\/([^/]+)$/);
if (channelDeleteMatch && req.method === "DELETE") {
return handleDeleteChannel(req, server, channelDeleteMatch[1]);
}
const channelPatchMatch = path.match(/^\/api\/channels\/([^/]+)$/);
if (channelPatchMatch && req.method === "PATCH") {
return handleRenameChannel(req, server, channelPatchMatch[1]);
}
const channelGetMatch = path.match(/^\/api\/channels\/([^/]+)$/);
if (channelGetMatch && req.method === "GET") {
return handleGetChannel(channelGetMatch[1]);
}
const jumpMatch = path.match(/^\/api\/channels\/([^/]+)\/jump$/);
if (jumpMatch && req.method === "POST") {
return handleJump(req, server, jumpMatch[1]);
}
const seekMatch = path.match(/^\/api\/channels\/([^/]+)\/seek$/);
if (seekMatch && req.method === "POST") {
return handleSeek(req, server, seekMatch[1]);
}
const queueMatch = path.match(/^\/api\/channels\/([^/]+)\/queue$/);
if (queueMatch && req.method === "PATCH") {
return handleModifyQueue(req, server, queueMatch[1]);
}
const modeMatch = path.match(/^\/api\/channels\/([^/]+)\/mode$/);
if (modeMatch && req.method === "POST") {
return handleSetMode(req, server, modeMatch[1]);
}
// Library routes
if (path === "/api/library") {
return handleGetLibrary(req, server);
}
if (path === "/api/upload" && req.method === "POST") {
return handleUpload(req, server);
}
// Fetch routes (yt-dlp)
if (path === "/api/fetch" && req.method === "POST") {
return handleFetch(req, server);
}
if (path === "/api/fetch/confirm" && req.method === "POST") {
return handleFetchConfirm(req, server);
}
if (path === "/api/fetch" && req.method === "GET") {
return handleGetFetchQueue(req, server);
}
const fetchCancelMatch = path.match(/^\/api\/fetch\/([^/]+)$/);
if (fetchCancelMatch && req.method === "DELETE") {
return handleCancelFetchItem(req, server, fetchCancelMatch[1]);
}
// Playlist routes
if (path === "/api/playlists" && req.method === "GET") {
return handleListPlaylists(req, server);
}
if (path === "/api/playlists" && req.method === "POST") {
return handleCreatePlaylist(req, server);
}
const sharedPlaylistMatch = path.match(/^\/api\/playlists\/shared\/([^/]+)$/);
if (sharedPlaylistMatch && req.method === "GET") {
return handleGetSharedPlaylist(req, server, sharedPlaylistMatch[1]);
}
if (sharedPlaylistMatch && req.method === "POST") {
return handleCopySharedPlaylist(req, server, sharedPlaylistMatch[1]);
}
const playlistMatch = path.match(/^\/api\/playlists\/([^/]+)$/);
if (playlistMatch && req.method === "GET") {
return handleGetPlaylist(req, server, playlistMatch[1]);
}
if (playlistMatch && req.method === "PATCH") {
return handleUpdatePlaylist(req, server, playlistMatch[1]);
}
if (playlistMatch && req.method === "DELETE") {
return handleDeletePlaylist(req, server, playlistMatch[1]);
}
const playlistTracksMatch = path.match(/^\/api\/playlists\/([^/]+)\/tracks$/);
if (playlistTracksMatch && req.method === "PATCH") {
return handleModifyPlaylistTracks(req, server, playlistTracksMatch[1]);
}
const playlistShareMatch = path.match(/^\/api\/playlists\/([^/]+)\/share$/);
if (playlistShareMatch && req.method === "POST") {
return handleSharePlaylist(req, server, playlistShareMatch[1]);
}
if (playlistShareMatch && req.method === "DELETE") {
return handleUnsharePlaylist(req, server, playlistShareMatch[1]);
}
// Auth routes
if (path === "/api/auth/signup" && req.method === "POST") {
return handleSignup(req, server);
}
if (path === "/api/auth/login" && req.method === "POST") {
return handleLogin(req, server);
}
if (path === "/api/auth/logout" && req.method === "POST") {
return handleLogout(req);
}
if (path === "/api/auth/me") {
return handleGetMe(req, server);
}
if (path === "/api/auth/kick-others" && req.method === "POST") {
return handleKickOthers(req, server);
}
// Admin routes
if (path === "/api/admin/users" && req.method === "GET") {
return handleListUsers(req, server);
}
const permissionsMatch = path.match(/^\/api\/admin\/users\/(\d+)\/permissions$/);
if (permissionsMatch && req.method === "POST") {
return handleGrantPermission(req, server, parseInt(permissionsMatch[1]));
}
if (permissionsMatch && req.method === "DELETE") {
return handleRevokePermission(req, server, parseInt(permissionsMatch[1]));
}
// Track serving (must be after other /api/tracks routes)
const trackMatch = path.match(/^\/api\/tracks\/(.+)$/);
if (trackMatch) {
return handleGetTrack(req, server, decodeURIComponent(trackMatch[1]));
}
// Static files
const staticResponse = await handleStatic(path);
if (staticResponse) return staticResponse;
return new Response("Not found", { status: 404 });
};
}