Files
radio1/server/routes.ts
2025-11-21 16:06:20 +03:00

298 lines
9.5 KiB
TypeScript

import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import {
insertPlaylistSchema,
insertTrackSchema,
insertPlaylistTrackSchema,
insertSettingsSchema
} from "@shared/schema";
import { createNavidromeService } from "./navidrome";
import { generateToken, verifyToken, extractTokenFromHeader } from "./auth";
// Middleware to check authentication
function requireAuth(req: any, res: any, next: any) {
const token = extractTokenFromHeader(req.headers.authorization);
if (!token || !verifyToken(token)) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
export async function registerRoutes(app: Express): Promise<Server> {
// Auth Routes
app.post("/api/auth/login", async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: "Missing credentials" });
}
// For demo: accept any non-empty username/password
// In production, verify against actual user database
const token = generateToken(username);
res.json({ token, username });
} catch (error) {
console.error("Login error:", error);
res.status(500).json({ error: "Login failed" });
}
});
// Playlist Routes (protected)
app.get("/api/playlists", requireAuth, async (req, res) => {
try {
const playlists = await storage.getAllPlaylists();
res.json(playlists);
} catch (error) {
console.error("Error fetching playlists:", error);
res.status(500).json({ error: "Failed to fetch playlists" });
}
});
app.get("/api/playlists/:id", requireAuth, async (req, res) => {
try {
const id = parseInt(req.params.id);
const playlist = await storage.getPlaylist(id);
if (!playlist) {
return res.status(404).json({ error: "Playlist not found" });
}
res.json(playlist);
} catch (error) {
console.error("Error fetching playlist:", error);
res.status(500).json({ error: "Failed to fetch playlist" });
}
});
app.post("/api/playlists", requireAuth, async (req, res) => {
try {
const validatedData = insertPlaylistSchema.parse(req.body);
const playlist = await storage.createPlaylist(validatedData);
res.status(201).json(playlist);
} catch (error) {
console.error("Error creating playlist:", error);
res.status(400).json({ error: "Invalid playlist data" });
}
});
app.patch("/api/playlists/:id", requireAuth, async (req, res) => {
try {
const id = parseInt(req.params.id);
const playlist = await storage.updatePlaylist(id, req.body);
if (!playlist) {
return res.status(404).json({ error: "Playlist not found" });
}
res.json(playlist);
} catch (error) {
console.error("Error updating playlist:", error);
res.status(500).json({ error: "Failed to update playlist" });
}
});
app.delete("/api/playlists/:id", requireAuth, async (req, res) => {
try {
const id = parseInt(req.params.id);
await storage.deletePlaylist(id);
res.status(204).send();
} catch (error) {
console.error("Error deleting playlist:", error);
res.status(500).json({ error: "Failed to delete playlist" });
}
});
// Playlist Tracks Routes (protected)
app.get("/api/playlists/:id/tracks", requireAuth, async (req, res) => {
try {
const playlistId = parseInt(req.params.id);
const tracks = await storage.getPlaylistTracks(playlistId);
res.json(tracks);
} catch (error) {
console.error("Error fetching playlist tracks:", error);
res.status(500).json({ error: "Failed to fetch playlist tracks" });
}
});
app.post("/api/playlists/:id/tracks", requireAuth, async (req, res) => {
try {
const playlistId = parseInt(req.params.id);
const { trackId, position } = req.body;
const validatedData = insertPlaylistTrackSchema.parse({
playlistId,
trackId,
position: position || 0,
});
const playlistTrack = await storage.addTrackToPlaylist(validatedData);
res.status(201).json(playlistTrack);
} catch (error) {
console.error("Error adding track to playlist:", error);
res.status(400).json({ error: "Failed to add track to playlist" });
}
});
app.delete("/api/playlists/:playlistId/tracks/:trackId", requireAuth, async (req, res) => {
try {
const playlistId = parseInt(req.params.playlistId);
const trackId = parseInt(req.params.trackId);
await storage.removeTrackFromPlaylist(playlistId, trackId);
res.status(204).send();
} catch (error) {
console.error("Error removing track from playlist:", error);
res.status(500).json({ error: "Failed to remove track from playlist" });
}
});
// Track Routes (protected)
app.get("/api/tracks", requireAuth, async (req, res) => {
try {
const tracks = await storage.getAllTracks();
res.json(tracks);
} catch (error) {
console.error("Error fetching tracks:", error);
res.status(500).json({ error: "Failed to fetch tracks" });
}
});
app.post("/api/tracks", requireAuth, async (req, res) => {
try {
const validatedData = insertTrackSchema.parse(req.body);
const track = await storage.createTrack(validatedData);
res.status(201).json(track);
} catch (error) {
console.error("Error creating track:", error);
res.status(400).json({ error: "Invalid track data" });
}
});
app.delete("/api/tracks/:id", requireAuth, async (req, res) => {
try {
const id = parseInt(req.params.id);
await storage.deleteTrack(id);
res.status(204).send();
} catch (error) {
console.error("Error deleting track:", error);
res.status(500).json({ error: "Failed to delete track" });
}
});
// Settings Routes (protected)
app.get("/api/settings", requireAuth, async (req, res) => {
try {
const settings = await storage.getSettings();
res.json(settings || {});
} catch (error) {
console.error("Error fetching settings:", error);
res.status(500).json({ error: "Failed to fetch settings" });
}
});
app.post("/api/settings", requireAuth, async (req, res) => {
try {
const validatedData = insertSettingsSchema.parse(req.body);
const settings = await storage.updateSettings(validatedData);
res.json(settings);
} catch (error) {
console.error("Error updating settings:", error);
res.status(400).json({ error: "Invalid settings data" });
}
});
// Navidrome Integration Routes (protected)
app.post("/api/navidrome/test", requireAuth, async (req, res) => {
try {
const { navidromeUrl, navidromeUsername, navidromeToken } = req.body;
const service = await createNavidromeService({
url: navidromeUrl,
username: navidromeUsername,
token: navidromeToken,
});
if (service) {
res.json({ success: true, message: "Connected to Navidrome successfully" });
} else {
res.status(400).json({ success: false, message: "Failed to connect to Navidrome" });
}
} catch (error) {
console.error("Error testing Navidrome connection:", error);
res.status(500).json({ success: false, message: "Connection test failed" });
}
});
app.post("/api/navidrome/search", requireAuth, async (req, res) => {
try {
const settings = await storage.getSettings();
if (!settings?.navidromeUrl || !settings?.navidromeUsername || !settings?.navidromeToken) {
return res.status(400).json({ error: "Navidrome not configured" });
}
const service = await createNavidromeService({
url: settings.navidromeUrl,
username: settings.navidromeUsername,
token: settings.navidromeToken,
});
if (!service) {
return res.status(500).json({ error: "Failed to connect to Navidrome" });
}
const { query } = req.body;
const tracks = await service.searchTracks(query);
res.json(tracks);
} catch (error) {
console.error("Error searching Navidrome:", error);
res.status(500).json({ error: "Search failed" });
}
});
app.post("/api/navidrome/import-artist", requireAuth, async (req, res) => {
try {
const settings = await storage.getSettings();
if (!settings?.navidromeUrl || !settings?.navidromeUsername || !settings?.navidromeToken) {
return res.status(400).json({ error: "Navidrome not configured" });
}
const service = await createNavidromeService({
url: settings.navidromeUrl,
username: settings.navidromeUsername,
token: settings.navidromeToken,
});
if (!service) {
return res.status(500).json({ error: "Failed to connect to Navidrome" });
}
const { artist } = req.body;
const navTracks = await service.getArtistTracks(artist);
const importedTracks = [];
for (const trackData of navTracks) {
const track = await storage.createTrack(trackData);
importedTracks.push(track);
}
res.json({
success: true,
count: importedTracks.length,
tracks: importedTracks
});
} catch (error) {
console.error("Error importing artist tracks:", error);
res.status(500).json({ error: "Import failed" });
}
});
const httpServer = createServer(app);
return httpServer;
}