Move uploaded images to disk storage, fix Next 16 async params/searchParams
Homepage was rendering 71MB of HTML because every uploaded image (gallery, products, proofs, custom requests, design previews, invoices, receipts) was stored as base64 in Postgres and double-embedded via RSC hydration payloads. Adds src/lib/storage.ts (sharp-based compression, disk storage under public/uploads/) and a new /api/design-uploads endpoint for images added inside the design tool, migrates existing rows, and drops the now-unused base64 columns and the dead ProductImage table. Also fixes a systemic bug from the Next.js 16 upgrade where dynamic pages across the app still destructured params/searchParams synchronously instead of awaiting them as Promises, which was silently breaking filters/params and crashing /products/[slug] outright. Updates README.md and SETUP_AND_BUILD.md to reflect Postgres + Next.js 16 and document the new image storage architecture and backup requirements. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e23f201d3d
commit
396347b982
@@ -3,6 +3,7 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { DEFAULT_PRINT_AREAS } from '@/lib/mockupDefaults';
|
||||
import { saveUploadedFile } from '@/lib/storage';
|
||||
|
||||
function slugify(input: string) {
|
||||
return input
|
||||
@@ -22,15 +23,9 @@ async function uniqueSlug(base: string) {
|
||||
return slug;
|
||||
}
|
||||
|
||||
async function fileToDataUrl(file: File): Promise<string> {
|
||||
const bytes = Buffer.from(await file.arrayBuffer());
|
||||
const mimeType = file.type || 'image/png';
|
||||
return `data:${mimeType};base64,${bytes.toString('base64')}`;
|
||||
}
|
||||
|
||||
// Reads the per-color photo file inputs the ColorPicker renders (named
|
||||
// "colorPhoto__<hexWithoutHash>" for front, "colorPhotoBack__<hexWithoutHash>" for
|
||||
// back) and builds { hex: dataUrl } for whichever colors actually got a file attached.
|
||||
// back) and builds { hex: url } for whichever colors actually got a file attached.
|
||||
async function extractColorPhotos(
|
||||
formData: FormData,
|
||||
colors: string[],
|
||||
@@ -41,7 +36,7 @@ async function extractColorPhotos(
|
||||
for (const hex of colors) {
|
||||
const file = formData.get(`${prefix}${hex.replace('#', '')}`) as File | null;
|
||||
if (file && file.size > 0) {
|
||||
result[hex] = await fileToDataUrl(file);
|
||||
result[hex] = await saveUploadedFile(file, 'products');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -103,8 +98,8 @@ export async function createProduct(formData: FormData) {
|
||||
const weightKgInput = String(formData.get('weightKg') ?? '').trim();
|
||||
const weightGrams = weightKgInput ? Math.round(Number(weightKgInput) * 1000) : null;
|
||||
|
||||
const imageUrl = imageFile && imageFile.size > 0 ? await fileToDataUrl(imageFile) : null;
|
||||
const imageUrlBack = imageBackFile && imageBackFile.size > 0 ? await fileToDataUrl(imageBackFile) : null;
|
||||
const imageUrl = imageFile && imageFile.size > 0 ? await saveUploadedFile(imageFile, 'products') : null;
|
||||
const imageUrlBack = imageBackFile && imageBackFile.size > 0 ? await saveUploadedFile(imageBackFile, 'products') : null;
|
||||
const colorPhotos = await extractColorPhotos(formData, colors, 'front');
|
||||
const colorPhotosBack = await extractColorPhotos(formData, colors, 'back');
|
||||
const saleFields = parseSaleFields(formData);
|
||||
@@ -310,7 +305,7 @@ export async function updateProduct(formData: FormData) {
|
||||
data.imageUrl = null;
|
||||
data.printArea = JSON.stringify(DEFAULT_PRINT_AREAS[mockup] ?? DEFAULT_PRINT_AREAS.tshirt);
|
||||
} else if (imageFile && imageFile.size > 0) {
|
||||
data.imageUrl = await fileToDataUrl(imageFile);
|
||||
data.imageUrl = await saveUploadedFile(imageFile, 'products');
|
||||
data.printArea = JSON.stringify(
|
||||
hasCustomPrintArea
|
||||
? {
|
||||
@@ -341,7 +336,7 @@ export async function updateProduct(formData: FormData) {
|
||||
data.imageUrlBack = null;
|
||||
data.printAreaBack = null;
|
||||
} else if (imageBackFile && imageBackFile.size > 0) {
|
||||
data.imageUrlBack = await fileToDataUrl(imageBackFile);
|
||||
data.imageUrlBack = await saveUploadedFile(imageBackFile, 'products');
|
||||
if (hasCustomPrintAreaBack) {
|
||||
data.printAreaBack = JSON.stringify({
|
||||
xPct: Number(formData.get('printXBack') ?? 0.3),
|
||||
|
||||
Reference in New Issue
Block a user