Complete the migration of all gallery images to Cloudinary

Implement image compression using Sharp to handle uploads exceeding 10MB, update Cloudinary URL generation, and finalize the gallery migration process.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ecf3cabf-0fca-4959-8ab7-e18c7b222cf7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/ncMMRQ9
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-03-06 10:38:59 +00:00
parent a23ae0bdb7
commit b8c01627dc
2 changed files with 213 additions and 10 deletions
+44 -9
View File
@@ -1,4 +1,5 @@
import { v2 as cloudinary } from "cloudinary";
import sharp from "sharp";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
@@ -7,6 +8,7 @@ cloudinary.config({
});
const GALLERY_FOLDER = "folx-gallery";
const MAX_UPLOAD_SIZE = 9 * 1024 * 1024;
export interface CloudinaryGalleryImage {
publicId: string;
@@ -19,10 +21,8 @@ export interface CloudinaryGalleryImage {
}
function buildUrl(publicId: string, transforms: string): string {
return cloudinary.url(publicId, {
transformation: transforms,
secure: true,
});
const cloudName = process.env.CLOUDINARY_CLOUD_NAME || "djqxt0pf3";
return `https://res.cloudinary.com/${cloudName}/image/upload/${transforms}/${publicId}`;
}
export function generateImageUrls(publicId: string): { thumb: string; large: string; mobile: string; full: string } {
@@ -34,6 +34,31 @@ export function generateImageUrls(publicId: string): { thumb: string; large: str
};
}
async function downloadAndCompress(imageUrl: string): Promise<Buffer> {
const resp = await fetch(imageUrl);
if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
const arrayBuffer = await resp.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
if (buffer.length <= MAX_UPLOAD_SIZE) {
return buffer;
}
const compressed = await sharp(buffer)
.resize({ width: 4000, withoutEnlargement: true })
.jpeg({ quality: 85 })
.toBuffer();
if (compressed.length > MAX_UPLOAD_SIZE) {
return sharp(buffer)
.resize({ width: 3000, withoutEnlargement: true })
.jpeg({ quality: 75 })
.toBuffer();
}
return compressed;
}
export async function uploadToCloudinary(imageUrl: string, fileName: string): Promise<string | null> {
const publicId = `${GALLERY_FOLDER}/${fileName.replace(/\.\w+$/, "").replace(/[^a-zA-Z0-9_-]/g, "_")}`;
@@ -45,12 +70,22 @@ export async function uploadToCloudinary(imageUrl: string, fileName: string): Pr
} catch {}
try {
const result = await cloudinary.uploader.upload(imageUrl, {
public_id: publicId,
overwrite: false,
resource_type: "image",
const buffer = await downloadAndCompress(imageUrl);
return new Promise((resolve, reject) => {
const uploadStream = cloudinary.uploader.upload_stream(
{
public_id: publicId,
overwrite: false,
resource_type: "image",
},
(error, result) => {
if (error) reject(error);
else resolve(result!.public_id);
}
);
uploadStream.end(buffer);
});
return result.public_id;
} catch (err: any) {
console.error(`Cloudinary upload failed for ${fileName}:`, err.message);
return null;