diff --git a/PLATFORM_ROADMAP.md b/PLATFORM_ROADMAP.md index 87476df..bd9b9e2 100644 --- a/PLATFORM_ROADMAP.md +++ b/PLATFORM_ROADMAP.md @@ -386,6 +386,8 @@ Gemeinsame technische Storage-Schicht für alle File-Typen. Domainmodelle (DMS F | B-STOR | Bestehendes `core/storage.py` (Local/S3, save/read/delete, Path-Traversal-Schutz) gezielt erweitern/vereinheitlichen: MIME-Prüfung, Size-Limits, Hashing und fehlende gemeinsame Helfer | 2 Tage | | B-STOR-MIG | DMS, Mail, Kommunikation, AI Assistant nutzen gemeinsamen Storage-Layer. Eigene Upload-Endpoints bleiben bestehen (`/dms/files/upload`, `/mail/.../attachment`) | 2 Tage | | B-STOR-TEST | Storage-Tests (Path-Traversal, MIME, Size, Hash) | 1 Tag | +| B-STOR-EXT | **External Storage Plugin System** — `StorageProvider` Interface für externe Storage-Quellen (WebDAV, Nextcloud, Google Drive, Dropbox). Provider registrieren sich via Plugin-Manifest, DMS SourceTree zeigt externe Quellen an. Settings → System → Storage Reiter für Verwaltung | 3 Tage | +| B-STOR-WEBDAV | **WebDAV Storage Plugin** — Erster External Storage Provider als Plugin. Verbindet WebDAV-Server (Nextcloud, ownCloud, radicale). Browse, Upload, Download, Delete. Credentials in Settings → System → Storage konfigurierbar | 2 Tage | ### B.4 WebSocket Helpers diff --git a/app/plugins/builtins/dms/migrations/0001_initial.sql b/app/plugins/builtins/dms/migrations/0001_initial.sql index 041fc24..be2ae30 100644 --- a/app/plugins/builtins/dms/migrations/0001_initial.sql +++ b/app/plugins/builtins/dms/migrations/0001_initial.sql @@ -28,3 +28,6 @@ CREATE TABLE IF NOT EXISTS files ( CREATE INDEX IF NOT EXISTS ix_files_folder ON files(folder_id); CREATE INDEX IF NOT EXISTS ix_files_tenant ON files(tenant_id); CREATE INDEX IF NOT EXISTS ix_files_name ON files(name); + +-- Add content_hash column for deduplication +ALTER TABLE files ADD COLUMN IF NOT EXISTS content_hash VARCHAR(64); diff --git a/app/plugins/builtins/dms/routes.py b/app/plugins/builtins/dms/routes.py index 6432afe..b911347 100644 --- a/app/plugins/builtins/dms/routes.py +++ b/app/plugins/builtins/dms/routes.py @@ -498,7 +498,7 @@ async def delete_folder( # ─── Files ─── -@router.post("/files/upload", status_code=status.HTTP_201_CREATED, response_model=None, dependencies=[Depends(require_permission("dms:write"))]) +@router.post("/files/upload", status_code=status.HTTP_201_CREATED, response_model=FileMetadataResponse, dependencies=[Depends(require_permission("dms:write"))]) async def upload_file( file: UploadFile = File(...), folder_id: str | None = Form(None), diff --git a/frontend/src/api/dms.ts b/frontend/src/api/dms.ts index 80ad49c..f71d2e3 100644 --- a/frontend/src/api/dms.ts +++ b/frontend/src/api/dms.ts @@ -27,7 +27,7 @@ export interface DmsFile { mime_type: string; size_bytes?: number; storage_path: string; - checksum?: string | null; + content_hash?: string | null; deleted_at: string | null; uploaded_by?: string; created_at?: string | null; @@ -39,6 +39,8 @@ export interface DmsFile { size?: number; /** @deprecated Use uploaded_by instead */ created_by?: string; + /** @deprecated Use content_hash instead */ + checksum?: string | null; } export interface FilePermission { diff --git a/frontend/src/pages/SettingsSystem.tsx b/frontend/src/pages/SettingsSystem.tsx index 60f9956..86dab85 100644 --- a/frontend/src/pages/SettingsSystem.tsx +++ b/frontend/src/pages/SettingsSystem.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Code, BookOpen, FileJson } from 'lucide-react'; +import { Code, BookOpen, FileJson, HardDrive } from 'lucide-react'; import { SettingsMenuOrderPage } from './SettingsMenuOrder'; import { SettingsThemePage } from './SettingsTheme'; import { SettingsPluginsPage } from './SettingsPlugins'; @@ -13,6 +13,7 @@ export function SettingsSystemPage() { { key: 'menu', label: t('settings.menuOrder', 'Menü') }, { key: 'theme', label: t('settings.theme', 'Theme') }, { key: 'plugins', label: t('settings.plugins', 'Plugins') }, + { key: 'storage', label: t('settings.storage', 'Storage') }, { key: 'entwickler', label: t('settings.developer', 'Entwickler') }, ]; @@ -44,6 +45,76 @@ export function SettingsSystemPage() { {activeTab === 'menu' && } {activeTab === 'theme' && } {activeTab === 'plugins' && } + {activeTab === 'storage' && ( +
+
+
+
+ +
+
+

{t('settings.storage.local', 'Lokaler Storage')}

+

/data/storage

+
+
+

+ {t('settings.storage.localDesc', 'Dateien werden lokal im Docker-Volume gespeichert. Bei Bedarf auf S3 oder externe Storage-Provider umstellbar.')} +

+
+
+ Backend: + Local +
+
+ Pfad: + /data/storage +
+
+
+ +
+
+
+ +
+
+

{t('settings.storage.s3', 'S3 Storage')}

+

{t('settings.storage.s3Desc', 'S3-kompatibler Storage (AWS S3, MinIO, etc.)')}

+
+
+
+
+ Status + {t('settings.storage.notConfigured', 'Nicht konfiguriert')} +
+

+ {t('settings.storage.s3Hint', 'S3 wird via Environment-Variablen konfiguriert: STORAGE_BACKEND=s3, S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY')} +

+
+
+ +
+
+
+ +
+
+

{t('settings.storage.external', 'Externe Storage-Provider')}

+

{t('settings.storage.externalDesc', 'WebDAV, Nextcloud, Google Drive, Dropbox')}

+
+
+
+
+ {t('settings.storage.webdav', 'WebDAV')} + {t('settings.storage.comingSoon', 'In Entwicklung')} +
+

+ {t('settings.storage.externalHint', 'Externe Storage-Provider werden als Plugins implementiert. In Zukunft hier konfigurierbar.')} +

+
+
+
+ )} {activeTab === 'entwickler' && (