mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
5 files changed
+21
-13
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -11,6 +11,7 @@ Preferred communication style: Simple, everyday language.
|
|||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
### October 14, 2025
|
### October 14, 2025
|
||||||
|
- **Cloudinary Upload Permissions Fix**: Fixed critical bug preventing non-admin users from uploading media. Problem: System searched for user-specific Cloudinary config (only admins can configure). Solution: Added `getAnyCloudinaryConfig()` method in storage layer to retrieve first available config, enabling shared Cloudinary credentials across all users. Updated `cloudinaryService.uploadMedia()` and `deleteMedia()` to use shared config internally while maintaining userId for media ownership. Upload and image editor endpoints now verify shared config exists before processing. Standard users can now upload media successfully using admin's Cloudinary configuration.
|
||||||
- **Android Camera Upload Fix**: Fixed critical Android camera capture bug where uploads failed with 400 error. Android devices send captured files with invalid names (empty, 'blob', etc.) that Multer rejects. Solution: `handleCameraCapture` now detects invalid filenames and creates new File object with generated name `camera-${timestamp}.${extension}` while preserving MIME type. Applied to both new-post and media-upload components. iPhone functionality unaffected.
|
- **Android Camera Upload Fix**: Fixed critical Android camera capture bug where uploads failed with 400 error. Android devices send captured files with invalid names (empty, 'blob', etc.) that Multer rejects. Solution: `handleCameraCapture` now detects invalid filenames and creates new File object with generated name `camera-${timestamp}.${extension}` while preserving MIME type. Applied to both new-post and media-upload components. iPhone functionality unaffected.
|
||||||
- **Mobile Performance Optimizations**: Implemented comprehensive mobile speed optimizations for "Nouvelle publication" and "Médiathèque" pages. Changes include: (1) Adaptive initial loading - 6 media items on mobile (<768px), 12 on desktop; (2) Optimized thumbnail URLs - replaced `originalUrl` with `facebookFeedUrl` (1080x1080 Cloudinary transformed images) for 70-90% reduction in data transfer while keeping `originalUrl` for zoom/preview quality; (3) Native lazy loading - added `loading="lazy"` attribute to all `<img>` tags for deferred off-screen image loading; (4) Responsive grid layout - 2 columns on mobile, 3 on desktop (sm:grid-cols-3) for better touch targets. Expected impact: Initial load time reduced from 3-5s to <1s on mobile devices.
|
- **Mobile Performance Optimizations**: Implemented comprehensive mobile speed optimizations for "Nouvelle publication" and "Médiathèque" pages. Changes include: (1) Adaptive initial loading - 6 media items on mobile (<768px), 12 on desktop; (2) Optimized thumbnail URLs - replaced `originalUrl` with `facebookFeedUrl` (1080x1080 Cloudinary transformed images) for 70-90% reduction in data transfer while keeping `originalUrl` for zoom/preview quality; (3) Native lazy loading - added `loading="lazy"` attribute to all `<img>` tags for deferred off-screen image loading; (4) Responsive grid layout - 2 columns on mobile, 3 on desktop (sm:grid-cols-3) for better touch targets. Expected impact: Initial load time reduced from 3-5s to <1s on mobile devices.
|
||||||
- **Camera Capture in Media Library**: Added camera button 📷 to MediaUpload component, mirroring functionality from new-post page. Mobile users (lg:hidden) can now capture photos/videos directly from device camera and auto-upload to media library. Implementation includes hidden file input with `capture="environment"`, `handleCameraCapture` handler with input value reset (`e.target.value = ''`) to enable repeated captures, and integration with existing `uploadMutation` for seamless cloud storage.
|
- **Camera Capture in Media Library**: Added camera button 📷 to MediaUpload component, mirroring functionality from new-post page. Mobile users (lg:hidden) can now capture photos/videos directly from device camera and auto-upload to media library. Implementation includes hidden file input with `capture="environment"`, `handleCameraCapture` handler with input value reset (`e.target.value = ''`) to enable repeated captures, and integration with existing `uploadMutation` for seamless cloud storage.
|
||||||
|
|||||||
+8
-8
@@ -467,16 +467,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
const user = req.user as User;
|
const user = req.user as User;
|
||||||
const userId = user.id;
|
const userId = user.id;
|
||||||
|
|
||||||
// Check if Cloudinary is configured
|
// Check if Cloudinary is configured (shared config used for all users)
|
||||||
const cloudinaryConfig = await storage.getCloudinaryConfig(userId);
|
const cloudinaryConfig = await storage.getAnyCloudinaryConfig();
|
||||||
|
|
||||||
if (!cloudinaryConfig) {
|
if (!cloudinaryConfig) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Cloudinary not configured. Please configure Cloudinary in Settings first."
|
error: "Cloudinary not configured. Please ask an administrator to configure Cloudinary in Settings first."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload to Cloudinary
|
// Upload to Cloudinary (service will use shared config internally)
|
||||||
const uploadResult = await cloudinaryService.uploadMedia(
|
const uploadResult = await cloudinaryService.uploadMedia(
|
||||||
req.file.buffer,
|
req.file.buffer,
|
||||||
req.file.originalname,
|
req.file.originalname,
|
||||||
@@ -667,15 +667,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
console.log("✅ Overlays applied, uploading to Cloudinary...");
|
console.log("✅ Overlays applied, uploading to Cloudinary...");
|
||||||
|
|
||||||
// Check if Cloudinary is configured
|
// Check if Cloudinary is configured (shared config used for all users)
|
||||||
const cloudinaryConfig = await storage.getCloudinaryConfig(userId);
|
const cloudinaryConfig = await storage.getAnyCloudinaryConfig();
|
||||||
if (!cloudinaryConfig) {
|
if (!cloudinaryConfig) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Cloudinary not configured. Please configure Cloudinary in Settings first."
|
error: "Cloudinary not configured. Please ask an administrator to configure Cloudinary in Settings first."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload to Cloudinary
|
// Upload to Cloudinary (service will use shared config internally)
|
||||||
const uploadResult = await cloudinaryService.uploadMedia(
|
const uploadResult = await cloudinaryService.uploadMedia(
|
||||||
outputBuffer,
|
outputBuffer,
|
||||||
`edited_${Date.now()}.jpg`,
|
`edited_${Date.now()}.jpg`,
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ class CloudinaryService {
|
|||||||
instagramFeedUrl: string | null;
|
instagramFeedUrl: string | null;
|
||||||
instagramStoryUrl: string | null;
|
instagramStoryUrl: string | null;
|
||||||
}> {
|
}> {
|
||||||
// Get user's Cloudinary config
|
// Get any available Cloudinary config (shared across all users)
|
||||||
const config = await storage.getCloudinaryConfig(userId);
|
const config = await storage.getAnyCloudinaryConfig();
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error('Cloudinary configuration not found. Please configure Cloudinary in Settings.');
|
throw new Error('Cloudinary configuration not found. Please ask an administrator to configure Cloudinary in Settings first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure Cloudinary with user's credentials
|
// Configure Cloudinary with user's credentials
|
||||||
@@ -81,10 +81,11 @@ class CloudinaryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(publicId: string, userId: string, mediaType: 'image' | 'video'): Promise<void> {
|
async deleteMedia(publicId: string, userId: string, mediaType: 'image' | 'video'): Promise<void> {
|
||||||
const config = await storage.getCloudinaryConfig(userId);
|
// Get any available Cloudinary config (shared across all users)
|
||||||
|
const config = await storage.getAnyCloudinaryConfig();
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error('Cloudinary configuration not found');
|
throw new Error('Cloudinary configuration not found. Please ask an administrator to configure Cloudinary in Settings first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudinary.config({
|
cloudinary.config({
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export interface IStorage {
|
|||||||
|
|
||||||
// Cloudinary Config
|
// Cloudinary Config
|
||||||
getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined>;
|
getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined>;
|
||||||
|
getAnyCloudinaryConfig(): Promise<CloudinaryConfig | undefined>;
|
||||||
createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig>;
|
createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig>;
|
||||||
updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
||||||
|
|
||||||
@@ -311,6 +312,11 @@ export class DatabaseStorage implements IStorage {
|
|||||||
return config || undefined;
|
return config || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAnyCloudinaryConfig(): Promise<CloudinaryConfig | undefined> {
|
||||||
|
const [config] = await db.select().from(cloudinaryConfig).limit(1);
|
||||||
|
return config || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig> {
|
async createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig> {
|
||||||
const [newConfig] = await db.insert(cloudinaryConfig).values(config).returning();
|
const [newConfig] = await db.insert(cloudinaryConfig).values(config).returning();
|
||||||
return newConfig;
|
return newConfig;
|
||||||
|
|||||||
Reference in new issue
Block a user