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' &&
/data/storage
++ {t('settings.storage.localDesc', 'Dateien werden lokal im Docker-Volume gespeichert. Bei Bedarf auf S3 oder externe Storage-Provider umstellbar.')} +
+{t('settings.storage.s3Desc', 'S3-kompatibler Storage (AWS S3, MinIO, etc.)')}
++ {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.externalDesc', 'WebDAV, Nextcloud, Google Drive, Dropbox')}
++ {t('settings.storage.externalHint', 'Externe Storage-Provider werden als Plugins implementiert. In Zukunft hier konfigurierbar.')} +
+