Phase 6: DMS & Attachments — Streaming, Deduplikation, API-Bereinigung
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
6.4 Upload streamen: - attachment_service.save_attachment: Streamt in 1MB Chunks statt await file.read() - routes/attachments.py: Uebergibt UploadFile direkt statt bytes 6.5 Download streamen: - DMS preview_file: FileResponse fuer LocalStorage (automatisches Streaming) - Kein storage.read() mehr fuer LocalStorage 6.6 Tenantlokale Deduplikation: - DMS Upload: Prueft content_hash vor Erstellung, wiederverwendet existierendes File - attachment_service: Dedup bereits vorhanden, jetzt mit Streaming kompatibel - Migration 0098: Partial Unique Index (tenant_id, content_hash) WHERE content_hash IS NOT NULL AND deleted_at IS NULL 6.7 API-Ausgabe bereinigt: - attachment_service: storage_path und content_hash aus API-Ausgaben entfernt - DMS routes: content_hash aus 4 API-Endpunkten entfernt Tests: 54/54 bestanden (17 Workspace + 13 API Token + 24 Command)
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"""Add tenant-local deduplication index on files.
|
||||
|
||||
Plan 6.6: Partial unique index on (tenant_id, content_hash)
|
||||
WHERE content_hash IS NOT NULL AND deleted_at IS NULL.
|
||||
|
||||
Before creating the unique index, checks for existing duplicates.
|
||||
|
||||
Revision ID: 0098
|
||||
Revises: 0097
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0098"
|
||||
down_revision = "0097"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Check for duplicates before creating unique index
|
||||
conn = op.get_bind()
|
||||
duplicates = conn.execute(
|
||||
sa.text(
|
||||
"SELECT tenant_id, content_hash, count(*) FROM files "
|
||||
"WHERE content_hash IS NOT NULL AND deleted_at IS NULL "
|
||||
"GROUP BY tenant_id, content_hash HAVING count(*) > 1"
|
||||
)
|
||||
).fetchall()
|
||||
|
||||
if duplicates:
|
||||
raise RuntimeError(
|
||||
f"Cannot create unique index: {len(duplicates)} duplicate (tenant_id, content_hash) pairs found. "
|
||||
"Data cleanup required before migration."
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS uq_files_tenant_content_hash "
|
||||
"ON files (tenant_id, content_hash) "
|
||||
"WHERE content_hash IS NOT NULL AND deleted_at IS NULL"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS uq_files_tenant_content_hash")
|
||||
Reference in New Issue
Block a user