Files
Socialflow/server/services/cloudinary.ts
T
michaelschal 4bbdabdbde Update image display to support multiple sizes and background colors
Configure Cloudinary service to generate multiple image transformations (Facebook Feed, Instagram Feed, Instagram Story) upon upload and update frontend to use these generated URLs, adjusting CSS from `object-contain` to `object-cover` for proper display.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/nGcGXQw
2025-10-02 12:28:33 +00:00

104 lines
3.3 KiB
TypeScript

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<any>((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<void> {
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();