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
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { v2 as cloudinary } from "cloudinary";
|
|
import sharp from "sharp";
|
|
|
|
cloudinary.config({
|
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
api_key: process.env.CLOUDINARY_API_KEY,
|
|
api_secret: process.env.CLOUDINARY_API_SECRET,
|
|
});
|
|
|
|
const GALLERY_FOLDER = "folx-gallery";
|
|
const MAX_UPLOAD_SIZE = 9 * 1024 * 1024;
|
|
|
|
export interface CloudinaryGalleryImage {
|
|
publicId: string;
|
|
fileName: string;
|
|
artist: string;
|
|
thumb: string;
|
|
large: string;
|
|
mobile: string;
|
|
full: string;
|
|
}
|
|
|
|
function buildUrl(publicId: string, transforms: string): string {
|
|
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 } {
|
|
return {
|
|
thumb: buildUrl(publicId, "c_fill,g_face:center,w_300,h_300,q_auto,f_auto"),
|
|
mobile: buildUrl(publicId, "c_fill,g_auto,w_600,h_338,q_auto,f_auto"),
|
|
large: buildUrl(publicId, "c_fit,w_1200,q_auto,f_auto"),
|
|
full: buildUrl(publicId, "c_fit,w_1800,q_auto,f_auto"),
|
|
};
|
|
}
|
|
|
|
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, "_")}`;
|
|
|
|
try {
|
|
const existing = await cloudinary.api.resource(publicId).catch(() => null);
|
|
if (existing) {
|
|
return publicId;
|
|
}
|
|
} catch {}
|
|
|
|
try {
|
|
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);
|
|
});
|
|
} catch (err: any) {
|
|
console.error(`Cloudinary upload failed for ${fileName}:`, err.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function checkCloudinaryImage(publicId: string): Promise<boolean> {
|
|
try {
|
|
await cloudinary.api.resource(publicId);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function listCloudinaryGallery(): Promise<string[]> {
|
|
try {
|
|
const result = await cloudinary.api.resources({
|
|
type: "upload",
|
|
prefix: GALLERY_FOLDER + "/",
|
|
max_results: 500,
|
|
resource_type: "image",
|
|
});
|
|
return result.resources.map((r: any) => r.public_id);
|
|
} catch (err: any) {
|
|
console.error("Cloudinary list failed:", err.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function extractArtistFromPublicId(publicId: string): string {
|
|
const name = publicId.replace(`${GALLERY_FOLDER}/`, "").replace(/_/g, " ");
|
|
if (/^DSC\d/i.test(name) || /^IMG[\s_]\d/i.test(name)) return "";
|
|
const cleaned = name
|
|
.replace(/\s*\(\d+\)\s*$/, "")
|
|
.replace(/\s*\d+\s*$/, "")
|
|
.replace(/\s*\(\d+\)\s*$/, "");
|
|
return cleaned.trim();
|
|
}
|
|
|
|
export { cloudinary };
|