portfolio/server/api/admin/files.js

69 lines
1.9 KiB
JavaScript
Raw Normal View History

import { readdirSync, statSync, unlinkSync, existsSync } from 'node:fs'
import { join, resolve } from 'node:path'
const publicDir = './public'
const allowedFolders = ['scores', 'pubs', 'album_art', 'images', 'hdp_images']
function getFolderPath(folder) {
return join(publicDir, folder)
}
function sanitizeFilename(filename) {
return filename.replace(/[^a-zA-Z0-9._-]/g, '_')
}
function isPathInside(base, target) {
const resolvedBase = resolve(base)
const resolvedTarget = resolve(target)
return resolvedTarget.startsWith(resolvedBase)
}
export default defineEventHandler(async (event) => {
requireAuth(event)
const method = event.method
const query = getQuery(event)
const folder = query.folder
if (!folder || !allowedFolders.includes(folder)) {
throw createError({ statusCode: 400, message: 'Invalid folder' })
}
const folderPath = getFolderPath(folder)
if (!existsSync(folderPath)) {
throw createError({ statusCode: 404, message: 'Folder not found' })
}
if (method === 'GET') {
const files = readdirSync(folderPath)
.filter(f => statSync(join(folderPath, f)).isFile())
.map(f => ({
name: f,
size: statSync(join(folderPath, f)).size,
url: `/${folder}/${f}`
}))
return files
}
if (method === 'DELETE') {
const rawFilename = query.file
if (!rawFilename) {
throw createError({ statusCode: 400, message: 'Filename required' })
}
const filename = sanitizeFilename(rawFilename)
const filePath = resolve(join(folderPath, filename))
if (!isPathInside(resolve(folderPath), filePath)) {
throw createError({ statusCode: 403, message: 'Forbidden' })
}
if (existsSync(filePath)) {
unlinkSync(filePath)
return { success: true }
}
throw createError({ statusCode: 404, message: 'File not found' })
}
throw createError({ statusCode: 405, message: 'Method not allowed' })
})