bugfix(FIL-01 + files verschwunden nach hochladen und reload
This commit is contained in:
parent
18fb8e32b3
commit
d9f437f63e
3 changed files with 80 additions and 22 deletions
|
|
@ -1087,12 +1087,15 @@ class ComponentObjects:
|
|||
return newfileName
|
||||
counter += 1
|
||||
|
||||
def createFile(self, name: str, mimeType: str, content: bytes) -> FileItem:
|
||||
def createFile(self, name: str, mimeType: str, content: bytes, folderId: Optional[str] = None) -> FileItem:
|
||||
"""Creates a new file entry if user has permission. Computes fileHash and fileSize from content.
|
||||
|
||||
Duplicate check: if a file with the same user + fileHash + fileName already exists,
|
||||
the existing file is returned instead of creating a new one.
|
||||
Same hash with different name is allowed (intentional copy by user).
|
||||
|
||||
Args:
|
||||
folderId: Optional parent folder ID. None/empty means the root folder.
|
||||
"""
|
||||
if not self.checkRbacPermission(FileItem, "create"):
|
||||
raise PermissionError("No permission to create files")
|
||||
|
|
@ -1120,6 +1123,11 @@ class ComponentObjects:
|
|||
else:
|
||||
scope = "personal"
|
||||
|
||||
# Normalize folderId: treat empty string as "no folder" (= root) – NULL in DB
|
||||
normalizedFolderId: Optional[str] = folderId
|
||||
if isinstance(normalizedFolderId, str) and not normalizedFolderId.strip():
|
||||
normalizedFolderId = None
|
||||
|
||||
fileItem = FileItem(
|
||||
mandateId=mandateId,
|
||||
featureInstanceId=featureInstanceId,
|
||||
|
|
@ -1128,7 +1136,7 @@ class ComponentObjects:
|
|||
mimeType=mimeType,
|
||||
fileSize=fileSize,
|
||||
fileHash=fileHash,
|
||||
folderId="",
|
||||
folderId=normalizedFolderId,
|
||||
)
|
||||
|
||||
# Store in database
|
||||
|
|
@ -1842,14 +1850,18 @@ class ComponentObjects:
|
|||
logger.error(f"Error getting file content: {str(e)}")
|
||||
return None
|
||||
|
||||
def saveUploadedFile(self, fileContent: bytes, fileName: str) -> tuple[FileItem, str]:
|
||||
"""Saves an uploaded file if user has permission."""
|
||||
def saveUploadedFile(self, fileContent: bytes, fileName: str, folderId: Optional[str] = None) -> tuple[FileItem, str]:
|
||||
"""Saves an uploaded file if user has permission.
|
||||
|
||||
Args:
|
||||
folderId: Optional parent folder ID. None means root folder.
|
||||
"""
|
||||
try:
|
||||
# Check file creation permission
|
||||
if not self.checkRbacPermission(FileItem, "create"):
|
||||
raise PermissionError("No permission to upload files")
|
||||
|
||||
logger.debug(f"Starting upload process for file: {fileName}")
|
||||
logger.debug(f"Starting upload process for file: {fileName} (folderId={folderId!r})")
|
||||
|
||||
if not isinstance(fileContent, bytes):
|
||||
logger.error(f"Invalid fileContent type: {type(fileContent)}")
|
||||
|
|
@ -1874,7 +1886,8 @@ class ComponentObjects:
|
|||
fileItem = self.createFile(
|
||||
name=fileName,
|
||||
mimeType=mimeType,
|
||||
content=fileContent
|
||||
content=fileContent,
|
||||
folderId=folderId,
|
||||
)
|
||||
|
||||
# Save binary data
|
||||
|
|
|
|||
|
|
@ -393,6 +393,13 @@ def getRecordsetPaginatedWithRBAC(
|
|||
continue
|
||||
if key not in validColumns:
|
||||
continue
|
||||
if val is None:
|
||||
# val=None in pagination.filters means "match empty/null"
|
||||
# (same convention as connectorDbPostgre._buildPaginationClauses).
|
||||
# Covers both historical empty-string values and true NULLs
|
||||
# e.g. root-folder files where folderId may be "" or NULL.
|
||||
whereConditions.append(f'("{key}" IS NULL OR "{key}"::TEXT = \'\')')
|
||||
continue
|
||||
if isinstance(val, dict):
|
||||
op = val.get("operator", "equals")
|
||||
v = val.get("value", "")
|
||||
|
|
@ -569,6 +576,13 @@ def getDistinctColumnValuesWithRBAC(
|
|||
continue
|
||||
if key not in validColumns:
|
||||
continue
|
||||
if val is None:
|
||||
# val=None in pagination.filters means "match empty/null"
|
||||
# (same convention as connectorDbPostgre._buildPaginationClauses).
|
||||
# Covers both historical empty-string values and true NULLs
|
||||
# e.g. root-folder files where folderId may be "" or NULL.
|
||||
whereConditions.append(f'("{key}" IS NULL OR "{key}"::TEXT = \'\')')
|
||||
continue
|
||||
if isinstance(val, dict):
|
||||
op = val.get("operator", "equals")
|
||||
v = val.get("value", "")
|
||||
|
|
|
|||
|
|
@ -243,8 +243,16 @@ def get_files(
|
|||
|
||||
recordFilter = None
|
||||
if paginationParams and paginationParams.filters and "folderId" in paginationParams.filters:
|
||||
fVal = paginationParams.filters.pop("folderId")
|
||||
recordFilter = {"folderId": fVal}
|
||||
fVal = paginationParams.filters.get("folderId")
|
||||
# For a concrete folderId we use recordFilter (exact equality).
|
||||
# For null / empty (= "root") we keep it in pagination.filters so the
|
||||
# connector applies `IS NULL OR = ''` – files predating the folderId
|
||||
# fix were stored with an empty string instead of NULL.
|
||||
if fVal is None or (isinstance(fVal, str) and fVal.strip() == ""):
|
||||
paginationParams.filters["folderId"] = None
|
||||
else:
|
||||
paginationParams.filters.pop("folderId")
|
||||
recordFilter = {"folderId": fVal}
|
||||
|
||||
result = managementInterface.getAllFiles(pagination=paginationParams, recordFilter=recordFilter)
|
||||
|
||||
|
|
@ -282,13 +290,19 @@ async def upload_file(
|
|||
file: UploadFile = File(...),
|
||||
workflowId: Optional[str] = Form(None),
|
||||
featureInstanceId: Optional[str] = Form(None),
|
||||
currentUser: User = Depends(getCurrentUser)
|
||||
folderId: Optional[str] = Form(None),
|
||||
currentUser: User = Depends(getCurrentUser),
|
||||
context: RequestContext = Depends(getRequestContext),
|
||||
) -> JSONResponse:
|
||||
# Add fileName property to UploadFile for consistency with backend model
|
||||
file.fileName = file.filename
|
||||
"""Upload a file"""
|
||||
try:
|
||||
managementInterface = interfaceDbManagement.getInterface(currentUser)
|
||||
managementInterface = interfaceDbManagement.getInterface(
|
||||
currentUser,
|
||||
mandateId=str(context.mandateId) if context.mandateId else None,
|
||||
featureInstanceId=str(context.featureInstanceId) if context.featureInstanceId else None,
|
||||
)
|
||||
|
||||
# Read file
|
||||
fileContent = await file.read()
|
||||
|
|
@ -301,13 +315,30 @@ async def upload_file(
|
|||
detail=f"File too large. Maximum size: {interfaceDbManagement.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
|
||||
)
|
||||
|
||||
# Normalize folderId: empty string / "null" / "root" → None (root folder)
|
||||
normalizedFolderId: Optional[str] = folderId
|
||||
if isinstance(normalizedFolderId, str):
|
||||
trimmed = normalizedFolderId.strip()
|
||||
if not trimmed or trimmed.lower() in {"null", "none", "root"}:
|
||||
normalizedFolderId = None
|
||||
else:
|
||||
normalizedFolderId = trimmed
|
||||
|
||||
# Save file via LucyDOM interface in the database
|
||||
fileItem, duplicateType = managementInterface.saveUploadedFile(fileContent, file.filename)
|
||||
fileItem, duplicateType = managementInterface.saveUploadedFile(
|
||||
fileContent, file.filename, folderId=normalizedFolderId
|
||||
)
|
||||
|
||||
if featureInstanceId and not fileItem.featureInstanceId:
|
||||
managementInterface.updateFile(fileItem.id, {"featureInstanceId": featureInstanceId})
|
||||
fileItem.featureInstanceId = featureInstanceId
|
||||
|
||||
# For exact duplicates we keep the existing record, but move it into the
|
||||
# target folder so the user actually sees their upload land where they expect.
|
||||
if duplicateType == "exact_duplicate" and normalizedFolderId != getattr(fileItem, "folderId", None):
|
||||
managementInterface.updateFile(fileItem.id, {"folderId": normalizedFolderId})
|
||||
fileItem.folderId = normalizedFolderId
|
||||
|
||||
# Determine response message based on duplicate type
|
||||
if duplicateType == "exact_duplicate":
|
||||
message = f"File '{file.filename}' already exists with identical content. Reusing existing file."
|
||||
|
|
|
|||
Loading…
Reference in a new issue