diff --git a/packages/backend/app/__pycache__/crud.cpython-311.pyc b/packages/backend/app/__pycache__/crud.cpython-311.pyc index b36fa6c..42f3f0d 100644 Binary files a/packages/backend/app/__pycache__/crud.cpython-311.pyc and b/packages/backend/app/__pycache__/crud.cpython-311.pyc differ diff --git a/packages/backend/app/__pycache__/main.cpython-311.pyc b/packages/backend/app/__pycache__/main.cpython-311.pyc index 0260582..f409c18 100644 Binary files a/packages/backend/app/__pycache__/main.cpython-311.pyc and b/packages/backend/app/__pycache__/main.cpython-311.pyc differ diff --git a/packages/backend/app/__pycache__/schemas.cpython-311.pyc b/packages/backend/app/__pycache__/schemas.cpython-311.pyc index fc09b76..f626480 100644 Binary files a/packages/backend/app/__pycache__/schemas.cpython-311.pyc and b/packages/backend/app/__pycache__/schemas.cpython-311.pyc differ diff --git a/packages/backend/app/crud.py b/packages/backend/app/crud.py index d643e9b..bac4179 100644 --- a/packages/backend/app/crud.py +++ b/packages/backend/app/crud.py @@ -1,8 +1,13 @@ -from sqlalchemy.orm import Session +import os +from sqlalchemy.orm import Session, joinedload from . import models, schemas def get_celebrity(db: Session, celebrity_id: int): - return db.query(models.Celebrity).filter(models.Celebrity.id == celebrity_id).first() + # Usiamo joinedload per caricare in anticipo le relazioni e evitare query N+1 + return db.query(models.Celebrity).options( + joinedload(models.Celebrity.profile_image), + joinedload(models.Celebrity.images) + ).filter(models.Celebrity.id == celebrity_id).first() def get_celebrities(db: Session, skip: int = 0, limit: int = 100): return db.query(models.Celebrity).offset(skip).limit(limit).all() @@ -34,4 +39,49 @@ def delete_celebrity(db: Session, celebrity_id: int): db.delete(db_celebrity) db.commit() - return db_celebrity \ No newline at end of file + return db_celebrity + +# --- Funzioni CRUD per le Immagini --- + +def get_image(db: Session, image_id: int): + return db.query(models.Image).filter(models.Image.id == image_id).first() + +def create_celebrity_image(db: Session, celebrity_id: int, file_path: str): + db_image = models.Image(celebrity_id=celebrity_id, file_path=file_path) + db.add(db_image) + db.commit() + db.refresh(db_image) + return db_image + +def set_profile_image(db: Session, celebrity_id: int, image_id: int): + db_celebrity = get_celebrity(db, celebrity_id) + db_image = get_image(db, image_id) + + if not db_celebrity or not db_image or db_image.celebrity_id != celebrity_id: + return None + + db_celebrity.profile_image_id = image_id + db.commit() + db.refresh(db_celebrity) + return get_celebrity(db, celebrity_id) # Ritorna con le relazioni aggiornate + +def delete_image(db: Session, image_id: int): + db_image = get_image(db, image_id) + if not db_image: + return None + + db_celebrity = get_celebrity(db, db_image.celebrity_id) + if db_celebrity and db_celebrity.profile_image_id == image_id: + db_celebrity.profile_image_id = None + db.add(db_celebrity) + + try: + file_on_disk = os.path.join("uploads", db_image.file_path) + if os.path.exists(file_on_disk): + os.remove(file_on_disk) + except OSError as e: + print(f"Error deleting file {file_on_disk}: {e}") + + db.delete(db_image) + db.commit() + return db_image \ No newline at end of file diff --git a/packages/backend/app/main.py b/packages/backend/app/main.py index a8aca1c..7478f1c 100644 --- a/packages/backend/app/main.py +++ b/packages/backend/app/main.py @@ -1,8 +1,13 @@ from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles from .routers import celebrities # Importa il nuovo router app = FastAPI() +# Monta la directory 'uploads' per servire i file statici (immagini caricate) +# Ora saranno accessibili tramite l'URL /api/uploads/ +app.mount("/api/uploads", StaticFiles(directory="uploads"), name="uploads") + # Includi il router delle celebrities nell'app principale app.include_router(celebrities.router) diff --git a/packages/backend/app/routers/__pycache__/celebrities.cpython-311.pyc b/packages/backend/app/routers/__pycache__/celebrities.cpython-311.pyc index f19b744..8757475 100644 Binary files a/packages/backend/app/routers/__pycache__/celebrities.cpython-311.pyc and b/packages/backend/app/routers/__pycache__/celebrities.cpython-311.pyc differ diff --git a/packages/backend/app/routers/celebrities.py b/packages/backend/app/routers/celebrities.py index 15a310d..6c41894 100644 --- a/packages/backend/app/routers/celebrities.py +++ b/packages/backend/app/routers/celebrities.py @@ -1,6 +1,8 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, File, UploadFile from sqlalchemy.orm import Session from typing import List +import uuid +import shutil from .. import crud, models, schemas from ..database import get_db @@ -13,7 +15,7 @@ router = APIRouter( @router.post("/", response_model=schemas.Celebrity, status_code=201) def create_celebrity(celebrity: schemas.CelebrityCreate, db: Session = Depends(get_db)): - # Qui potresti aggiungere un check per vedere se una celebrità con lo stesso nome esiste già + # Qui potresti aggiungere un check per vedere se una celebrit├á con lo stesso nome esiste gi├á return crud.create_celebrity(db=db, celebrity=celebrity) @router.get("/", response_model=List[schemas.Celebrity]) @@ -40,4 +42,80 @@ def delete_celebrity(celebrity_id: int, db: Session = Depends(get_db)): db_celebrity = crud.delete_celebrity(db, celebrity_id=celebrity_id) if db_celebrity is None: raise HTTPException(status_code=404, detail="Celebrity not found") - return db_celebrity \ No newline at end of file + return db_celebrity + +# --- 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)): + db_celebrity = crud.get_celebrity(db, celebrity_id=celebrity_id) + if db_celebrity is None: + raise HTTPException(status_code=404, detail="Celebrity not found") + + file_extension = file.filename.split('.')[-1] + 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) + +@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)): + db_celebrity = crud.get_celebrity(db, celebrity_id=celebrity_id) + if not db_celebrity: + raise HTTPException(status_code=404, detail="Celebrity not found") + + url = str(request.url) + try: + async with httpx.AsyncClient() as client: + # Eseguiamo prima una richiesta HEAD per controllare il tipo e la dimensione + head_response = await client.head(url, follow_redirects=True, timeout=10) + head_response.raise_for_status() + + content_type = head_response.headers.get('content-type') + if not content_type or not content_type.startswith('image/'): + raise HTTPException(status_code=400, detail="URL does not point to a valid image.") + + # Limite di dimensione (es. 10MB) + content_length = int(head_response.headers.get('content-length', 0)) + if content_length > 10 * 1024 * 1024: + raise HTTPException(status_code=400, detail="Image size exceeds 10MB limit.") + + # Scarica l'immagine + get_response = await client.get(url, follow_redirects=True, timeout=30) + get_response.raise_for_status() + + # Determina l'estensione del file + file_extension = content_type.split('/')[-1] + if file_extension == 'jpeg': file_extension = 'jpg' + + unique_filename = f"{uuid.uuid4()}.{file_extension}" + file_location = f"uploads/{unique_filename}" + + with open(file_location, "wb") as file_object: + file_object.write(get_response.content) + + return crud.create_celebrity_image(db=db, celebrity_id=celebrity_id, file_path=unique_filename) + + except httpx.RequestError as exc: + raise HTTPException(status_code=400, detail=f"Failed to fetch image from URL: {exc}") + except Exception as e: + raise HTTPException(status_code=500, detail=f"An internal error occurred: {e}") + +@router.put("/{celebrity_id}/profile-image", response_model=schemas.Celebrity) +def set_celebrity_profile_image(celebrity_id: int, request: schemas.SetProfileImageRequest, db: Session = Depends(get_db)): + updated_celebrity = crud.set_profile_image(db, celebrity_id=celebrity_id, image_id=request.image_id) + if updated_celebrity is None: + raise HTTPException(status_code=404, detail="Celebrity or Image not found, or image does not belong to celebrity.") + return updated_celebrity + +# NOTA: Un approccio pi├╣ RESTful sarebbe /api/images/{image_id} in un router dedicato. +# Per semplicit├á, lo inseriamo qui. +@router.delete("/images/{image_id}", response_model=schemas.Image) +def delete_image(image_id: int, db: Session = Depends(get_db)): + db_image = crud.delete_image(db, image_id=image_id) + if db_image is None: + raise HTTPException(status_code=404, detail="Image not found") + return db_image \ No newline at end of file diff --git a/packages/backend/app/schemas.py b/packages/backend/app/schemas.py index 6ab0dbc..b3c1d8c 100644 --- a/packages/backend/app/schemas.py +++ b/packages/backend/app/schemas.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, HttpUrl from typing import Optional, List from datetime import date, datetime from .models import GenderType, ShoeSystemType, BraSystemType, SurgeryType @@ -74,6 +74,12 @@ class Image(ImageBase): uploaded_at: datetime class Config: from_attributes = True +# --- Richiesta per impostare l'immagine del profilo --- +class SetProfileImageRequest(BaseModel): + image_id: int + +class ImageUrlRequest(BaseModel): + url: HttpUrl # Pydantic valida automaticamente che sia un URL valido # ============================================================================= # SCHEMI PER CELEBRITY @@ -107,7 +113,7 @@ class CelebrityBase(BaseModel): official_website: Optional[str] = None profile_image_id: Optional[int] = None -# Schema per la creazione di una nuova celebrità (eredita da Base) +# Schema per la creazione di una nuova celebrit├á (eredita da Base) class CelebrityCreate(CelebrityBase): pass @@ -146,11 +152,12 @@ class Celebrity(CelebrityBase): updated_at: datetime # Campi relazionali che verranno popolati automaticamente da SQLAlchemy + profile_image: Optional[Image] = None images: List[Image] = [] tattoos: List[Tattoo] = [] aliases: List[CelebrityAlias] = [] - # Per semplicità, possiamo usare qui gli schemi di base, + # Per semplicit├á, possiamo usare qui gli schemi di base, # ma in un'app reale potresti volere schemi specifici per la "lettura". professions: List[Profession] = [] studios: List[Studio] = [] diff --git a/packages/backend/requirements.txt b/packages/backend/requirements.txt index fc76e0e..4d3c527 100644 --- a/packages/backend/requirements.txt +++ b/packages/backend/requirements.txt @@ -1,6 +1,7 @@ # FastAPI e server fastapi uvicorn[standard] +python-multipart # Pydantic per la validazione e le impostazioni pydantic @@ -9,4 +10,7 @@ pydantic-settings # Database sqlalchemy psycopg2-binary -alembic \ No newline at end of file +alembic + +# Per richieste HTTP (scaricare immagini da URL) +httpx \ No newline at end of file diff --git a/packages/frontend/src/components/CelebrityProfile.css b/packages/frontend/src/components/CelebrityProfile.css index 682ef4e..e47671a 100644 --- a/packages/frontend/src/components/CelebrityProfile.css +++ b/packages/frontend/src/components/CelebrityProfile.css @@ -18,12 +18,13 @@ text-align: center; } -.profile-sidebar img { +.profile-sidebar .profile-main-image { border-radius: var(--border-radius); margin-bottom: 1rem; width: 100%; - height: auto; + height: 400px; /* Altezza fissa per l'immagine profilo */ object-fit: cover; + background-color: var(--pico-muted-background-color); /* Sfondo per immagini trasparenti o in caricamento */ } .profile-sidebar .editable-field-container { @@ -78,4 +79,101 @@ .error-message { color: var(--pico-color-red-500); +} + +/* --- Stili per Galleria e Upload --- */ +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 1rem; + margin: 1rem 0; +} +.gallery-item { + position: relative; + border-radius: var(--border-radius); + overflow: hidden; + border: 1px solid var(--pico-card-border-color); +} +.gallery-item img { + display: block; + width: 100%; + height: 150px; + object-fit: cover; +} +.gallery-item-actions { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.6); + padding: 0.25rem; + display: flex; + justify-content: space-around; + transform: translateY(100%); + transition: transform 0.2s ease-in-out; +} +.gallery-item:hover .gallery-item-actions { + transform: translateY(0); +} +.gallery-item-actions button { + font-size: 0.75rem; + padding: 0.2rem 0.4rem; + margin: 0; + --pico-font-weight: 500; +} +.gallery-item-actions button:disabled { + background-color: var(--pico-primary-background); + color: var(--pico-primary-inverse); + border-color: var(--pico-primary-background); + opacity: 1; +} + +.upload-section { + margin-top: 1.5rem; + padding: 1rem; + border: 1px solid var(--pico-card-border-color); + border-radius: var(--border-radius); + display: flex; + flex-direction: column; + gap: 1.5rem; /* Aumentato lo spazio */ +} +.upload-section h4 { + margin: 0 0 0.5rem 0; +} +.upload-section .grid { + grid-template-columns: 1fr auto; + gap: 1rem; + margin: 0; + align-items: center; /* Allinea verticalmente */ +} +.upload-section input { + margin: 0; +} +.upload-section button { + margin: 0; +} + +.drop-zone { + padding: 2rem; + border: 2px dashed var(--pico-muted-border-color); + border-radius: var(--border-radius); + text-align: center; + cursor: pointer; + transition: background-color 0.2s, border-color 0.2s; + background-color: var(--pico-card-background-color); +} +.drop-zone:hover { + border-color: var(--pico-primary-hover-border); +} +.drop-zone.dragging-over { + background-color: var(--pico-primary-background); + border-color: var(--pico-primary); + color: var(--pico-primary-inverse); +} +.drop-zone p { + margin: 0; + color: var(--pico-secondary); +} +.drop-zone strong { + color: var(--pico-primary); } \ No newline at end of file diff --git a/packages/frontend/src/components/CelebrityProfile.jsx b/packages/frontend/src/components/CelebrityProfile.jsx index 4a932ca..62eafe6 100644 --- a/packages/frontend/src/components/CelebrityProfile.jsx +++ b/packages/frontend/src/components/CelebrityProfile.jsx @@ -1,8 +1,8 @@ // packages/frontend/src/components/CelebrityProfile.jsx -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; -import { getCelebrityById, updateCelebrity, deleteCelebrity } from '../services/api'; +import { getCelebrityById, updateCelebrity, deleteCelebrity, uploadImage, setProfileImage, deleteImage, addImageFromUrl } from '../services/api'; import EditableField from './EditableField'; import './CelebrityProfile.css'; @@ -13,22 +13,42 @@ function CelebrityProfile() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + // Stati per la gestione upload + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const [imageUrl, setImageUrl] = useState(''); + const [isFetchingFromUrl, setIsFetchingFromUrl] = useState(false); + const [isDraggingOver, setIsDraggingOver] = useState(false); + const fileInputRef = useRef(null); + + const fetchProfile = useCallback(() => { + getCelebrityById(id) + .then((data) => { + if (data.birth_date) { + data.birth_date = data.birth_date.split('T')[0]; + } + setCelebrity(data); + setError(null); + }) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)); + }, [id]); + useEffect(() => { if (id) { setLoading(true); - getCelebrityById(id) - .then((data) => { - if (data.birth_date) { - data.birth_date = data.birth_date.split('T')[0]; - } - setCelebrity(data); - }) - .catch((err) => setError(err.message)) - .finally(() => setLoading(false)); + fetchProfile(); } else { navigate('/'); } - }, [id, navigate]); + }, [id, navigate, fetchProfile]); + + // Effetto per caricare il file quando `selectedFile` cambia + useEffect(() => { + if (selectedFile) { + handleUpload(); + } + }, [selectedFile]); const handleFieldSave = async (fieldName, newValue) => { let valueToSend = newValue === '' ? null : newValue; @@ -39,8 +59,7 @@ function CelebrityProfile() { try { const updatedCelebrity = await updateCelebrity(id, payload); - setCelebrity((prev) => ({ ...prev, [fieldName]: newValue })); - console.log('Salvataggio riuscito:', updatedCelebrity); + setCelebrity(prev => ({ ...prev, ...updatedCelebrity })); // Aggiornamento pi├╣ robusto } catch (err) { console.error(`Errore durante il salvataggio del campo ${fieldName}:`, err); throw err; @@ -48,31 +67,104 @@ function CelebrityProfile() { }; const handleDelete = async () => { - if (window.confirm(`Sei sicuro di voler eliminare ${celebrity.name}? L'azione è irreversibile.`)) { + if (window.confirm(`Sei sicuro di voler eliminare ${celebrity.name}? L'azione ├¿ irreversibile.`)) { try { await deleteCelebrity(id); - alert(`${celebrity.name} è stato eliminato con successo.`); + alert(`${celebrity.name} ├¿ stato eliminato con successo.`); navigate('/'); } catch (err) { setError(`Errore durante l'eliminazione: ${err.message}`); } } }; + + // --- Gestori per le Immagini --- + const handleFileChange = (e) => setSelectedFile(e.target.files[0]); + + const handleUpload = async () => { + if (!selectedFile) 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 + } catch (err) { + setError(`Upload fallito: ${err.message}`); + } finally { + setIsUploading(false); + } + }; + + const handleFetchFromUrl = async () => { + if (!imageUrl) return; + setIsFetchingFromUrl(true); + setError(null); + try { + await addImageFromUrl(id, imageUrl); + setImageUrl(''); // Pulisce l'input + fetchProfile(); + } catch (err) { + setError(`Fetch da URL fallito: ${err.message}`); + } finally { + setIsFetchingFromUrl(false); + } + }; + + const handleSetProfileImage = async (imageId) => { + try { + const updatedCelebrity = await setProfileImage(id, imageId); + setCelebrity(updatedCelebrity); // Aggiorna lo stato con i dati freschi dal server + } catch (err) { + setError(`Errore nell'impostare l'immagine: ${err.message}`); + } + }; + + const handleDeleteImage = async (imageId) => { + if (window.confirm("Sei sicuro di voler eliminare questa immagine?")) { + try { + await deleteImage(imageId); + fetchProfile(); // Ricarica per aggiornare la galleria + } catch (err) { + setError(`Errore nell'eliminazione dell'immagine: ${err.message}`); + } + } + }; + + // --- Gestori per Drag & Drop --- + const handleDragEnter = (e) => { e.preventDefault(); e.stopPropagation(); setIsDraggingOver(true); }; + const handleDragLeave = (e) => { e.preventDefault(); e.stopPropagation(); setIsDraggingOver(false); }; + const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); }; // Necessario per permettere il drop + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsDraggingOver(false); + const files = e.dataTransfer.files; + if (files && files.length > 0) { + setSelectedFile(files[0]); + } + }; + if (loading) return
Caricamento profilo...
; if (error && !celebrity) return

