From cb52dbfabe83c1e39b2654e7ac0137298d486094 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Wed, 11 Mar 2026 23:35:50 +0200 Subject: [PATCH] =?UTF-8?q?Dokumenttien=20poisto=20kaikille=20+=20versioid?= =?UTF-8?q?en=20s=C3=A4ilytysrajoitus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Poisto-nappi näkyy dokumentin luojalle (ei enää vain admin) - API: document_delete sallii poiston adminille tai luojalle - Uusi max_versions-sarake documents-tauluun (oletus 10) - Versioiden automaattinen pruning: uuden version tallennuksen yhteydessä poistetaan vanhimmat versiot jos yli max_versions (tiedostot levyltä myös) - Valittavissa per dokumentti: 5, 10, 20, 50 tai rajaton Co-Authored-By: Claude Opus 4.6 --- api.php | 8 +++++++- db.php | 35 +++++++++++++++++++++++++++++++++-- index.html | 10 ++++++++++ script.js | 10 +++++++--- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/api.php b/api.php index 45d6596..6ca3e26 100644 --- a/api.php +++ b/api.php @@ -4283,7 +4283,7 @@ switch ($action) { break; case 'document_delete': - requireAdmin(); + requireAuth(); $companyId = requireCompany(); if ($method !== 'POST') break; try { @@ -4295,6 +4295,12 @@ switch ($action) { echo json_encode(['error' => 'Dokumenttia ei löytynyt']); break; } + // Salli poisto adminille tai dokumentin luojalle + if (!isCompanyAdmin() && $doc['created_by'] !== currentUser()) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta poistaa']); + break; + } // Poista tiedostot levyltä $docDir = DATA_DIR . '/companies/' . $companyId . '/documents/' . $docId; if (is_dir($docDir)) { diff --git a/db.php b/db.php index e384968..6304a22 100644 --- a/db.php +++ b/db.php @@ -612,6 +612,7 @@ function initDatabase(): void { "ALTER TABLE todos ADD COLUMN category VARCHAR(30) DEFAULT '' AFTER priority", "ALTER TABLE user_companies ADD COLUMN role VARCHAR(20) DEFAULT 'user' AFTER company_id", "ALTER TABLE documents ADD COLUMN folder_id VARCHAR(20) DEFAULT NULL AFTER customer_id", + "ALTER TABLE documents ADD COLUMN max_versions INT DEFAULT 10 AFTER current_version", "ALTER TABLE document_versions ADD COLUMN content MEDIUMTEXT DEFAULT NULL AFTER mime_type", ]; foreach ($alters as $sql) { @@ -1818,14 +1819,15 @@ function dbSaveDocument(string $companyId, array $doc): string { $id = $doc['id'] ?? generateId(); $now = date('Y-m-d H:i:s'); _dbExecute(" - INSERT INTO documents (id, company_id, customer_id, folder_id, title, description, category, current_version, created_by, luotu, muokattu, muokkaaja) - VALUES (:id, :companyId, :customerId, :folderId, :title, :description, :category, :currentVersion, :createdBy, :luotu, :muokattu, :muokkaaja) + INSERT INTO documents (id, company_id, customer_id, folder_id, title, description, category, current_version, max_versions, created_by, luotu, muokattu, muokkaaja) + VALUES (:id, :companyId, :customerId, :folderId, :title, :description, :category, :currentVersion, :maxVersions, :createdBy, :luotu, :muokattu, :muokkaaja) ON DUPLICATE KEY UPDATE title = VALUES(title), description = VALUES(description), category = VALUES(category), customer_id = VALUES(customer_id), folder_id = VALUES(folder_id), + max_versions = VALUES(max_versions), muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja) ", [ @@ -1837,6 +1839,7 @@ function dbSaveDocument(string $companyId, array $doc): string { 'description' => $doc['description'] ?? '', 'category' => $doc['category'] ?? 'muu', 'currentVersion' => (int)($doc['current_version'] ?? 0), + 'maxVersions' => (int)($doc['max_versions'] ?? 10), 'createdBy' => $doc['created_by'] ?? '', 'luotu' => $doc['luotu'] ?? $now, 'muokattu' => $now, @@ -1877,6 +1880,34 @@ function dbAddDocumentVersion(string $documentId, array $version): void { _dbExecute("UPDATE documents SET current_version = ?, muokattu = ?, muokkaaja = ? WHERE id = ?", [ $nextVersion, $now, $version['created_by'] ?? '', $documentId ]); + + // Versioiden pruning: poista vanhimmat jos yli max_versions + _pruneDocumentVersions($documentId); +} + +function _pruneDocumentVersions(string $documentId): void { + $doc = _dbFetchOne("SELECT max_versions, company_id FROM documents WHERE id = ?", [$documentId]); + if (!$doc) return; + $maxVersions = (int)($doc['max_versions'] ?? 10); + if ($maxVersions <= 0) return; // 0 = rajaton + + $versions = _dbFetchAll( + "SELECT id, version_number, filename FROM document_versions WHERE document_id = ? ORDER BY version_number DESC", + [$documentId] + ); + + if (count($versions) <= $maxVersions) return; + + // Poista vanhimmat versiot (säilytä uusimmat $maxVersions kpl) + $toDelete = array_slice($versions, $maxVersions); + foreach ($toDelete as $v) { + // Poista tiedosto levyltä jos olemassa + if (!empty($v['filename'])) { + $filePath = DATA_DIR . '/companies/' . $doc['company_id'] . '/documents/' . $documentId . '/' . $v['filename']; + if (is_file($filePath)) unlink($filePath); + } + _dbExecute("DELETE FROM document_versions WHERE id = ?", [$v['id']]); + } } function dbRestoreDocumentVersion(string $documentId, string $versionId, string $user): ?int { diff --git a/index.html b/index.html index 880a3cb..1cd669e 100644 --- a/index.html +++ b/index.html @@ -894,6 +894,16 @@ +
+ + +
diff --git a/script.js b/script.js index b460fd0..8b653e1 100644 --- a/script.js +++ b/script.js @@ -4970,7 +4970,8 @@ function renderDocReadView() { document.getElementById('doc-read-title').textContent = d.title || ''; document.getElementById('doc-read-customer').textContent = '👤 ' + customerName; document.getElementById('doc-read-category').innerHTML = `${docCategoryLabels[d.category] || d.category || 'Muu'}`; - document.getElementById('doc-read-version').textContent = `📌 Versio ${d.current_version || 0}`; + const maxV = (d.max_versions && d.max_versions > 0) ? d.max_versions : '∞'; + document.getElementById('doc-read-version').textContent = `📌 Versio ${d.current_version || 0} (max ${maxV})`; document.getElementById('doc-read-date').textContent = d.muokattu ? '📅 ' + new Date(d.muokattu).toLocaleDateString('fi-FI') : ''; const isMeeting = d.category === 'kokousmuistio'; @@ -4981,9 +4982,10 @@ function renderDocReadView() { document.getElementById('doc-read-description').textContent = d.description || ''; } - // Admin-napit + // Poista-nappi: näytetään adminille tai dokumentin luojalle const isAdmin = isCurrentUserAdmin(); - document.getElementById('btn-doc-delete').style.display = isAdmin ? '' : 'none'; + const isOwner = d.created_by === (currentUser?.username || ''); + document.getElementById('btn-doc-delete').style.display = (isAdmin || isOwner) ? '' : 'none'; // Kokousmuistio vs tiedostopohjainen const contentSection = document.getElementById('doc-read-content-section'); @@ -5162,6 +5164,7 @@ function openDocEdit(doc, forceCategory, forceCustomerId) { const cat = forceCategory || doc?.category || 'muu'; document.getElementById('doc-edit-category').value = cat; document.getElementById('doc-edit-folder-id').value = doc?.folder_id || currentDocFolderId || ''; + document.getElementById('doc-edit-max-versions').value = doc?.max_versions ?? 10; const isMeeting = cat === 'kokousmuistio'; document.getElementById('doc-edit-title').textContent = doc @@ -5227,6 +5230,7 @@ document.getElementById('doc-edit-form')?.addEventListener('submit', async (e) = category: cat, customer_id: document.getElementById('doc-edit-customer').value || null, folder_id: document.getElementById('doc-edit-folder-id').value || null, + max_versions: parseInt(document.getElementById('doc-edit-max-versions').value) || 10, created_by: currentUser?.username || '' };