From b3640e78a12b0d05d938f0b9a87fffb5c1ab0597 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Thu, 29 Jan 2026 15:23:33 +0100 Subject: [PATCH] feat(03-01): add API endpoints for serving images - Add GET /api/images/[id] for original images - Add GET /api/images/[id]/thumbnail for thumbnails - Both endpoints return 404 for missing images - Immutable cache headers for CDN optimization --- src/routes/api/images/[id]/+server.ts | 40 +++++++++++++++++++ .../api/images/[id]/thumbnail/+server.ts | 29 ++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/routes/api/images/[id]/+server.ts create mode 100644 src/routes/api/images/[id]/thumbnail/+server.ts diff --git a/src/routes/api/images/[id]/+server.ts b/src/routes/api/images/[id]/+server.ts new file mode 100644 index 0000000..7284dba --- /dev/null +++ b/src/routes/api/images/[id]/+server.ts @@ -0,0 +1,40 @@ +import { error } from '@sveltejs/kit'; +import { readFile } from 'node:fs/promises'; +import { imageRepository } from '$lib/server/db/repository'; +import { getOriginalPath } from '$lib/server/images/storage'; +import type { RequestHandler } from './$types'; + +const MIME_TYPES: Record = { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + webp: 'image/webp', + heic: 'image/heic', + heif: 'image/heif' +}; + +export const GET: RequestHandler = async ({ params }) => { + const { id } = params; + + const image = imageRepository.getById(id); + if (!image) { + error(404, 'Image not found'); + } + + try { + const path = getOriginalPath(id, image.ext); + const buffer = await readFile(path); + const mimeType = MIME_TYPES[image.ext.toLowerCase()] || 'application/octet-stream'; + + return new Response(buffer, { + headers: { + 'Content-Type': mimeType, + 'Content-Length': buffer.length.toString(), + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }); + } catch { + error(404, 'Image file not found'); + } +}; diff --git a/src/routes/api/images/[id]/thumbnail/+server.ts b/src/routes/api/images/[id]/thumbnail/+server.ts new file mode 100644 index 0000000..7dcc47c --- /dev/null +++ b/src/routes/api/images/[id]/thumbnail/+server.ts @@ -0,0 +1,29 @@ +import { error } from '@sveltejs/kit'; +import { readFile } from 'node:fs/promises'; +import { imageRepository } from '$lib/server/db/repository'; +import { getThumbnailPath } from '$lib/server/images/storage'; +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ params }) => { + const { id } = params; + + const image = imageRepository.getById(id); + if (!image) { + error(404, 'Image not found'); + } + + try { + const path = getThumbnailPath(id); + const buffer = await readFile(path); + + return new Response(buffer, { + headers: { + 'Content-Type': 'image/jpeg', + 'Content-Length': buffer.length.toString(), + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }); + } catch { + error(404, 'Thumbnail not found'); + } +};