feat(03-02): add uploadImage form action with thumbnail generation
- Add uploadImage action that handles multipart file uploads - Validate file type and entry existence before processing - Generate thumbnail using Sharp with EXIF rotation - Save original and thumbnail to filesystem - Store image metadata in database - Return entries with their images attached from load function
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { entryRepository } from '$lib/server/db/repository';
|
||||
import { entryRepository, imageRepository } from '$lib/server/db/repository';
|
||||
import { saveOriginal, saveThumbnail, ensureDirectories } from '$lib/server/images/storage';
|
||||
import { generateThumbnail } from '$lib/server/images/thumbnails';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const showCompleted = url.searchParams.get('showCompleted') === 'true';
|
||||
const entries = entryRepository.getOrdered({ showCompleted });
|
||||
|
||||
// Attach images to each entry
|
||||
const entriesWithImages = entries.map((entry) => ({
|
||||
...entry,
|
||||
images: imageRepository.getByEntryId(entry.id)
|
||||
}));
|
||||
|
||||
return {
|
||||
entries,
|
||||
entries: entriesWithImages,
|
||||
showCompleted
|
||||
};
|
||||
};
|
||||
@@ -127,5 +135,58 @@ export const actions: Actions = {
|
||||
entryRepository.update(id, { status: newStatus });
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
uploadImage: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('image') as File;
|
||||
const entryId = formData.get('entryId') as string;
|
||||
|
||||
if (!file || file.size === 0) {
|
||||
return fail(400, { error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
if (!entryId) {
|
||||
return fail(400, { error: 'Entry ID required' });
|
||||
}
|
||||
|
||||
// Verify entry exists
|
||||
const entry = entryRepository.getById(entryId);
|
||||
if (!entry) {
|
||||
return fail(404, { error: 'Entry not found' });
|
||||
}
|
||||
|
||||
// Validate image type
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return fail(400, { error: 'File must be an image' });
|
||||
}
|
||||
|
||||
const ext = file.name.split('.').pop()?.toLowerCase() || 'jpg';
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
try {
|
||||
await ensureDirectories();
|
||||
|
||||
// Generate and save thumbnail
|
||||
const thumbnailBuffer = await generateThumbnail(buffer);
|
||||
|
||||
// Save to database to get ID
|
||||
const image = imageRepository.create({
|
||||
entryId,
|
||||
filename: file.name,
|
||||
ext
|
||||
});
|
||||
|
||||
// Save original
|
||||
await saveOriginal(image.id, ext, buffer);
|
||||
|
||||
// Save thumbnail
|
||||
await saveThumbnail(image.id, thumbnailBuffer);
|
||||
|
||||
return { success: true, imageId: image.id };
|
||||
} catch (err) {
|
||||
console.error('Upload error:', err);
|
||||
return fail(500, { error: 'Failed to save image' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user