(() => { "use strict"; const targetSelect = document.getElementById("photoUploadTarget"); const folderInput = document.getElementById("photoUploadFolder"); const uploadButton = document.getElementById("btnUploadPhoto"); const fileInput = document.getElementById("photoUploadInput"); const statusBox = document.getElementById("photoUploadStatus"); if ( !targetSelect || !folderInput || !uploadButton || !fileInput || !statusBox ) { console.warn("[photoUpload] Elementi interfaccia upload non trovati"); return; } let pendingUpload = null; let uploading = false; function setStatus(message, type = "info") { statusBox.textContent = message || ""; statusBox.className = `upload-${type}`; } function normalize(value) { return String(value ?? "").trim(); } function getAuthorizationHeaders() { /* * Se l'autenticazione utilizza cookie/sessione, * non serve aggiungere Authorization. * * Se utilizzi JWT in localStorage, modifica i nomi * qui sotto in base al tuo progetto. */ const token = localStorage.getItem("token") || localStorage.getItem("jwtToken") || localStorage.getItem("accessToken"); if (!token) { return {}; } return { Authorization: `Bearer ${token}`, }; } async function readJsonResponse(response) { const contentType = response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { return response.json(); } const text = await response.text(); return { ok: false, error: text || `Errore HTTP ${response.status}`, }; } function setUploadEnabled() { const hasTarget = Boolean(normalize(targetSelect.value)); const hasFolder = Boolean(normalize(folderInput.value)); uploadButton.disabled = uploading || !hasTarget || !hasFolder; } async function loadUploadTargets() { targetSelect.disabled = true; uploadButton.disabled = true; targetSelect.innerHTML = ''; setStatus("Caricamento destinazioni autorizzate...", "info"); try { const response = await fetch("/photos/upload-targets", { method: "GET", credentials: "include", headers: { Accept: "application/json", ...getAuthorizationHeaders(), }, }); const result = await readJsonResponse(response); if (!response.ok || !result.ok) { throw new Error( result.error || `Impossibile caricare le destinazioni (${response.status})`, ); } const targets = Array.isArray(result.targets) ? result.targets : []; targetSelect.replaceChildren(); const placeholder = document.createElement("option"); placeholder.value = ""; placeholder.textContent = "Seleziona utente o device"; targetSelect.appendChild(placeholder); for (const target of targets) { const value = normalize(target.value); if (!value) { continue; } const option = document.createElement("option"); option.value = value; option.textContent = target.type === "device" ? `${target.label || value} (device)` : `${target.label || value} (utente)`; option.dataset.targetType = target.type || ""; targetSelect.appendChild(option); } targetSelect.disabled = targets.length === 0; if (targets.length === 0) { setStatus("Nessuna destinazione autorizzata per l'upload.", "error"); } else { setStatus("Seleziona la destinazione e premi “Carica foto”.", "info"); } setUploadEnabled(); } catch (error) { console.error("[photoUpload] Errore upload-targets:", error); targetSelect.innerHTML = ''; targetSelect.disabled = true; uploadButton.disabled = true; setStatus(error.message, "error"); } } uploadButton.addEventListener("click", () => { if (uploading) { return; } const source = normalize(targetSelect.value); const folder = normalize(folderInput.value); if (!source) { setStatus("Seleziona prima l'utente o il device.", "error"); return; } if (!folder) { setStatus("Inserisci prima il nome della cartella.", "error"); folderInput.focus(); return; } if ( folder === "." || folder === ".." || folder.includes("/") || folder.includes("\\") || folder.includes("\0") ) { setStatus("Nome cartella non valido. Non usare /, \\ oppure ..", "error"); folderInput.focus(); return; } pendingUpload = { source, folder, }; fileInput.value = ""; setStatus( `Destinazione: ${source}\n` + `Cartella: ${folder}\n` + "Apertura del selettore file...", "info", ); /* * Deve essere chiamato direttamente nel gestore del click. * Non inserire await, setTimeout o prompt prima di questo punto. */ fileInput.click(); }); fileInput.addEventListener("change", async () => { const files = Array.from(fileInput.files || []); console.log( "[photoUpload] File selezionati:", files.map((file) => ({ name: file.name, type: file.type, size: file.size, })), ); if (files.length === 0) { pendingUpload = null; setStatus("Nessun file selezionato.", "error"); return; } if (!pendingUpload) { setStatus( "Destinazione upload non disponibile. Ripeti la selezione.", "error", ); fileInput.value = ""; return; } const { source, folder } = pendingUpload; pendingUpload = null; uploading = true; targetSelect.disabled = true; folderInput.disabled = true; setUploadEnabled(); const totalSelectedBytes = files.reduce( (total, file) => total + file.size, 0, ); const totalSelectedMb = (totalSelectedBytes / 1024 / 1024).toFixed(1); setStatus( `Upload di ${files.length} file in corso...\n` + `Dimensione totale: ${totalSelectedMb} MB\n` + `Destinazione: ${source}\n` + `Cartella: ${folder}`, "info", ); try { const formData = new FormData(); formData.append("source", source); formData.append("folder", folder); /* * Tutti i file usano il campo "photo". * Il server li riceve con: * uploadMedia.array("photo", MEDIA_UPLOAD_MAX_FILES) */ for (const file of files) { formData.append("photo", file, file.name); } console.log("[photoUpload] Invio richiesta", { source, folder, fileCount: files.length, totalSelectedBytes, }); const response = await fetch("/photos/upload", { method: "POST", credentials: "include", /* * Non aggiungere Content-Type. * Il browser genera automaticamente il boundary * multipart/form-data. */ headers: { Accept: "application/json", ...getAuthorizationHeaders(), }, body: formData, }); const result = await readJsonResponse(response); console.log("[photoUpload] Risposta server:", { status: response.status, result, }); if (!response.ok || !result.ok) { throw new Error( result.error || `Upload non riuscito (${response.status})`, ); } const returnedFiles = Array.isArray(result.files) ? result.files : []; const renamedFiles = returnedFiles.filter((file) => { return file.renamed; }); let message = "Upload completato correttamente.\n" + `File caricati: ${result.fileCount ?? returnedFiles.length}\n` + `Foto: ${result.photoCount ?? 0}\n` + `Video: ${result.videoCount ?? 0}\n` + `Destinazione: ${result.source ?? source}\n` + `Cartella: ${result.folder ?? folder}`; if (renamedFiles.length > 0) { message += "\nFile rinominati perché già esistenti: " + renamedFiles.length; for (const file of renamedFiles) { message += `\n${file.originalFileName} -> ${file.fileName}`; } } if (result.watcherPending) { message += "\nIl watcher elaborerà automaticamente tutti i media."; } setStatus(message, "success"); } catch (error) { console.error("[photoUpload] Errore upload:", error); setStatus(`Upload fallito: ${error.message}`, "error"); } finally { uploading = false; targetSelect.disabled = false; folderInput.disabled = false; /* * Permette di selezionare nuovamente gli stessi file. */ fileInput.value = ""; setUploadEnabled(); } }); folderInput.addEventListener("input", () => { setUploadEnabled(); }); targetSelect.addEventListener("change", () => { setUploadEnabled(); const source = normalize(targetSelect.value); if (source) { setStatus(`Destinazione selezionata: ${source}`, "info"); } else { setStatus("Seleziona l'utente o il device di destinazione.", "info"); } }); loadUploadTargets(); })();