Naturalised env import
This commit is contained in:
@@ -1,30 +1,27 @@
|
|||||||
// src/routes/api/download/+server.js
|
|
||||||
import { redirect, error } from '@sveltejs/kit';
|
import { redirect, error } from '@sveltejs/kit';
|
||||||
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import {
|
import { env } from '$env/dynamic/private';
|
||||||
B2_ENDPOINT, B2_REGION, B2_ACCESS_KEY_ID, B2_SECRET_ACCESS_KEY, B2_BUCKET_NAME
|
|
||||||
} from '$env/dynamic/private';
|
|
||||||
|
|
||||||
const s3 = new S3Client({
|
|
||||||
endpoint: B2_ENDPOINT,
|
|
||||||
region: B2_REGION,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: B2_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: B2_SECRET_ACCESS_KEY
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function GET({ url }) {
|
export async function GET({ url }) {
|
||||||
const id = url.searchParams.get('id');
|
const id = url.searchParams.get('id');
|
||||||
if (!id) throw error(400, 'Missing photo identity ID');
|
if (!id) throw error(400, 'Missing photo identity ID');
|
||||||
|
|
||||||
try {
|
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 fullSizeKey = `images/${id}`;
|
const fullSizeKey = `images/${id}`;
|
||||||
|
|
||||||
// Create a command that forces the browser to treat this as an attachment download
|
// Force the browser to treat this as an attachment download
|
||||||
const command = new GetObjectCommand({
|
const command = new GetObjectCommand({
|
||||||
Bucket: B2_BUCKET_NAME,
|
Bucket: env.B2_BUCKET_NAME,
|
||||||
Key: fullSizeKey,
|
Key: fullSizeKey,
|
||||||
ResponseContentDisposition: `attachment; filename="${id}"`
|
ResponseContentDisposition: `attachment; filename="${id}"`
|
||||||
});
|
});
|
||||||
@@ -32,12 +29,10 @@ export async function GET({ url }) {
|
|||||||
// Generate a quick 60-second valid link
|
// Generate a quick 60-second valid link
|
||||||
const downloadUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
|
const downloadUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
|
||||||
|
|
||||||
// Redirect the user directly to the freshly generated download link
|
// Redirect browser directly to the download stream
|
||||||
throw redirect(307, downloadUrl);
|
throw redirect(307, downloadUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// SvelteKit uses redirect exceptions, so let them pass through cleanly
|
|
||||||
if (err.status === 307) throw err;
|
if (err.status === 307) throw err;
|
||||||
|
|
||||||
console.error('Server download redirection failed:', err);
|
console.error('Server download redirection failed:', err);
|
||||||
throw error(500, 'Could not generate download routing asset.');
|
throw error(500, 'Could not generate download routing asset.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,46 @@
|
|||||||
import { json, error } from '@sveltejs/kit';
|
import { json, error } from '@sveltejs/kit';
|
||||||
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import {
|
import { env } from '$env/dynamic/private';
|
||||||
B2_ENDPOINT, B2_REGION, B2_ACCESS_KEY_ID, B2_SECRET_ACCESS_KEY, B2_BUCKET_NAME
|
|
||||||
} from '$env/dynamic/private';
|
|
||||||
|
|
||||||
const s3 = new S3Client({
|
|
||||||
endpoint: B2_ENDPOINT,
|
|
||||||
region: B2_REGION,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: B2_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: B2_SECRET_ACCESS_KEY
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function GET({ url }) {
|
export async function GET({ url }) {
|
||||||
const nextToken = url.searchParams.get('next') || undefined;
|
const nextToken = url.searchParams.get('next') || undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Fetch a batch of files from the thumbs/ folder
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch a batch of files from the thumbs/ folder
|
||||||
const listCommand = new ListObjectsV2Command({
|
const listCommand = new ListObjectsV2Command({
|
||||||
Bucket: B2_BUCKET_NAME,
|
Bucket: env.B2_BUCKET_NAME,
|
||||||
Prefix: 'thumbs/',
|
Prefix: 'thumbs/',
|
||||||
MaxKeys: 40, // Increased slight headroom since we filter on the server side now
|
MaxKeys: 40,
|
||||||
ContinuationToken: nextToken
|
ContinuationToken: nextToken
|
||||||
});
|
});
|
||||||
|
|
||||||
const listResponse = await s3.send(listCommand);
|
const listResponse = await s3.send(listCommand);
|
||||||
const contents = listResponse.Contents || [];
|
const contents = listResponse.Contents || [];
|
||||||
|
|
||||||
// 2. Strict Filter: Only allow actual .jpg or .jpeg extensions
|
// Strict Filter: Only allow actual .jpg or .jpeg extensions
|
||||||
const imageFiles = contents.filter(file => {
|
const imageFiles = contents.filter(file => {
|
||||||
const lowerKey = file.Key.toLowerCase();
|
const lowerKey = file.Key.toLowerCase();
|
||||||
return lowerKey.endsWith('.jpg') || lowerKey.endsWith('.jpeg');
|
return lowerKey.endsWith('.jpg') || lowerKey.endsWith('.jpeg');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. Generate presigned URLs for validated items
|
// Generate presigned URLs for validated items
|
||||||
const photos = await Promise.all(
|
const photos = await Promise.all(
|
||||||
imageFiles.map(async (file) => {
|
imageFiles.map(async (file) => {
|
||||||
const id = file.Key.replace('thumbs/', '');
|
const id = file.Key.replace('thumbs/', '');
|
||||||
const fullSizeKey = `images/${id}`;
|
const fullSizeKey = `images/${id}`;
|
||||||
|
|
||||||
const thumbCommand = new GetObjectCommand({ Bucket: B2_BUCKET_NAME, Key: file.Key });
|
const thumbCommand = new GetObjectCommand({ Bucket: env.B2_BUCKET_NAME, Key: file.Key });
|
||||||
const fullCommand = new GetObjectCommand({ Bucket: B2_BUCKET_NAME, Key: fullSizeKey });
|
const fullCommand = new GetObjectCommand({ Bucket: env.B2_BUCKET_NAME, Key: fullSizeKey });
|
||||||
|
|
||||||
// 10 minutes = 600 seconds
|
// 10 minutes = 600 seconds
|
||||||
const [thumbUrl, fullUrl] = await Promise.all([
|
const [thumbUrl, fullUrl] = await Promise.all([
|
||||||
@@ -56,7 +54,6 @@ export async function GET({ url }) {
|
|||||||
|
|
||||||
return json({
|
return json({
|
||||||
photos,
|
photos,
|
||||||
// Pass the original continuation token back so pagination structural flow remains intact
|
|
||||||
nextContinuationToken: listResponse.NextContinuationToken || null
|
nextContinuationToken: listResponse.NextContinuationToken || null
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// src/routes/login/+page.server.js
|
|
||||||
import { fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import { GALLERY_PASSWORD } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async ({ request, cookies }) => {
|
default: async ({ request, cookies }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
const password = data.get('password');
|
const password = data.get('password');
|
||||||
|
|
||||||
if (password === GALLERY_PASSWORD) {
|
if (password === env.GALLERY_PASSWORD) {
|
||||||
cookies.set('gallery_session', 'authenticated', {
|
cookies.set('gallery_session', 'authenticated', {
|
||||||
path: '/',
|
path: '/',
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user