From 2f878a857a3408ae9324d07a729babdf6b776559 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Mon, 4 Aug 2025 14:58:11 -0600 Subject: [PATCH] Fix music file serving path to match Docker mount point --- server/api/music/[filename].get.ts | 103 +++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 server/api/music/[filename].get.ts diff --git a/server/api/music/[filename].get.ts b/server/api/music/[filename].get.ts new file mode 100644 index 0000000..094110a --- /dev/null +++ b/server/api/music/[filename].get.ts @@ -0,0 +1,103 @@ +import { promises as fs } from 'fs' +import { join } from 'path' +import { createReadStream } from 'fs' + +export default defineEventHandler(async (event) => { + let filename = getRouterParam(event, 'filename') + + if (!filename) { + throw createError({ + statusCode: 400, + statusMessage: 'Filename is required' + }) + } + + // Log incoming request for debugging proxy issues + const headers = getHeaders(event) + const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown' + console.log(`[MUSIC API] Request from ${realIP} for file: ${filename}`) + + // Decode the filename + try { + filename = decodeURIComponent(filename) + console.log('Decoded filename:', filename) + } catch (error) { + console.error('Error decoding filename:', error) + // If decoding fails, use original filename + } + + try { + const musicDir = join(process.cwd(), 'public', 'music') + const filePath = join(musicDir, filename) + + // Security check: ensure the file is within the music directory + if (!filePath.startsWith(musicDir)) { + throw createError({ + statusCode: 403, + statusMessage: 'Access denied' + }) + } + + // Check if file exists + try { + await fs.access(filePath) + } catch { + throw createError({ + statusCode: 404, + statusMessage: 'File not found' + }) + } + + // Get file stats + const stats = await fs.stat(filePath) + + // Set appropriate headers + const headers = getHeaders(event) + + // Handle range requests for audio streaming + const range = headers.range + + if (range) { + const parts = range.replace(/bytes=/, "").split("-") + const start = parseInt(parts[0], 10) + const end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1 + const chunksize = (end - start) + 1 + + setHeader(event, 'Content-Range', `bytes ${start}-${end}/${stats.size}`) + setHeader(event, 'Accept-Ranges', 'bytes') + setHeader(event, 'Content-Length', chunksize.toString()) + setResponseStatus(event, 206) + + return sendStream(event, createReadStream(filePath, { start, end })) + } else { + // Send entire file + setHeader(event, 'Content-Length', stats.size.toString()) + setHeader(event, 'Accept-Ranges', 'bytes') + + // Set content type based on file extension + const ext = filename.toLowerCase().split('.').pop() + const contentTypes = { + 'mp3': 'audio/mpeg', + 'wav': 'audio/wav', + 'flac': 'audio/flac', + 'm4a': 'audio/mp4', + 'ogg': 'audio/ogg', + 'aac': 'audio/aac' + } + + setHeader(event, 'Content-Type', contentTypes[ext] || 'audio/mpeg') + + return sendStream(event, createReadStream(filePath)) + } + } catch (error) { + if (error.statusCode) { + throw error + } + + console.error('Error serving music file:', error) + throw createError({ + statusCode: 500, + statusMessage: 'Failed to serve music file' + }) + } +}) \ No newline at end of file