Files
photogallery/src/routes/api/photos/+server.js
2026-06-29 09:42:42 +01:00

65 lines
2.0 KiB
JavaScript

import { json, error } from '@sveltejs/kit';
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { env } from '$env/dynamic/private';
export async function GET({ url }) {
const nextToken = url.searchParams.get('next') || undefined;
try {
const s3 = new S3Client({
endpoint: env.B2_ENDPOINT,
region: env.B2_REGION,
credentials: {
accessKeyId: env.B2_ACCESS_KEY_ID,
secretAccessKey: env.B2_SECRET_ACCESS_KEY
}
});
const tokenDuration = parseInt(env.B2_TOKEN_DURATION, 10) || 600;
// Fetch a batch of files from the thumbs/ folder
const listCommand = new ListObjectsV2Command({
Bucket: env.B2_BUCKET_NAME,
Prefix: 'thumbs/',
MaxKeys: 40,
ContinuationToken: nextToken
});
const listResponse = await s3.send(listCommand);
const contents = listResponse.Contents || [];
// Strict Filter: Only allow actual .jpg or .jpeg extensions
const imageFiles = contents.filter(file => {
const lowerKey = file.Key.toLowerCase();
return lowerKey.endsWith('.jpg') || lowerKey.endsWith('.jpeg');
});
// Generate presigned URLs for validated items
const photos = await Promise.all(
imageFiles.map(async (file) => {
const id = file.Key.replace('thumbs/', '');
const fullSizeKey = `images/${id}`;
const thumbCommand = new GetObjectCommand({ Bucket: env.B2_BUCKET_NAME, Key: file.Key });
const fullCommand = new GetObjectCommand({ Bucket: env.B2_BUCKET_NAME, Key: fullSizeKey });
// 10 minutes = 600 seconds
const [thumbUrl, fullUrl] = await Promise.all([
getSignedUrl(s3, thumbCommand, { expiresIn: tokenDuration }),
getSignedUrl(s3, fullCommand, { expiresIn: tokenDuration })
]);
return { id, thumbUrl, fullUrl };
})
);
return json({
photos,
nextContinuationToken: listResponse.NextContinuationToken || null
});
} catch (err) {
console.error(err);
throw error(500, 'Failed to process images from storage.');
}
}