upload multiple files

This commit is contained in:
Nicola Malizia
2025-10-10 20:57:02 +02:00
parent a3a0bfc971
commit a0a5bab4f1
9 changed files with 67 additions and 26 deletions

View File

@@ -46,20 +46,39 @@ def delete_celebrity(celebrity_id: int, db: Session = Depends(get_db)):
# --- NUOVI ENDPOINT PER LE IMMAGINI ---
@router.post("/{celebrity_id}/images", response_model=schemas.Image, status_code=201)
def upload_celebrity_image(celebrity_id: int, file: UploadFile = File(...), db: Session = Depends(get_db)):
@router.post("/{celebrity_id}/images", response_model=List[schemas.Image], status_code=201)
def upload_celebrity_images(celebrity_id: int, files: List[UploadFile] = File(...), db: Session = Depends(get_db)):
db_celebrity = crud.get_celebrity(db, celebrity_id=celebrity_id)
if db_celebrity is None:
raise HTTPException(status_code=404, detail="Celebrity not found")
created_images = []
for file in files:
try:
file_extension = file.filename.split('.')[-1]
if not file_extension: # Gestisce file senza estensione
file_extension = "jpg" # Default
unique_filename = f"{uuid.uuid4()}.{file_extension}"
file_location = f"uploads/{unique_filename}"
with open(file_location, "wb+") as file_object:
shutil.copyfileobj(file.file, file_object)
return crud.create_celebrity_image(db=db, celebrity_id=celebrity_id, file_path=unique_filename)
db_image = crud.create_celebrity_image(db=db, celebrity_id=celebrity_id, file_path=unique_filename)
created_images.append(db_image)
except Exception as e:
# Se un file fallisce, potremmo voler continuare con gli altri,
# ma per ora lanciamo un'eccezione per l'intero batch.
# In un'app di produzione, si potrebbe restituire una risposta parziale.
print(f"Failed to upload file {file.filename}: {e}")
raise HTTPException(status_code=500, detail=f"Could not upload file: {file.filename}")
if not created_images:
raise HTTPException(status_code=400, detail="No files were successfully uploaded.")
return created_images
@router.post("/{celebrity_id}/images/from-url", response_model=schemas.Image, status_code=201)
async def add_image_from_url(celebrity_id: int, request: schemas.ImageUrlRequest, db: Session = Depends(get_db)):

View File

@@ -2,7 +2,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { getCelebrityById, updateCelebrity, deleteCelebrity, uploadImage, setProfileImage, deleteImage, addImageFromUrl } from '../services/api';
import { getCelebrityById, updateCelebrity, deleteCelebrity, uploadImages, setProfileImage, deleteImage, addImageFromUrl } from '../services/api';
import EditableField from './EditableField';
import './CelebrityProfile.css';
@@ -14,7 +14,7 @@ function CelebrityProfile() {
const [error, setError] = useState(null);
// Stati per la gestione upload
const [selectedFile, setSelectedFile] = useState(null);
const [selectedFiles, setSelectedFiles] = useState(null); // Da file a files
const [isUploading, setIsUploading] = useState(false);
const [imageUrl, setImageUrl] = useState('');
const [isFetchingFromUrl, setIsFetchingFromUrl] = useState(false);
@@ -43,12 +43,13 @@ function CelebrityProfile() {
}
}, [id, navigate, fetchProfile]);
// Effetto per caricare il file quando `selectedFile` cambia
// Effetto per caricare i file quando `selectedFiles` cambia
useEffect(() => {
if (selectedFile) {
if (selectedFiles && selectedFiles.length > 0) {
handleUpload();
}
}, [selectedFile]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFiles]);
const handleFieldSave = async (fieldName, newValue) => {
let valueToSend = newValue === '' ? null : newValue;
@@ -79,20 +80,24 @@ function CelebrityProfile() {
};
// --- Gestori per le Immagini ---
const handleFileChange = (e) => setSelectedFile(e.target.files[0]);
const handleFileChange = (e) => {
if (e.target.files.length > 0) {
setSelectedFiles(Array.from(e.target.files));
}
};
const handleUpload = async () => {
if (!selectedFile) return;
if (!selectedFiles || selectedFiles.length === 0) return;
setIsUploading(true);
setError(null);
try {
await uploadImage(id, selectedFile);
setSelectedFile(null);
document.querySelector('input[type="file"]').value = ""; // Reset del campo file
fetchProfile(); // Ricarica i dati per vedere la nuova immagine
await uploadImages(id, selectedFiles); // Chiama la nuova funzione plurale
fetchProfile();
} catch (err) {
setError(`Upload fallito: ${err.message}`);
} finally {
setSelectedFiles(null);
if(fileInputRef.current) fileInputRef.current.value = "";
setIsUploading(false);
}
};
@@ -142,7 +147,7 @@ function CelebrityProfile() {
setIsDraggingOver(false);
const files = e.dataTransfer.files;
if (files && files.length > 0) {
setSelectedFile(files[0]);
setSelectedFiles(Array.from(files));
}
};
@@ -207,8 +212,11 @@ function CelebrityProfile() {
onDragLeave={handleDragLeave}
aria-busy={isUploading}
>
<input type="file" ref={fileInputRef} onChange={handleFileChange} hidden />
{isUploading ? <p>Caricamento...</p> : <p>Trascina un file qui, o <strong>clicca per selezionare</strong>.</p>}
<input type="file" ref={fileInputRef} onChange={handleFileChange} hidden multiple />
{isUploading
? <p>Caricamento di {selectedFiles.length} file...</p>
: <p>Trascina i file qui, o <strong>clicca per selezionare</strong>.</p>
}
</div>
</div>

View File

@@ -60,6 +60,20 @@ export const uploadImage = async (celebrityId, file) => {
return handleResponse(response);
};
export const uploadImages = async (celebrityId, files) => {
const formData = new FormData();
// Aggiunge ogni file allo stesso campo 'files'. Il backend lo interpreterà come una lista.
files.forEach(file => {
formData.append('files', file);
});
const response = await fetch(`${API_BASE_URL}/celebrities/${celebrityId}/images`, {
method: 'POST',
body: formData,
});
return handleResponse(response);
};
export const setProfileImage = async (celebrityId, imageId) => {
const response = await fetch(`${API_BASE_URL}/celebrities/${celebrityId}/profile-image`, {
method: 'PUT',

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB