Files
harheimertc/server/api/hero-images.get.js
Torsten Schulz (local) bf1caefde4
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 7m31s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m1s
feat: update security headers and improve content security policy; enhance hero image component and loading states in public news
2026-05-31 14:19:15 +02:00

103 lines
2.6 KiB
JavaScript

import { promises as fs } from 'fs'
import path from 'path'
const HERO_ROOT_CANDIDATES = [
path.join(process.cwd(), 'public', 'images', 'hero'),
path.join(process.cwd(), '.output', 'public', 'images', 'hero'),
path.join(process.cwd(), '..', 'public', 'images', 'hero'),
path.join(process.cwd(), '..', '.output', 'public', 'images', 'hero')
]
const FALLBACK_FILE_CANDIDATES = [
'hero_fallback.webp',
'hero_fallback.jpg',
'hero_fallback.jpeg',
'hero_fallback.png'
]
async function findExistingDir(paths) {
for (const candidate of paths) {
try {
const stats = await fs.stat(candidate)
if (stats.isDirectory()) return candidate
} catch {
// ignore
}
}
return null
}
async function fileExists(filePath) {
try {
const stats = await fs.stat(filePath)
return stats.isFile()
} catch {
return false
}
}
function isAllowedVariantKey(key) {
return /^[A-Za-z0-9_-]+$/.test(key)
}
function appendPathSegment(rootDir, segment) {
if (!isAllowedVariantKey(segment)) return null
return `${rootDir}${path.sep}${segment}`
}
async function listHeroVariants(heroRoot) {
const dirEntries = await fs.readdir(heroRoot, { withFileTypes: true })
const variants = []
for (const entry of dirEntries) {
if (!entry.isDirectory()) continue
const key = entry.name
if (!isAllowedVariantKey(key)) continue
const variantDir = appendPathSegment(heroRoot, key)
if (!variantDir) continue
const mobileFile = 'hero_960.webp'
const desktopFile = 'hero_1600.webp'
const mobilePath = `${variantDir}${path.sep}${mobileFile}`
const desktopPath = `${variantDir}${path.sep}${desktopFile}`
if (!mobilePath || !desktopPath) continue
if (!(await fileExists(mobilePath)) || !(await fileExists(desktopPath))) {
continue
}
let fallbackFile = desktopFile
for (const candidate of FALLBACK_FILE_CANDIDATES) {
const fallbackPath = `${variantDir}${path.sep}${candidate}`
if (fallbackPath && await fileExists(fallbackPath)) {
fallbackFile = candidate
break
}
}
variants.push({
key,
mobileWebp: `/images/hero/${key}/${mobileFile}`,
desktopWebp: `/images/hero/${key}/${desktopFile}`,
fallback: `/images/hero/${key}/${fallbackFile}`
})
}
return variants.sort((a, b) => a.key.localeCompare(b.key, 'de'))
}
export default defineEventHandler(async (event) => {
const heroRoot = await findExistingDir(HERO_ROOT_CANDIDATES)
if (!heroRoot) {
return { variants: [] }
}
const variants = await listHeroVariants(heroRoot)
setHeader(event, 'Cache-Control', 'public, max-age=300, stale-while-revalidate=600')
return { variants }
})