import { v2 as cloudinary } from 'cloudinary'; import { storage } from '../storage'; class CloudinaryService { async uploadMedia( buffer: Buffer, fileName: string, userId: string, mimeType: string ): Promise<{ publicId: string; originalUrl: string; facebookFeedUrl: string | null; instagramFeedUrl: string | null; instagramStoryUrl: string | null; }> { // Get user's Cloudinary config const config = await storage.getCloudinaryConfig(userId); if (!config) { throw new Error('Cloudinary configuration not found. Please configure Cloudinary in Settings.'); } // Configure Cloudinary with user's credentials cloudinary.config({ cloud_name: config.cloudName, api_key: config.apiKey, api_secret: config.apiSecret, secure: true, }); const isVideo = mimeType.startsWith('video/'); // Define eager transformations for images (generated during upload) const eagerTransformations = !isVideo ? [ { width: 1200, height: 630, crop: 'pad', gravity: 'center', background: 'auto' }, // Facebook Feed { width: 1080, height: 1080, crop: 'pad', gravity: 'center', background: 'auto' }, // Instagram Feed { width: 1080, height: 1920, crop: 'pad', gravity: 'center', background: 'auto' } // Instagram Story ] : undefined; // Upload to Cloudinary with eager transformations const uploadResult = await new Promise((resolve, reject) => { const uploadStream = cloudinary.uploader.upload_stream( { folder: 'social-flow', public_id: `${Date.now()}-${fileName.replace(/\.[^/.]+$/, '')}`, resource_type: isVideo ? 'video' : 'image', eager: eagerTransformations, eager_async: false, // Generate transformations immediately }, (error, result) => { if (error) reject(error); else resolve(result); } ); uploadStream.end(buffer); }); const publicId = uploadResult.public_id; const originalUrl = uploadResult.secure_url; // Extract eager transformation URLs (generated by Cloudinary during upload) let facebookFeedUrl = null; let instagramFeedUrl = null; let instagramStoryUrl = null; if (!isVideo && uploadResult.eager && uploadResult.eager.length === 3) { facebookFeedUrl = uploadResult.eager[0].secure_url; // Facebook Feed (1200x630) instagramFeedUrl = uploadResult.eager[1].secure_url; // Instagram Feed (1080x1080) instagramStoryUrl = uploadResult.eager[2].secure_url; // Instagram Story (1080x1920) } return { publicId, originalUrl, facebookFeedUrl, instagramFeedUrl, instagramStoryUrl, }; } async deleteMedia(publicId: string, userId: string, mediaType: 'image' | 'video'): Promise { const config = await storage.getCloudinaryConfig(userId); if (!config) { throw new Error('Cloudinary configuration not found'); } cloudinary.config({ cloud_name: config.cloudName, api_key: config.apiKey, api_secret: config.apiSecret, secure: true, }); await cloudinary.uploader.destroy(publicId, { resource_type: mediaType, }); } } export const cloudinaryService = new CloudinaryService();