upload multiple files
This commit is contained in:
Binary file not shown.
@@ -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)):
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
BIN
uploads/165fa68d-eb00-47f7-a1b2-4d5fa8352728.png
Normal file
BIN
uploads/165fa68d-eb00-47f7-a1b2-4d5fa8352728.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
uploads/1d024628-4db7-4333-8b77-6e3e61de5a20.webp
Normal file
BIN
uploads/1d024628-4db7-4333-8b77-6e3e61de5a20.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
uploads/34da28b0-5f05-4996-acf3-fbeec43fe916.png
Normal file
BIN
uploads/34da28b0-5f05-4996-acf3-fbeec43fe916.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
uploads/dfdcb6c6-68c4-4dc0-a15e-889df4356e3e.png
Normal file
BIN
uploads/dfdcb6c6-68c4-4dc0-a15e-889df4356e3e.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
BIN
uploads/ecb201fe-6968-4891-99d6-deafe7fca852.png
Normal file
BIN
uploads/ecb201fe-6968-4891-99d6-deafe7fca852.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
Reference in New Issue
Block a user