ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: blob-дедупликация файлов

This commit is contained in:
DCCONSTRUCTIONS
2026-04-27 09:45:16 +03:00
parent b945bc4d31
commit 490aa1bc04
18 changed files with 827 additions and 114 deletions
@@ -116,6 +116,7 @@ class FileAssetSerializer(BaseSerializer):
"page",
"draft_issue",
"user",
"blob",
"is_deleted",
"deleted_at",
"storage_metadata",
@@ -678,6 +678,7 @@ class IssueAttachmentSerializer(BaseSerializer):
"workspace",
"project",
"issue",
"blob",
"updated_by",
"updated_at",
]
+53 -31
View File
@@ -15,7 +15,6 @@ from rest_framework.response import Response
from drf_spectacular.utils import OpenApiExample, OpenApiRequest
# Module Imports
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
from plane.settings.storage import S3Storage
from plane.db.models import FileAsset, User, Workspace
from plane.api.views.base import BaseAPIView
@@ -44,6 +43,11 @@ from plane.utils.openapi import (
)
from plane.utils.exception_logger import log_exception
from plane.utils.upload_limits import resolve_workspace_upload_size_limit
from plane.utils.file_dedup import (
UploadedObjectMissing,
confirm_uploaded_file_asset,
release_file_asset_blob,
)
class UserAssetEndpoint(BaseAPIView):
@@ -53,9 +57,11 @@ class UserAssetEndpoint(BaseAPIView):
asset = FileAsset.objects.filter(id=asset_id).first()
if asset is None:
return
if not asset.is_uploaded:
release_file_asset_blob(asset, delete_untracked_object=True)
asset.is_deleted = True
asset.deleted_at = timezone.now()
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save(update_fields=["is_deleted", "deleted_at", "updated_at"])
return
def entity_asset_delete(self, entity_type, asset, request):
@@ -209,15 +215,17 @@ class UserAssetEndpoint(BaseAPIView):
"""
# get the asset id
asset = FileAsset.objects.get(id=asset_id, user_id=request.user.id)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(asset_id))
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["is_uploaded", "attributes"])
try:
confirm_uploaded_file_asset(
asset,
request=request,
attributes=request.data.get("attributes", asset.attributes),
)
except UploadedObjectMissing:
return Response(
{"error": "The uploaded asset object was not found.", "status": False},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_204_NO_CONTENT)
@asset_docs(
@@ -236,11 +244,13 @@ class UserAssetEndpoint(BaseAPIView):
This performs a soft delete by marking the asset as deleted and updating the user's profile.
"""
asset = FileAsset.objects.get(id=asset_id, user_id=request.user.id)
if not asset.is_uploaded:
release_file_asset_blob(asset, request=request, delete_untracked_object=True)
asset.is_deleted = True
asset.deleted_at = timezone.now()
# get the entity and save the asset id for the request field
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save(update_fields=["is_deleted", "deleted_at", "updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -251,9 +261,11 @@ class UserServerAssetEndpoint(BaseAPIView):
asset = FileAsset.objects.filter(id=asset_id).first()
if asset is None:
return
if not asset.is_uploaded:
release_file_asset_blob(asset, delete_untracked_object=True)
asset.is_deleted = True
asset.deleted_at = timezone.now()
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save(update_fields=["is_deleted", "deleted_at", "updated_at"])
return
def entity_asset_delete(self, entity_type, asset, request):
@@ -365,15 +377,17 @@ class UserServerAssetEndpoint(BaseAPIView):
"""
# get the asset id
asset = FileAsset.objects.get(id=asset_id, user_id=request.user.id)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(asset_id))
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["is_uploaded", "attributes"])
try:
confirm_uploaded_file_asset(
asset,
request=request,
attributes=request.data.get("attributes", asset.attributes),
)
except UploadedObjectMissing:
return Response(
{"error": "The uploaded asset object was not found.", "status": False},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_204_NO_CONTENT)
@asset_docs(
@@ -393,11 +407,13 @@ class UserServerAssetEndpoint(BaseAPIView):
asset as deleted and updating the user's profile.
"""
asset = FileAsset.objects.get(id=asset_id, user_id=request.user.id)
if not asset.is_uploaded:
release_file_asset_blob(asset, request=request, delete_untracked_object=True)
asset.is_deleted = True
asset.deleted_at = timezone.now()
# get the entity and save the asset id for the request field
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save(update_fields=["is_deleted", "deleted_at", "updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -604,15 +620,21 @@ class GenericAssetEndpoint(BaseAPIView):
try:
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug, is_deleted=False)
# Update is_uploaded status
asset.is_uploaded = request.data.get("is_uploaded", asset.is_uploaded)
# Update storage metadata if not present
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(asset_id))
asset.save(update_fields=["is_uploaded"])
if request.data.get("is_uploaded", asset.is_uploaded):
confirm_uploaded_file_asset(
asset,
request=request,
attributes=request.data.get("attributes"),
)
else:
asset.is_uploaded = False
asset.save(update_fields=["is_uploaded", "updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
except FileAsset.DoesNotExist:
return Response({"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND)
except UploadedObjectMissing:
return Response(
{"error": "The uploaded asset object was not found.", "status": False},
status=status.HTTP_400_BAD_REQUEST,
)
+12 -12
View File
@@ -82,11 +82,11 @@ from plane.db.models import (
Workspace,
)
from plane.settings.storage import S3Storage
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
from .base import BaseAPIView
from plane.utils.host import base_host
from plane.utils.issue_relation_mapper import get_actual_relation
from plane.utils.attachment_preview import attachment_object_exists, get_attachment_preview_response
from plane.utils.file_dedup import finalize_uploaded_file_asset, release_file_asset_blob, UploadedObjectMissing
from plane.utils.upload_limits import resolve_workspace_upload_size_limit
from plane.bgtasks.webhook_task import model_activity
from plane.app.permissions import ROLE
@@ -2020,9 +2020,11 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView):
)
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
if not issue_attachment.is_uploaded:
release_file_asset_blob(issue_attachment, request=request, delete_untracked_object=True)
issue_attachment.is_deleted = True
issue_attachment.deleted_at = timezone.now()
issue_attachment.save()
issue_attachment.save(update_fields=["is_deleted", "deleted_at", "updated_at"])
issue_activity.delay(
type="attachment.activity.deleted",
@@ -2036,10 +2038,6 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView):
origin=base_host(request=request, is_app=True),
)
# Get the storage metadata
if not issue_attachment.storage_metadata:
get_asset_object_metadata.delay(str(issue_attachment.id))
issue_attachment.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@issue_attachment_docs(
@@ -2174,14 +2172,16 @@ class IssueAttachmentDetailAPIEndpoint(BaseAPIView):
origin=base_host(request=request, is_app=True),
)
# Update the attachment
issue_attachment.is_uploaded = True
issue_attachment.created_by = request.user
issue_attachment.save(update_fields=["created_by", "updated_at"])
# Get the storage metadata
if not issue_attachment.storage_metadata:
get_asset_object_metadata.delay(str(issue_attachment.id))
issue_attachment.save()
try:
finalize_uploaded_file_asset(issue_attachment, request=request)
except UploadedObjectMissing:
return Response(
{"error": "The uploaded attachment object was not found.", "status": False},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_204_NO_CONTENT)