Files
leocrm/alembic/versions/0098_files_dedup_index.py
T
Agent Zero 29d55cb187
Check Cross-Plugin Imports / check (push) Has been cancelled
Phase 6: DMS & Attachments — Streaming, Deduplikation, API-Bereinigung
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)
2026-08-03 14:21:43 +02:00

49 lines
1.3 KiB
Python

"""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")