feat: add FilesBlock interface and component integration
- Introduced FilesBlockItem and FilesBlockData interfaces to support file handling in the CMS. - Integrated FilesBlock component into ContentRows.svelte for rendering file blocks. - Updated block type handling to include support for files, enhancing content versatility.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import {
|
||||
buildFileCacheKey,
|
||||
cacheFile,
|
||||
contentTypeFromExt,
|
||||
extFromSrc,
|
||||
getCachedFile,
|
||||
} from '$lib/file-cache.server';
|
||||
|
||||
const IMMUTABLE_HEADERS = {
|
||||
'cache-control': 'public, max-age=31536000, immutable',
|
||||
} as const;
|
||||
|
||||
function getCmsBaseUrl(): string {
|
||||
return (
|
||||
env.PUBLIC_CMS_URL ||
|
||||
import.meta.env.PUBLIC_CMS_URL ||
|
||||
'http://localhost:3000'
|
||||
).replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function resolveSourceUrl(src: string): string {
|
||||
if (src.startsWith('http://') || src.startsWith('https://')) return src;
|
||||
if (src.startsWith('//')) return `https:${src}`;
|
||||
if (src.startsWith('/')) return `${getCmsBaseUrl()}${src}`;
|
||||
return `${getCmsBaseUrl()}/api/assets/${encodeURIComponent(src)}`;
|
||||
}
|
||||
|
||||
function buildResponseHeaders(
|
||||
contentType: string,
|
||||
download: string | null,
|
||||
filename?: string,
|
||||
): HeadersInit {
|
||||
const headers: Record<string, string> = {
|
||||
'content-type': contentType,
|
||||
...IMMUTABLE_HEADERS,
|
||||
};
|
||||
if (download === '1' || download === 'true') {
|
||||
const safe = (filename ?? 'download').replace(/"/g, '');
|
||||
headers['content-disposition'] = `attachment; filename="${safe}"`;
|
||||
} else if (filename) {
|
||||
const safe = filename.replace(/"/g, '');
|
||||
headers['content-disposition'] = `inline; filename="${safe}"`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const src = url.searchParams.get('src');
|
||||
if (!src) {
|
||||
return new Response('Missing "src" parameter', { status: 400 });
|
||||
}
|
||||
const download = url.searchParams.get('dl');
|
||||
const filenameParam = url.searchParams.get('name') ?? undefined;
|
||||
const inferredName = (() => {
|
||||
try {
|
||||
const path = src.split('?')[0].split('#')[0];
|
||||
const last = path.split('/').pop();
|
||||
return last ? decodeURIComponent(last) : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
const filename = filenameParam ?? inferredName;
|
||||
|
||||
const key = buildFileCacheKey(src);
|
||||
|
||||
const cached = await getCachedFile(key);
|
||||
if (cached) {
|
||||
return new Response(new Uint8Array(cached.buffer), {
|
||||
headers: buildResponseHeaders(cached.contentType, download, filename),
|
||||
});
|
||||
}
|
||||
|
||||
const sourceUrl = resolveSourceUrl(src);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(sourceUrl);
|
||||
} catch (err) {
|
||||
console.error('[cms-files] CMS fetch failed:', err);
|
||||
return new Response('CMS not reachable', { status: 502 });
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
return new Response('File fetch failed', { status: res.status });
|
||||
}
|
||||
|
||||
const buffer = await res.arrayBuffer();
|
||||
const upstreamCt = res.headers.get('content-type') ?? '';
|
||||
const ext = extFromSrc(src);
|
||||
const contentType = upstreamCt && !upstreamCt.startsWith('application/octet-stream')
|
||||
? upstreamCt
|
||||
: ext
|
||||
? contentTypeFromExt(ext)
|
||||
: upstreamCt || 'application/octet-stream';
|
||||
|
||||
const result = await cacheFile(key, buffer, contentType, src);
|
||||
|
||||
return new Response(new Uint8Array(result.buffer), {
|
||||
headers: buildResponseHeaders(result.contentType, download, filename),
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user