73 lines
2.5 KiB
JavaScript
73 lines
2.5 KiB
JavaScript
// server/src/routes/uploads-resized.js
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { findOriginalFile, getOrCreateResized, SUPPORTED_FORMATS, VALID_WIDTHS } from '../lib/image-resize.js'
|
|
|
|
const CACHE_CONTROL_IMMUTABLE = 'public, max-age=31536000, immutable'
|
|
const CACHE_CONTROL_SHORT = 'public, max-age=86400'
|
|
|
|
/**
|
|
* Register GET /uploads-resized/* route for on-demand image resizing.
|
|
* Must be registered BEFORE fastify-static for /uploads/.
|
|
*/
|
|
export function registerUploadsResized(fastify) {
|
|
fastify.get('/uploads-resized/*', async (request, reply) => {
|
|
const rawPath = request.params['*']
|
|
const url = new URL(request.url, 'http://localhost')
|
|
const widthParam = url.searchParams.get('w')
|
|
|
|
// Parse: [subdir/]filename.format
|
|
const parts = rawPath.split('/')
|
|
let filename, subdir = ''
|
|
|
|
if (parts.length > 1) {
|
|
subdir = parts.slice(0, -1).join('/') + '/'
|
|
filename = parts[parts.length - 1]
|
|
} else {
|
|
filename = parts[0]
|
|
}
|
|
|
|
const dotIdx = filename.lastIndexOf('.')
|
|
if (dotIdx === -1) {
|
|
return reply.code(400).send({ error: 'Invalid request: no format specified' })
|
|
}
|
|
|
|
const uuid = filename.slice(0, dotIdx)
|
|
const format = filename.slice(dotIdx + 1).toLowerCase()
|
|
|
|
if (!SUPPORTED_FORMATS.has(format)) {
|
|
return reply.code(400).send({ error: `Unsupported format: ${format}. Use avif or webp.` })
|
|
}
|
|
|
|
// Validate width
|
|
let width = null
|
|
if (widthParam) {
|
|
const w = parseInt(widthParam, 10)
|
|
if (!VALID_WIDTHS.includes(w)) {
|
|
return reply.code(400).send({ error: `Invalid width: ${widthParam}. Use: ${VALID_WIDTHS.join(', ')}` })
|
|
}
|
|
width = w
|
|
}
|
|
|
|
// If no width requested, serve original with short cache
|
|
if (!width) {
|
|
const originalPath = await findOriginalFile(uuid, subdir || undefined)
|
|
if (!originalPath) {
|
|
return reply.code(404).send({ error: 'Image not found' })
|
|
}
|
|
reply.header('Cache-Control', CACHE_CONTROL_SHORT)
|
|
reply.header('Content-Type', format === 'avif' ? 'image/avif' : 'image/webp')
|
|
return reply.send(fs.createReadStream(originalPath))
|
|
}
|
|
|
|
const result = await getOrCreateResized(uuid, width, format, subdir || undefined)
|
|
if (!result) {
|
|
return reply.code(404).send({ error: 'Image not found' })
|
|
}
|
|
|
|
reply.header('Cache-Control', CACHE_CONTROL_IMMUTABLE)
|
|
reply.header('Content-Type', format === 'avif' ? 'image/avif' : 'image/webp')
|
|
return reply.send(fs.createReadStream(result.path))
|
|
})
|
|
}
|