Files
RepoDructor/server/api/music/[filename].get.ts
josedario87 2f878a857a
All checks were successful
build-and-deploy / build (push) Successful in 19s
build-and-deploy / deploy (push) Successful in 3s
Fix music file serving path to match Docker mount point
2025-08-04 14:58:11 -06:00

103 lines
2.9 KiB
TypeScript

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'
})
}
})