Full DMS: folders + ACLs, versioning, trash, bulk ops, preview, smart folders

Rebuild the dashboard as a Drive-style document management system on top of
the existing CaveauAI hybrid RAG pipeline.

Backend:
- 5 migrations (versions, trash soft-delete, saved searches, categories, audit)
- DMS helpers (folder ACL walker, disk storage, audit, version snapshot,
  XLSX/PPTX/HTML/CSV/MD extractors)
- New APIs: folders, document-versions, trash, bulk, preview, saved-searches,
  categories, diagnostics
- Extended APIs: documents (folder_id, soft-delete, ACL filter, sort),
  upload (9 file types, version-collision detection with replace/new/keep-both,
  disk persistence), chat-stream (folder scoping + graph related-documents)
- 30-day trash purge cron with Qdrant + disk + graph cleanup

Frontend:
- Drive-style two-pane browser with folder tree, drag-drop, bulk-action bar,
  right-click context menu, multi-select
- New pages: folders (tree + per-folder ACL editor), trash (restore/purge)
- Extended pages: upload (folder picker, version-collision modal, 9 file
  type chips), document (Preview/Versions/Permissions tabs with PDF.js +
  mammoth.js + audio), index (DMS KPIs + activity feed), settings (live
  diagnostics ping MariaDB/Qdrant/LiteLLM/FalkorDB/disk), chat (folder
  scope chips + related-authorities chips)
- New CSS (dms.css) + JS bundle (dms.js) exposing window.DBN_DMS
- Sidebar nav adds Folders + Trash items

All routes return HTTP 200 in local smoke test; all 32 files lint clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 22:24:56 +02:00
parent b84827ecea
commit 2e2b0b45fa
30 changed files with 5438 additions and 335 deletions
@@ -0,0 +1,49 @@
-- DBN DMS migration 001 — document versioning
-- Adds client_document_versions table + current_version/storage_path columns on client_documents.
-- Safe to re-run: uses IF NOT EXISTS / INFORMATION_SCHEMA guards.
CREATE TABLE IF NOT EXISTS client_document_versions (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
document_id INT UNSIGNED NOT NULL,
client_id INT UNSIGNED NOT NULL,
version_number INT UNSIGNED NOT NULL,
title VARCHAR(500) NOT NULL,
content LONGTEXT NOT NULL,
file_size_bytes INT UNSIGNED DEFAULT 0,
original_filename VARCHAR(255) NULL,
storage_path VARCHAR(500) NULL,
word_count INT UNSIGNED DEFAULT 0,
uploaded_by INT UNSIGNED NULL,
notes VARCHAR(500) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_doc_ver (document_id, version_number),
KEY idx_client (client_id),
KEY idx_uploaded_by (uploaded_by),
CONSTRAINT fk_cdv_doc FOREIGN KEY (document_id) REFERENCES client_documents(id) ON DELETE CASCADE,
CONSTRAINT fk_cdv_client FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='Per-document version history. Latest = client_documents.current_version.';
-- current_version column (guarded against re-run)
SET @col_exists := (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'client_documents'
AND COLUMN_NAME = 'current_version'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE client_documents ADD COLUMN current_version INT UNSIGNED NOT NULL DEFAULT 1 AFTER chunk_count',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- storage_path column (guarded)
SET @col_exists := (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'client_documents'
AND COLUMN_NAME = 'storage_path'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE client_documents ADD COLUMN storage_path VARCHAR(500) NULL AFTER original_filename',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+34
View File
@@ -0,0 +1,34 @@
-- DBN DMS migration 002 — soft-delete trash
-- Adds deleted_at / deleted_by columns to client_documents and client_folders.
-- client_documents.deleted_at
SET @col_exists := (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'client_documents' AND COLUMN_NAME = 'deleted_at'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE client_documents
ADD COLUMN deleted_at DATETIME NULL AFTER updated_at,
ADD COLUMN deleted_by INT UNSIGNED NULL AFTER deleted_at,
ADD KEY idx_deleted (deleted_at)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- client_folders.deleted_at (folders table may not exist if migration 119 hasn't run; guard table too)
SET @tbl_exists := (
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'client_folders'
);
SET @col_exists := IF(@tbl_exists = 0, 1,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'client_folders' AND COLUMN_NAME = 'deleted_at')
);
SET @sql := IF(@tbl_exists = 1 AND @col_exists = 0,
'ALTER TABLE client_folders
ADD COLUMN deleted_at DATETIME NULL,
ADD COLUMN deleted_by INT UNSIGNED NULL,
ADD KEY idx_deleted (deleted_at)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -0,0 +1,20 @@
-- DBN DMS migration 003 — saved searches / smart folders
CREATE TABLE IF NOT EXISTS client_saved_searches (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
name VARCHAR(120) NOT NULL,
icon VARCHAR(40) NULL,
color VARCHAR(7) NULL,
query_json JSON NOT NULL COMMENT '{q, category, tags[], folder_id, source_type, status, include_subfolders}',
is_shared TINYINT(1) NOT NULL DEFAULT 0,
sort_order SMALLINT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_client_user (client_id, user_id),
KEY idx_shared (client_id, is_shared),
CONSTRAINT fk_css_client FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE,
CONSTRAINT fk_css_user FOREIGN KEY (user_id) REFERENCES client_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='Persisted filter sets ("smart folders"). is_shared=1 = visible to whole tenant.';
@@ -0,0 +1,19 @@
-- DBN DMS migration 004 — tenant-managed categories
-- client_documents.category continues to store the slug as VARCHAR (back-compat).
-- This table just describes display metadata + lets tenants curate the list.
CREATE TABLE IF NOT EXISTS client_categories (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id INT UNSIGNED NOT NULL,
slug VARCHAR(50) NOT NULL,
label VARCHAR(100) NOT NULL,
color VARCHAR(7) NULL,
icon VARCHAR(40) NULL,
sort_order SMALLINT NOT NULL DEFAULT 0,
is_system TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 = seeded default, cannot be deleted',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_client_slug (client_id, slug),
KEY idx_client (client_id),
CONSTRAINT fk_cc_client FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='Per-tenant category dictionary. Slug is the value used in client_documents.category.';
+19
View File
@@ -0,0 +1,19 @@
-- DBN DMS migration 005 — audit log
-- Powers the "Recent activity" widget on the dashboard overview.
CREATE TABLE IF NOT EXISTS client_document_audit (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
client_id INT UNSIGNED NOT NULL,
user_id INT UNSIGNED NULL,
document_id INT UNSIGNED NULL,
folder_id INT UNSIGNED NULL,
action VARCHAR(40) NOT NULL
COMMENT 'upload|view|edit|move|rename|version|delete|restore|share|download|search|chat|category|tag',
details JSON NULL,
ip_addr VARCHAR(45) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_client_time (client_id, created_at),
KEY idx_doc (document_id),
KEY idx_user_time (user_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='Append-only audit trail for DMS activity. Document/folder FKs intentionally not enforced — keep history after deletes.';