diff --git a/apiserver/plane/app/views/asset.py b/apiserver/plane/app/views/asset.py index eddbb4505..31e002e15 100644 --- a/apiserver/plane/app/views/asset.py +++ b/apiserver/plane/app/views/asset.py @@ -39,10 +39,8 @@ class FileAssetEndpoint(BaseAPIView): def delete(self, request, workspace_id, asset_key): asset_key = str(workspace_id) + "/" + asset_key file_asset = FileAsset.objects.get(asset=asset_key) - # Delete the file from storage - file_asset.asset.delete(save=False) - # Delete the file object - file_asset.delete() + file_asset.is_deleted = True + file_asset.save() return Response(status=status.HTTP_204_NO_CONTENT) @@ -50,25 +48,25 @@ class UserAssetsEndpoint(BaseAPIView): parser_classes = (MultiPartParser, FormParser) def get(self, request, asset_key): - files = FileAsset.objects.filter(asset=asset_key, created_by=request.user) - if files.exists(): - serializer = FileAssetSerializer(files, context={"request": request}) - return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK) - else: - return Response({"error": "Asset key does not exist", "status": False}, status=status.HTTP_200_OK) + files = FileAsset.objects.filter(asset=asset_key, created_by=request.user) + if files.exists(): + serializer = FileAssetSerializer(files, context={"request": request}) + return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK) + else: + return Response({"error": "Asset key does not exist", "status": False}, status=status.HTTP_200_OK) def post(self, request): - serializer = FileAssetSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + serializer = FileAssetSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, asset_key): - file_asset = FileAsset.objects.get(asset=asset_key, created_by=request.user) - # Delete the file from storage - file_asset.asset.delete(save=False) - # Delete the file object - file_asset.delete() - return Response(status=status.HTTP_204_NO_CONTENT) + file_asset = FileAsset.objects.get(asset=asset_key, created_by=request.user) + # Delete the file from storage + file_asset.asset.delete(save=False) + # Delete the file object + file_asset.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/bgtasks/file_asset_task.py b/apiserver/plane/bgtasks/file_asset_task.py new file mode 100644 index 000000000..339d24583 --- /dev/null +++ b/apiserver/plane/bgtasks/file_asset_task.py @@ -0,0 +1,29 @@ +# Python imports +from datetime import timedelta + +# Django imports +from django.utils import timezone +from django.db.models import Q + +# Third party imports +from celery import shared_task + +# Module imports +from plane.db.models import FileAsset + + +@shared_task +def delete_file_asset(): + + # file assets to delete + file_assets_to_delete = FileAsset.objects.filter( + Q(is_deleted=True) & Q(updated_at__lte=timezone.now() - timedelta(days=7)) + ) + + # Delete the file from storage and the file object from the database + for file_asset in file_assets_to_delete: + # Delete the file from storage + file_asset.asset.delete(save=False) + # Delete the file object + file_asset.delete() + diff --git a/apiserver/plane/celery.py b/apiserver/plane/celery.py index dfb094339..4d4e3475b 100644 --- a/apiserver/plane/celery.py +++ b/apiserver/plane/celery.py @@ -24,6 +24,10 @@ app.conf.beat_schedule = { "task": "plane.bgtasks.exporter_expired_task.delete_old_s3_link", "schedule": crontab(hour=0, minute=0), }, + "check-every-day-to-delete-file-asset": { + "task": "plane.bgtasks.file_asset_task.delete_file_asset", + "schedule": crontab(hour=0, minute=0), + }, } # Load task modules from all registered Django app configs. diff --git a/apiserver/plane/db/migrations/0051_fileasset_is_deleted.py b/apiserver/plane/db/migrations/0051_fileasset_is_deleted.py new file mode 100644 index 000000000..914852bfd --- /dev/null +++ b/apiserver/plane/db/migrations/0051_fileasset_is_deleted.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-11-20 08:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0050_user_use_case_alter_workspace_organization_size'), + ] + + operations = [ + migrations.AddField( + model_name='fileasset', + name='is_deleted', + field=models.BooleanField(default=False), + ), + ] diff --git a/apiserver/plane/db/models/asset.py b/apiserver/plane/db/models/asset.py index 01ef1d9d8..ab3c38d9c 100644 --- a/apiserver/plane/db/models/asset.py +++ b/apiserver/plane/db/models/asset.py @@ -36,6 +36,7 @@ class FileAsset(BaseModel): workspace = models.ForeignKey( "db.Workspace", on_delete=models.CASCADE, null=True, related_name="assets" ) + is_deleted = models.BooleanField(default=False) class Meta: verbose_name = "File Asset" diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 2cf94a68c..8b81102fe 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -285,6 +285,7 @@ else: CELERY_IMPORTS = ( "plane.bgtasks.issue_automation_task", "plane.bgtasks.exporter_expired_task", + "plane.bgtasks.file_asset_task", ) # Sentry Settings