feat(sources): PDF-Upload als neuer Quellentyp pdf_document
- POST /api/sources/global/upload-pdf: multipart File-Upload,
50 MB Limit, SHA256-Dedup, speichert PDF unter <dirname(DB)>/pdfs/{sha}.pdf,
legt Source mit processed_at=NULL an (Monitor verarbeitet asynchron)
- pattern in GlobalSourceUpdate um pdf_document erweitert (2x)
- dashboard.html: Button + Modal im Grundquellen-Sub-Tab
- sources.js: openPdfUploadModal + setupPdfUploadForm + FormData-Submit
- app.js: API.upload(path, formData) Helper fuer multipart
- requirements.txt: pypdf (Validierung optional)
Dieser Commit ist enthalten in:
@@ -26,6 +26,23 @@ const API = {
|
||||
post(path, body) { return this.request(path, { method: "POST", body: JSON.stringify(body) }); },
|
||||
put(path, body) { return this.request(path, { method: "PUT", body: body ? JSON.stringify(body) : undefined }); },
|
||||
del(path) { return this.request(path, { method: "DELETE" }); },
|
||||
|
||||
async upload(path, formData) {
|
||||
const headers = {};
|
||||
if (this.token) headers["Authorization"] = `Bearer ${this.token}`;
|
||||
const res = await fetch(path, { method: "POST", headers, body: formData });
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("username");
|
||||
window.location.href = "/";
|
||||
return;
|
||||
}
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
throw new Error(data.detail || `Fehler ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
};
|
||||
|
||||
// --- State ---
|
||||
|
||||
@@ -311,6 +311,8 @@ function editGlobalSource(id) {
|
||||
|
||||
function setupSourceForms() {
|
||||
document.getElementById("newGlobalSourceBtn").addEventListener("click", openNewGlobalSource);
|
||||
document.getElementById("newPdfSourceBtn")?.addEventListener("click", openPdfUploadModal);
|
||||
setupPdfUploadForm();
|
||||
document.getElementById("discoverSourceBtn").addEventListener("click", () => {
|
||||
document.getElementById("discoverUrl").value = "";
|
||||
document.getElementById("discoverStatus").style.display = "none";
|
||||
@@ -880,3 +882,68 @@ function toggleSourceInfo(id) {
|
||||
if (btn) btn.classList.toggle("active", !isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
// --- PDF-Quellen-Upload ---
|
||||
function openPdfUploadModal() {
|
||||
const form = document.getElementById("pdfUploadForm");
|
||||
if (form) form.reset();
|
||||
const err = document.getElementById("pdfUploadError");
|
||||
if (err) { err.style.display = "none"; err.textContent = ""; }
|
||||
const prog = document.getElementById("pdfUploadProgress");
|
||||
if (prog) prog.style.display = "none";
|
||||
openModal("modalPdfUpload");
|
||||
}
|
||||
|
||||
function setupPdfUploadForm() {
|
||||
const form = document.getElementById("pdfUploadForm");
|
||||
if (!form || form.dataset.bound === "1") return;
|
||||
form.dataset.bound = "1";
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const errEl = document.getElementById("pdfUploadError");
|
||||
const progEl = document.getElementById("pdfUploadProgress");
|
||||
const submitBtn = document.getElementById("pdfUploadSubmitBtn");
|
||||
errEl.style.display = "none";
|
||||
|
||||
const fileInput = document.getElementById("pdfFile");
|
||||
const f = fileInput?.files?.[0];
|
||||
if (!f) {
|
||||
errEl.textContent = "Bitte eine PDF-Datei auswaehlen.";
|
||||
errEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
if (f.size > 50 * 1024 * 1024) {
|
||||
errEl.textContent = "Datei ueberschreitet 50 MB.";
|
||||
errEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append("file", f);
|
||||
const nm = document.getElementById("pdfName").value.trim();
|
||||
if (nm) fd.append("name", nm);
|
||||
fd.append("category", document.getElementById("pdfCategory").value || "sonstige");
|
||||
const lng = document.getElementById("pdfLanguage").value.trim();
|
||||
if (lng) fd.append("language", lng);
|
||||
const nt = document.getElementById("pdfNotes").value.trim();
|
||||
if (nt) fd.append("notes", nt);
|
||||
|
||||
submitBtn.disabled = true;
|
||||
progEl.style.display = "block";
|
||||
try {
|
||||
await API.upload("/api/sources/global/upload-pdf", fd);
|
||||
closeModal("modalPdfUpload");
|
||||
if (typeof showToast === "function") {
|
||||
showToast("PDF hochgeladen -- Verarbeitung laeuft im Hintergrund", "success");
|
||||
}
|
||||
loadGlobalSources();
|
||||
} catch (err) {
|
||||
errEl.textContent = err.message || "Upload fehlgeschlagen";
|
||||
errEl.style.display = "block";
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
progEl.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren