Files
Socialflow/server/services/cloudinary.ts
T
michaelschal 446459e2b7 Update image formatting for social media posts
Adjust Cloudinary transformations for Facebook and Instagram posts, optimizing aspect ratios and cropping for different platforms.

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/d8HozM2
2025-10-09 10:14:51 +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: 1080, height: 1080, crop: 'fill', gravity: 'center' }, // Facebook Feed (square, no borders)
{ 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();