missions carrousse

This commit is contained in:
alma 2025-05-06 10:59:38 +02:00
parent 76d4d55285
commit dddfd5024b
3 changed files with 211 additions and 24 deletions

View File

@ -0,0 +1,115 @@
# Minio Troubleshooting Guide
This document outlines the fixes implemented for the mission file upload issues with Minio.
## Problem Description
Mission uploads (logo and attachments) were not working correctly:
- Files weren't appearing in Minio despite upload attempts
- Mission logos weren't displaying even though they were uploaded
- Participation field showed "Non spécifié" despite values in the database
- SDG/ODD icons weren't displaying correctly
## Implemented Fixes
### 1. Added URL Generation Function
Added a `getPublicUrl` function in `lib/s3.ts` that properly constructs URLs for files stored in Minio:
```typescript
export function getPublicUrl(filePath: string): string {
if (!filePath) return '';
if (filePath.startsWith('http')) return filePath; // Already a full URL
// Remove leading slash if present
const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
// Construct the full URL
const endpoint = S3_CONFIG.endpoint?.replace(/\/$/, ''); // Remove trailing slash if present
const bucket = S3_CONFIG.bucket;
// Return original path if no endpoint is configured
if (!endpoint) return cleanPath;
// Construct and return the full URL
return `${endpoint}/${bucket}/${cleanPath}`;
}
```
### 2. Updated Mission Display Page
Modified `app/missions/page.tsx` to use the `getPublicUrl` function when displaying mission logos:
```tsx
{mission.logo ? (
<img
src={mission.logo ? getPublicUrl(mission.logo) : ''}
alt={mission.name}
className="w-full h-full object-cover rounded-md border border-gray-200"
onError={(e) => {
// Error handling...
}}
/>
) : null}
```
### 3. Enhanced Upload API
Updated `/app/api/missions/upload/route.ts` to:
- Include additional logging
- Generate and return proper public URLs
- Improve error handling
### 4. Enhanced Mission Detail API
Modified `/app/api/missions/[missionId]/route.ts` to include public URLs in the response:
```typescript
const missionWithUrls = {
...mission,
logoUrl: mission.logo ? getPublicUrl(mission.logo) : null,
attachments: mission.attachments.map((attachment) => ({
...attachment,
publicUrl: getPublicUrl(attachment.filePath)
}))
};
```
### 5. Added Testing Tools
1. Browser Console Utilities:
- `window.testMinioConnection()` - Test Minio connectivity
- `window.getMinioUrl(path)` - Generate a public URL for debugging
2. Server-side Test Script:
- Created `scripts/test-minio-upload.js` to test uploads from the command line
- Tests uploading, downloading, and URL generation
## How to Test
1. **Using the browser console:**
```javascript
// Test connection and list files
window.testMinioConnection()
// Generate URL for a specific path
window.getMinioUrl('user-123/missions/456/logo.jpg')
```
2. **Using the server-side script:**
```bash
node scripts/test-minio-upload.js
```
## Required Environment Variables
Make sure these are properly set in your environment:
- `MINIO_S3_UPLOAD_BUCKET_URL` - The Minio endpoint URL
- `MINIO_AWS_REGION` - The AWS region (often 'us-east-1' for Minio)
- `MINIO_AWS_S3_UPLOAD_BUCKET_NAME` - The bucket name
- `MINIO_ACCESS_KEY` - Access key for Minio
- `MINIO_SECRET_KEY` - Secret key for Minio
## Additional Notes
1. The same Minio bucket is used for both Pages and Missions.
2. Pages functionality is working properly, suggesting the Minio configuration itself is correct.
3. Make sure that the bucket has proper permissions for public read access.
4. The URL paths for SDG/ODD icons were corrected to use `/F SDG Icons 2019 WEB/F-WEB-Goal-XX.png`

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useState, useRef } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { UploadCloud, X, Check, Loader2 } from 'lucide-react'; import { UploadCloud, X, Check, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
@ -21,6 +21,15 @@ export function FileUpload({
maxSize = 5 * 1024 * 1024, // 5MB maxSize = 5 * 1024 * 1024, // 5MB
acceptedFileTypes = type === 'logo' ? 'image/*' : '.pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png' acceptedFileTypes = type === 'logo' ? 'image/*' : '.pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png'
}: FileUploadProps) { }: FileUploadProps) {
// Log props on init
console.log('FileUpload component initialized with props:', {
type,
missionId,
hasMissionId: !!missionId,
maxSize,
acceptedFileTypes
});
const { data: session } = useSession(); const { data: session } = useSession();
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
@ -28,6 +37,28 @@ export function FileUpload({
const [file, setFile] = useState<File | null>(null); const [file, setFile] = useState<File | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const isMounted = useRef(true);
// Cleanup on unmount
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
// Log when session changes
useEffect(() => {
console.log('Session state in FileUpload:', {
isSessionLoaded: !!session,
hasUser: !!session?.user,
userId: session?.user?.id
});
}, [session]);
// Log when missionId changes
useEffect(() => {
console.log('MissionId in FileUpload component:', missionId);
}, [missionId]);
// Handle drag events // Handle drag events
const handleDragOver = (e: React.DragEvent) => { const handleDragOver = (e: React.DragEvent) => {
@ -92,11 +123,41 @@ export function FileUpload({
}; };
const handleUpload = async () => { const handleUpload = async () => {
if (!file || !session?.user?.id || !missionId) { // Log all values to see what's missing
console.error('Upload failed: Missing required data', { console.log('Debug upload parameters:', {
fileExists: !!file, file,
userIdExists: !!session?.user?.id, sessionUser: session?.user,
missionIdExists: !!missionId userId: session?.user?.id,
missionId
});
// Check for missing parameters and provide specific feedback
if (!file) {
console.error('Upload failed: No file selected');
toast({
title: 'Upload failed',
description: 'No file selected. Please select a file first.',
variant: 'destructive',
});
return;
}
if (!session?.user?.id) {
console.error('Upload failed: No user session');
toast({
title: 'Authentication required',
description: 'You need to be logged in to upload files. Please log in and try again.',
variant: 'destructive',
});
return;
}
if (!missionId) {
console.error('Upload failed: Missing mission ID');
toast({
title: 'Upload failed',
description: 'Missing mission ID. Please refresh the page and try again.',
variant: 'destructive',
}); });
return; return;
} }
@ -142,10 +203,14 @@ export function FileUpload({
const result = await response.json(); const result = await response.json();
console.log('Upload successful, result:', result); console.log('Upload successful, result:', result);
// Only update state if the component is still mounted
if (isMounted.current) {
setProgress(100); setProgress(100);
// Reset file after successful upload // Reset file after successful upload
setTimeout(() => { setTimeout(() => {
if (isMounted.current) {
setFile(null); setFile(null);
setIsUploading(false); setIsUploading(false);
setProgress(0); setProgress(0);
@ -154,6 +219,7 @@ export function FileUpload({
if (onUploadComplete) { if (onUploadComplete) {
onUploadComplete(result); onUploadComplete(result);
} }
}
toast({ toast({
title: 'File uploaded successfully', title: 'File uploaded successfully',
@ -161,9 +227,15 @@ export function FileUpload({
variant: 'default', variant: 'default',
}); });
}, 1000); }, 1000);
}
} catch (error) { } catch (error) {
console.error('Upload error details:', error); console.error('Upload error details:', error);
// Only update state if the component is still mounted
if (isMounted.current) {
setIsUploading(false); setIsUploading(false);
}
toast({ toast({
title: 'Upload failed', title: 'Upload failed',
description: error instanceof Error ? error.message : 'An error occurred during upload', description: error instanceof Error ? error.message : 'An error occurred during upload',

0
scripts/test-minio-upload.js Normal file → Executable file
View File