Errore: {error}

; - if (!celebrity) return

Nessuna celebrità trovata.

; + if (!celebrity) return

Nessuna celebrità trovata.

; + + const profileImageUrl = celebrity.profile_image + ? `/api/uploads/${celebrity.profile_image.file_path}` + : 'https://via.placeholder.com/400x550.png?text=Nessuna+Immagine'; const genderOptions = [{ value: 'female', label: 'Female' }, { value: 'male', label: 'Male' }, { value: 'other', label: 'Other' }]; const braSystemOptions = [{ value: 'US', label: 'US' }, { value: 'UK', label: 'UK' }, { value: 'EU', label: 'EU' }, { value: 'FR', label: 'FR' }, { value: 'AU', label: 'AU' }, { value: 'IT', label: 'IT' }, { value: 'JP', label: 'JP' }]; const shoeSystemOptions = [{ value: 'EU', label: 'EU' }, { value: 'US', label: 'US' }, { value: 'UK', label: 'UK' }]; - const booleanOptions = [{ value: 'true', label: 'Sì' }, { value: 'false', label: 'No' }]; + const booleanOptions = [{ value: 'true', label: 'S├¼' }, { value: 'false', label: 'No' }]; return (
- {error &&

Errore: {error}

} + {error &&

Errore: {error}

}
+

Galleria e Upload

+
+ {celebrity.images.map(img => ( +
+ {`Immagine +
+ + +
+
+ ))} +
+
+
+

Carica da File

+
fileInputRef.current.click()} + onDrop={handleDrop} + onDragOver={handleDragOver} + onDragEnter={handleDragEnter} + onDragLeave={handleDragLeave} + aria-busy={isUploading} + > + + {isUploading ?

Caricamento...

:

Trascina un file qui, o clicca per selezionare.

} +
+
+ +
+

Aggiungi da URL

+
+ setImageUrl(e.target.value)} + disabled={isFetchingFromUrl} + /> + +
+
+
+
+ +

Dati Anagrafici

- + - +
diff --git a/packages/frontend/src/services/api.js b/packages/frontend/src/services/api.js index e1c4904..f64a022 100644 --- a/packages/frontend/src/services/api.js +++ b/packages/frontend/src/services/api.js @@ -3,7 +3,7 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'; const handleResponse = async (response) => { if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.detail || 'Si è verificato un errore'); + throw new Error(errorData.detail || 'Si ├¿ verificato un errore'); } return response.json(); }; @@ -45,4 +45,46 @@ export const deleteCelebrity = async (id) => { throw new Error(errorData.detail || 'Errore durante l\'eliminazione'); } return response.json(); // O un messaggio di successo +}; + +// --- NUOVE FUNZIONI PER LE IMMAGINI --- + +export const uploadImage = async (celebrityId, file) => { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch(`${API_BASE_URL}/celebrities/${celebrityId}/images`, { + method: 'POST', + body: formData, // Il browser imposta automaticamente l'header Content-Type corretto + }); + return handleResponse(response); +}; + +export const setProfileImage = async (celebrityId, imageId) => { + const response = await fetch(`${API_BASE_URL}/celebrities/${celebrityId}/profile-image`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ image_id: imageId }), + }); + return handleResponse(response); +}; + +export const deleteImage = async (imageId) => { + const response = await fetch(`${API_BASE_URL}/celebrities/images/${imageId}`, { + method: 'DELETE', + }); + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.detail || "Errore durante l'eliminazione dell'immagine"); + } + return response.json(); +}; + +export const addImageFromUrl = async (celebrityId, imageUrl) => { + const response = await fetch(`${API_BASE_URL}/celebrities/${celebrityId}/images/from-url`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: imageUrl }), + }); + return handleResponse(response); }; \ No newline at end of file diff --git a/proxy/nginx.conf b/proxy/nginx.conf index e798ec9..afaad96 100644 --- a/proxy/nginx.conf +++ b/proxy/nginx.conf @@ -1,6 +1,9 @@ server { listen 80; + # Aumenta la dimensione massima del corpo della richiesta a 10GBS + client_max_body_size 10g; + # Indirizza tutte le richieste che iniziano con /api al servizio backend location /api { proxy_pass http://backend:8000; diff --git a/uploads/25109b88-28f1-4040-8675-3db632bbc9dc.png b/uploads/25109b88-28f1-4040-8675-3db632bbc9dc.png new file mode 100644 index 0000000..d723b80 Binary files /dev/null and b/uploads/25109b88-28f1-4040-8675-3db632bbc9dc.png differ diff --git a/uploads/303993ba-8ed3-4a5c-89e0-bc7873dc23aa.png b/uploads/303993ba-8ed3-4a5c-89e0-bc7873dc23aa.png new file mode 100644 index 0000000..770d53b Binary files /dev/null and b/uploads/303993ba-8ed3-4a5c-89e0-bc7873dc23aa.png differ diff --git a/uploads/76a0502b-da2b-44c5-9f3f-18b942877c10.jpg b/uploads/76a0502b-da2b-44c5-9f3f-18b942877c10.jpg new file mode 100644 index 0000000..1870fa0 Binary files /dev/null and b/uploads/76a0502b-da2b-44c5-9f3f-18b942877c10.jpg differ