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 { 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 { 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 deleteFromCloudinary(publicId: string): Promise { try { const result = await cloudinary.uploader.destroy(publicId, { resource_type: "image" }); return result.result === "ok"; } catch (err: any) { console.error(`Cloudinary delete failed for ${publicId}:`, err.message); return false; } } export async function checkCloudinaryImage(publicId: string): Promise { try { await cloudinary.api.resource(publicId); return true; } catch { return false; } } export async function listCloudinaryGallery(): Promise { 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 interface CloudinaryResourceInfo { publicId: string; fileName: string; artist: string; width: number; height: number; format: string; bytes: number; createdAt: string; thumb: string; large: string; mobile: string; full: string; } export async function listCloudinaryGalleryDetailed(): Promise { try { let allResources: any[] = []; let nextCursor: string | undefined; do { const result = await cloudinary.api.resources({ type: "upload", prefix: GALLERY_FOLDER + "/", max_results: 500, resource_type: "image", ...(nextCursor ? { next_cursor: nextCursor } : {}), }); allResources = allResources.concat(result.resources); nextCursor = result.next_cursor; } while (nextCursor); return allResources.map((r: any) => { const urls = generateImageUrls(r.public_id); const rawName = r.public_id.replace(`${GALLERY_FOLDER}/`, "").replace(/_/g, " "); let originalFileName = rawName; const ext = r.format || "jpg"; originalFileName = rawName + "." + ext; return { publicId: r.public_id, fileName: originalFileName, artist: extractArtistFromPublicId(r.public_id), width: r.width, height: r.height, format: r.format, bytes: r.bytes, createdAt: r.created_at, ...urls, }; }); } catch (err: any) { console.error("Cloudinary detailed 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 };