image optimization. Image resizing client side, index added to db, pagination

This commit is contained in:
jackiettran
2025-12-30 20:23:32 -05:00
parent 3e31b9d08b
commit 807082eebf
25 changed files with 587 additions and 123 deletions

View File

@@ -1,4 +1,9 @@
import api from "./api";
import {
resizeImage,
getVariantKey,
getSizeSuffix,
} from "../utils/imageResizer";
/**
* Get the public URL for an image (S3 only)
@@ -151,39 +156,6 @@ export async function uploadFile(
return { key: presigned.key, publicUrl: presigned.publicUrl };
}
/**
* Upload multiple files to S3 (complete flow)
*/
export async function uploadFiles(
uploadType: UploadType,
files: File[],
options: UploadOptions = {}
): Promise<{ key: string; publicUrl: string }[]> {
if (files.length === 0) return [];
// Get presigned URLs for all files
const presignedUrls = await getPresignedUrls(uploadType, files);
// Upload all files in parallel
await Promise.all(
files.map((file, i) =>
uploadToS3(file, presignedUrls[i].uploadUrl, options)
)
);
// Confirm all uploads
const keys = presignedUrls.map((p) => p.key);
const { confirmed, total } = await confirmUploads(keys);
if (confirmed.length < total) {
console.warn(`${total - confirmed.length} uploads failed verification`);
}
return presignedUrls
.filter((p) => confirmed.includes(p.key))
.map((p) => ({ key: p.key, publicUrl: p.publicUrl }));
}
/**
* Get a signed URL for accessing private content (messages, condition-checks)
*/
@@ -193,3 +165,119 @@ export async function getSignedUrl(key: string): Promise<string> {
);
return response.data.url;
}
/**
* Get a signed URL for a specific image size variant (private content)
* Backend will validate ownership using the base key
*/
export async function getSignedImageUrl(
baseKey: string,
size: "thumbnail" | "medium" | "original" = "original"
): Promise<string> {
const suffix = getSizeSuffix(size);
const variantKey = getVariantKey(baseKey, suffix);
return getSignedUrl(variantKey);
}
/**
* Get URL for a specific image size variant
* Falls back to original if variant doesn't exist (backward compatibility)
*/
export function getImageUrl(
baseKey: string | null | undefined,
size: "thumbnail" | "medium" | "original" = "original"
): string {
if (!baseKey) return "";
const suffix = getSizeSuffix(size);
const variantKey = getVariantKey(baseKey, suffix);
return getPublicImageUrl(variantKey);
}
export interface UploadWithResizeOptions extends UploadOptions {
skipResize?: boolean;
}
/**
* Upload a single image with all size variants (thumbnail, medium, original)
* Returns the base key (original, without suffix) for database storage
*/
export async function uploadImageWithVariants(
uploadType: UploadType,
file: File,
options: UploadWithResizeOptions = {}
): Promise<{ baseKey: string; publicUrl: string; variants: string[] }> {
const { onProgress, skipResize } = options;
// If skipping resize, use regular upload
if (skipResize) {
const result = await uploadFile(uploadType, file, { onProgress });
return { baseKey: result.key, publicUrl: result.publicUrl, variants: [result.key] };
}
// Generate resized variants
const resizedImages = await resizeImage(file);
if (resizedImages.length === 0) {
throw new Error("Failed to resize image");
}
// Get presigned URLs for all variants
const files = resizedImages.map((r) => r.file);
const presignedUrls = await getPresignedUrls(uploadType, files);
// Upload all variants in parallel with combined progress
const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
let uploadedBytes = 0;
await Promise.all(
files.map((variantFile, i) =>
uploadToS3(variantFile, presignedUrls[i].uploadUrl, {
onProgress: (percent) => {
if (onProgress) {
const fileContribution = (variantFile.size / totalBytes) * percent;
// Approximate combined progress
onProgress(Math.min(99, Math.round(uploadedBytes / totalBytes * 100 + fileContribution)));
}
},
}).then(() => {
uploadedBytes += files[i].size;
})
)
);
// Confirm all uploads
const keys = presignedUrls.map((p) => p.key);
await confirmUploads(keys);
// Find the original variant key (no suffix) for database storage
const originalIdx = resizedImages.findIndex((r) => r.variant.size === "original");
const baseKey = presignedUrls[originalIdx]?.key || presignedUrls[0].key;
if (onProgress) onProgress(100);
return {
baseKey,
publicUrl: getPublicImageUrl(baseKey),
variants: keys,
};
}
/**
* Upload multiple images with all size variants
* Returns array of base keys for database storage
*/
export async function uploadImagesWithVariants(
uploadType: UploadType,
files: File[],
options: UploadWithResizeOptions = {}
): Promise<{ baseKey: string; publicUrl: string }[]> {
if (files.length === 0) return [];
const results = await Promise.all(
files.map((file) => uploadImageWithVariants(uploadType, file, options))
);
return results.map((r) => ({ baseKey: r.baseKey, publicUrl: r.publicUrl }));
}