diff --git a/apiserver/plane/graphql/mutations/asset/project.py b/apiserver/plane/graphql/mutations/asset/project.py index 4ab8be2f1c..668566283a 100644 --- a/apiserver/plane/graphql/mutations/asset/project.py +++ b/apiserver/plane/graphql/mutations/asset/project.py @@ -24,7 +24,7 @@ from plane.settings.storage import S3Storage from plane.graphql.types.asset import ( FileAssetType, AssetPresignedUrlResponseType, - UserAssetFileEnumType, + FileAssetAssetType, ProjectAssetEnumType, ) from plane.bgtasks.storage_metadata_task import get_asset_object_metadata @@ -63,21 +63,18 @@ def get_entity_id_field(entity_type, entity_identifier): if entity_type == ProjectAssetEnumType.PROJECT_COVER.value: return {"project_id": entity_identifier} - if entity_type in [ - ProjectAssetEnumType.ISSUE_ATTACHMENT.value, - ProjectAssetEnumType.ISSUE_DESCRIPTION.value, - ]: - return {"issue_id": entity_identifier} - if entity_type == ProjectAssetEnumType.PAGE_DESCRIPTION.value: return {"page_id": entity_identifier} - if entity_type == ProjectAssetEnumType.COMMENT_DESCRIPTION.value: - return {"comment_id": entity_identifier} + if entity_type == ProjectAssetEnumType.ISSUE_DESCRIPTION.value: + return {"issue_id": entity_identifier} if entity_type == ProjectAssetEnumType.DRAFT_ISSUE_DESCRIPTION.value: return {"draft_issue_id": entity_identifier} + if entity_type == ProjectAssetEnumType.COMMENT_DESCRIPTION.value: + return {"comment_id": entity_identifier} + return {} @@ -131,11 +128,12 @@ class ProjectAssetMutation: # Checking if the entity type is valid if not entity_type or entity_type not in [ ProjectAssetEnumType.PROJECT_COVER, - ProjectAssetEnumType.ISSUE_ATTACHMENT, - ProjectAssetEnumType.ISSUE_DESCRIPTION, - ProjectAssetEnumType.COMMENT_DESCRIPTION, ProjectAssetEnumType.PAGE_DESCRIPTION, + ProjectAssetEnumType.ISSUE_DESCRIPTION, ProjectAssetEnumType.DRAFT_ISSUE_DESCRIPTION, + ProjectAssetEnumType.ISSUE_ATTACHMENT, + ProjectAssetEnumType.DRAFT_ISSUE_ATTACHMENT, + ProjectAssetEnumType.COMMENT_DESCRIPTION, ]: message = "Invalid entity type." error_extensions = {"code": "INVALID_ENTITY_TYPE", "statusCode": 400} @@ -143,11 +141,11 @@ class ProjectAssetMutation: # Checking if the file type is valid allowed_types = [ - UserAssetFileEnumType.IMAGE_JPEG.value, - UserAssetFileEnumType.IMAGE_PNG.value, - UserAssetFileEnumType.IMAGE_WEBP.value, - UserAssetFileEnumType.IMAGE_JPG.value, - UserAssetFileEnumType.IMAGE_GIF.value, + FileAssetAssetType.IMAGE_JPEG.value, + FileAssetAssetType.IMAGE_PNG.value, + FileAssetAssetType.IMAGE_WEBP.value, + FileAssetAssetType.IMAGE_JPG.value, + FileAssetAssetType.IMAGE_GIF.value, ] if type not in allowed_types: message = "Invalid file type. Only JPEG and PNG files are allowed." @@ -283,24 +281,24 @@ class ProjectAssetMutation: project_details = await get_project(project) await save_project_cover(project_details, asset.id) - if asset.entity_type == ProjectAssetEnumType.ISSUE_DESCRIPTION.value: - for asset in assets: - asset.issue_id = entity_id - await sync_to_async(asset.save)(update_fields=["issue_id"]) - - if asset.entity_type == ProjectAssetEnumType.COMMENT_DESCRIPTION.value: - for asset in assets: - asset.comment_id = entity_id - await sync_to_async(asset.save)(update_fields=["comment_id"]) - if asset.entity_type == ProjectAssetEnumType.PAGE_DESCRIPTION.value: for asset in assets: asset.page_id = entity_id await sync_to_async(asset.save)(update_fields=["page_id"]) + if asset.entity_type == ProjectAssetEnumType.ISSUE_DESCRIPTION.value: + for asset in assets: + asset.issue_id = entity_id + await sync_to_async(asset.save)(update_fields=["issue_id"]) + if asset.entity_type == ProjectAssetEnumType.DRAFT_ISSUE_DESCRIPTION.value: for asset in assets: asset.draft_issue_id = entity_id await sync_to_async(asset.save)(update_fields=["draft_issue_id"]) + if asset.entity_type == ProjectAssetEnumType.COMMENT_DESCRIPTION.value: + for asset in assets: + asset.comment_id = entity_id + await sync_to_async(asset.save)(update_fields=["comment_id"]) + return True diff --git a/apiserver/plane/graphql/mutations/asset/user.py b/apiserver/plane/graphql/mutations/asset/user.py index 719b3a1f4d..cf94ceede0 100644 --- a/apiserver/plane/graphql/mutations/asset/user.py +++ b/apiserver/plane/graphql/mutations/asset/user.py @@ -23,7 +23,7 @@ from plane.settings.storage import S3Storage from plane.graphql.types.asset import ( FileAssetType, AssetPresignedUrlResponseType, - UserAssetFileEnumType, + FileAssetAssetType, UserAssetEnumType, ) from plane.bgtasks.storage_metadata_task import get_asset_object_metadata @@ -127,11 +127,11 @@ class UserAssetMutation: # Checking if the file type is valid allowed_types = [ - UserAssetFileEnumType.IMAGE_JPEG.value, - UserAssetFileEnumType.IMAGE_PNG.value, - UserAssetFileEnumType.IMAGE_WEBP.value, - UserAssetFileEnumType.IMAGE_JPG.value, - UserAssetFileEnumType.IMAGE_GIF.value, + FileAssetAssetType.IMAGE_JPEG.value, + FileAssetAssetType.IMAGE_PNG.value, + FileAssetAssetType.IMAGE_WEBP.value, + FileAssetAssetType.IMAGE_JPG.value, + FileAssetAssetType.IMAGE_GIF.value, ] if type not in allowed_types: message = "Invalid file type. Only JPEG and PNG files are allowed." diff --git a/apiserver/plane/graphql/mutations/asset/workspace.py b/apiserver/plane/graphql/mutations/asset/workspace.py index 71d7abe362..9def936349 100644 --- a/apiserver/plane/graphql/mutations/asset/workspace.py +++ b/apiserver/plane/graphql/mutations/asset/workspace.py @@ -24,7 +24,7 @@ from plane.settings.storage import S3Storage from plane.graphql.types.asset import ( FileAssetType, AssetPresignedUrlResponseType, - UserAssetFileEnumType, + FileAssetAssetType, WorkspaceAssetEnumType, ) from plane.bgtasks.storage_metadata_task import get_asset_object_metadata @@ -187,11 +187,11 @@ class WorkspaceAssetMutation: # Checking if the file type is valid allowed_types = [ - UserAssetFileEnumType.IMAGE_JPEG.value, - UserAssetFileEnumType.IMAGE_PNG.value, - UserAssetFileEnumType.IMAGE_WEBP.value, - UserAssetFileEnumType.IMAGE_JPG.value, - UserAssetFileEnumType.IMAGE_GIF.value, + FileAssetAssetType.IMAGE_JPEG.value, + FileAssetAssetType.IMAGE_PNG.value, + FileAssetAssetType.IMAGE_WEBP.value, + FileAssetAssetType.IMAGE_JPG.value, + FileAssetAssetType.IMAGE_GIF.value, ] if type not in allowed_types: message = "Invalid file type. Only JPEG and PNG files are allowed." diff --git a/apiserver/plane/graphql/mutations/issue.py b/apiserver/plane/graphql/mutations/issue.py index 06ebde168d..bc8ab166a4 100644 --- a/apiserver/plane/graphql/mutations/issue.py +++ b/apiserver/plane/graphql/mutations/issue.py @@ -439,48 +439,6 @@ class IssueUserPropertyMutation: return issue_properties -@strawberry.type -class IssueAttachmentMutation: - # @strawberry.mutation( - # extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] - # ) - # def upload_file(self, file: Upload, info: Info) -> bool: - # content = file.read() - # filename = file.filename - # @strawberry.mutation( - # extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] - # ) - # async def upload_file(self, file: Upload, info: Info) -> bool: - # content = await sync_to_async(file.read)() - # filename = file.filename - - # # Save the file using Django's file storage - # await sync_to_async(default_storage.save)(filename, content) - - # return True - - @strawberry.mutation( - extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] - ) - async def deleteIssueAttachment( - self, - info: Info, - slug: str, - project: strawberry.ID, - issue: strawberry.ID, - attachment: strawberry.ID, - ) -> bool: - issue_attachment = await sync_to_async(FileAsset.objects.get)( - id=attachment, - entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, - entity_identifier=issue, - project_id=project, - workspace__slug=slug, - ) - await sync_to_async(issue_attachment.delete)() - return True - - @strawberry.type class IssueSubscriptionMutation: @strawberry.mutation( diff --git a/apiserver/plane/graphql/mutations/issues/__init__.py b/apiserver/plane/graphql/mutations/issues/__init__.py index f606487fd5..c2e70c6904 100644 --- a/apiserver/plane/graphql/mutations/issues/__init__.py +++ b/apiserver/plane/graphql/mutations/issues/__init__.py @@ -4,3 +4,4 @@ from .sub_issue import SubIssueMutation from .cycle import IssueCycleMutation from .module import IssueModuleMutation from .links import IssueLinkMutation +from .attachment import IssueAttachmentMutation diff --git a/apiserver/plane/graphql/mutations/issues/attachment.py b/apiserver/plane/graphql/mutations/issues/attachment.py new file mode 100644 index 0000000000..0f9e420c73 --- /dev/null +++ b/apiserver/plane/graphql/mutations/issues/attachment.py @@ -0,0 +1,235 @@ +# Python imports +import json +import requests +import uuid +from typing import Optional + +# Django imports +from django.conf import settings +from django.utils import timezone + +# Third-Party Imports +import strawberry +from strawberry.scalars import JSON +from strawberry.exceptions import GraphQLError + +# Python Standard Library Imports +from asgiref.sync import sync_to_async + +# Strawberry Imports +from strawberry.types import Info +from strawberry.permission import PermissionExtension + +# Module Imports +from plane.settings.storage import S3Storage +from plane.db.models import FileAsset, Workspace +from plane.graphql.types.asset import FileAssetType, FileAssetEntityType +from plane.graphql.types.issues.attachment import ( + IssueAttachmentPresignedUrlResponseType, +) +from plane.graphql.permissions.project import ProjectBasePermission +from plane.bgtasks.issue_activities_task import issue_activity +from plane.bgtasks.storage_metadata_task import get_asset_object_metadata + + +@sync_to_async +def validate_feature_flag(slug: str, user_id: str, feature_key: str) -> bool: + url = f"{settings.FEATURE_FLAG_SERVER_BASE_URL}/api/feature-flags/" + json = {"workspace_slug": slug, "user_id": user_id, "flag_key": feature_key} + headers = { + "content-type": "application/json", + "x-api-key": settings.FEATURE_FLAG_SERVER_AUTH_TOKEN, + } + response = requests.post(url, json=json, headers=headers) + response.raise_for_status() + return response.json().get("value", False) + + +@sync_to_async +def get_workspace(slug): + try: + return Workspace.objects.get(slug=slug) + except Workspace.DoesNotExist: + return None + + +@sync_to_async +def create_file_asset( + workspace, project_id, issue_id, attributes, asset_key, size, user_id +) -> FileAssetType: + return FileAsset.objects.create( + workspace=workspace, + project_id=project_id, + issue_id=issue_id, + attributes=attributes, + asset=asset_key, + size=size, + created_by_id=user_id, + entity_type=FileAssetEntityType.ISSUE_ATTACHMENT.value, + ) + + +@strawberry.type +class IssueAttachmentMutation: + @strawberry.mutation( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def create_issue_attachment( + self, + info: Info, + slug: str, + project: strawberry.ID, + issue: strawberry.ID, + name: str, + type: str, + size: int, + ) -> IssueAttachmentPresignedUrlResponseType: + user = info.context.user + + # Checking if the file size is within the limit + size_limit = min(size, settings.FILE_SIZE_LIMIT) + if validate_feature_flag( + slug=slug, user_id=str(user.id), feature_key="FILE_SIZE_LIMIT_PRO" + ): + size_limit = min(size, settings.PRO_FILE_SIZE_LIMIT) + + if type not in settings.ATTACHMENT_MIME_TYPES: + message = "Invalid file type." + error_extensions = {"code": "INVALID_FILE_TYPE", "statusCode": 400} + raise GraphQLError(message, extensions=error_extensions) + + # getting the workspace + workspace = await get_workspace(slug=slug) + if workspace is None: + message = "Workspace not found." + error_extensions = {"code": "WORKSPACE_NOT_FOUND", "statusCode": 404} + raise GraphQLError(message, extensions=error_extensions) + + # asset key + asset_key = f"{workspace.id}/{uuid.uuid4().hex}-{name}" + + # creating a file asset + attachment = await create_file_asset( + workspace=workspace, + project_id=project, + issue_id=issue, + attributes={"name": name, "type": type, "size": size_limit}, + asset_key=asset_key, + size=size_limit, + user_id=user.id, + ) + + # Get the presigned URL + storage = S3Storage(request=info.context["request"]) + + # Generate a presigned URL to share an S3 object + presigned_url_data = await sync_to_async(storage.generate_presigned_post)( + object_name=asset_key, file_type=type, file_size=size_limit + ) + + # Return the presigned URL + return IssueAttachmentPresignedUrlResponseType( + upload_data=presigned_url_data, + attachment_id=str(attachment.id), + asset_url=attachment.asset_url, + ) + + @strawberry.mutation( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def update_issue_attachment( + self, + info: Info, + slug: str, + project: strawberry.ID, + issue: strawberry.ID, + attachment: strawberry.ID, + attributes: Optional[JSON] = None, + ) -> FileAssetType: + user = info.context.user + issue_attachment = await sync_to_async(FileAsset.objects.get)( + workspace__slug=slug, + project_id=project, + entity_type=FileAssetEntityType.ISSUE_ATTACHMENT.value, + issue=issue, + id=attachment, + ) + + if not issue_attachment.is_uploaded: + issue_activity.delay( + type="attachment.activity.created", + requested_data=None, + actor_id=str(info.context.user.id), + issue_id=str(issue), + project_id=str(project), + current_instance=json.dumps({"id": str(attachment)}), + epoch=int(timezone.now().timestamp()), + notification=True, + origin=info.context.request.META.get("HTTP_ORIGIN"), + ) + + issue_attachment.is_uploaded = True + issue_attachment.created_by = user + + # get the storage metadata + if not issue_attachment.storage_metadata: + get_asset_object_metadata.delay(asset_id=str(attachment)) + + # update the file asset attributes + if attributes is not None: + issue_attachment.attributes = attributes + else: + issue_attachment.attributes = issue_attachment.attributes + + # save the file asset + await sync_to_async(issue_attachment.save)( + update_fields=["is_uploaded", "created_by", "attributes"] + ) + + return issue_attachment + + @strawberry.mutation( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def delete_issue_attachment( + self, + info: Info, + slug: str, + project: strawberry.ID, + issue: strawberry.ID, + attachment: strawberry.ID, + ) -> bool: + issue_attachment = await sync_to_async(FileAsset.objects.get)( + workspace__slug=slug, + project_id=project, + entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, + issue=issue, + id=attachment, + ) + + if not issue_attachment: + message = "Attachment not found." + error_extensions = {"code": "ATTACHMENT_NOT_FOUND", "statusCode": 400} + raise GraphQLError(message, extensions=error_extensions) + + issue_attachment.is_deleted = True + issue_attachment.deleted_at = timezone.now() + + # save the file asset + await sync_to_async(issue_attachment.save)( + update_fields=["is_deleted", "deleted_at"] + ) + + issue_activity.delay( + type="attachment.activity.deleted", + requested_data=None, + actor_id=str(info.context.user.id), + issue_id=str(issue), + project_id=str(project), + current_instance=None, + epoch=int(timezone.now().timestamp()), + notification=True, + origin=info.context.request.META.get("HTTP_ORIGIN"), + ) + + return True diff --git a/apiserver/plane/graphql/queries/issues/attachment.py b/apiserver/plane/graphql/queries/issues/attachment.py index 405055872d..de6b773880 100644 --- a/apiserver/plane/graphql/queries/issues/attachment.py +++ b/apiserver/plane/graphql/queries/issues/attachment.py @@ -7,10 +7,11 @@ from asgiref.sync import sync_to_async # Strawberry Imports from strawberry.types import Info from strawberry.permission import PermissionExtension +from strawberry.exceptions import GraphQLError # Module Imports from plane.db.models import FileAsset -from plane.graphql.types.issues.attachment import IssueAttachmentType +from plane.graphql.types.asset import FileAssetType from plane.graphql.permissions.project import ProjectBasePermission @@ -21,14 +22,41 @@ class IssueAttachmentQuery: ) async def issue_attachment( self, info: Info, slug: str, project: strawberry.ID, issue: strawberry.ID - ) -> list[IssueAttachmentType]: + ) -> list[FileAssetType]: issue_attachments = await sync_to_async(list)( FileAsset.objects.filter( workspace__slug=slug, project_id=project, entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, issue=issue, + is_uploaded=True, ) ) return issue_attachments + + @strawberry.field( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def issue_attachment_detail( + self, + info: Info, + slug: str, + project: strawberry.ID, + issue: strawberry.ID, + attachment: strawberry.ID, + ) -> FileAssetType: + issue_attachment = await sync_to_async(FileAsset.objects.get)( + workspace__slug=slug, + project_id=project, + entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, + issue=issue, + id=attachment, + ) + + if not issue_attachment.is_uploaded: + message = "The attachment is not uploaded." + error_extensions = {"code": "ATTACHMENT_NOT_UPLOADED", "statusCode": 400} + raise GraphQLError(message, extensions=error_extensions) + + return issue_attachment diff --git a/apiserver/plane/graphql/schema.py b/apiserver/plane/graphql/schema.py index dadfdc20d4..e1e69606eb 100644 --- a/apiserver/plane/graphql/schema.py +++ b/apiserver/plane/graphql/schema.py @@ -64,7 +64,6 @@ from .mutations.project import ( from .mutations.issue import ( IssueMutation, IssueUserPropertyMutation, - IssueAttachmentMutation, IssueSubscriptionMutation, ) from .mutations.notification import NotificationMutation @@ -88,6 +87,7 @@ from .mutations.issues import ( IssueModuleMutation, IssueCycleMutation, IssueLinkMutation, + IssueAttachmentMutation, ) from .mutations.device import DeviceInformationMutation from .mutations.asset import ( diff --git a/apiserver/plane/graphql/types/asset/__init__.py b/apiserver/plane/graphql/types/asset/__init__.py index a730b4751b..553d7029b7 100644 --- a/apiserver/plane/graphql/types/asset/__init__.py +++ b/apiserver/plane/graphql/types/asset/__init__.py @@ -1,4 +1,9 @@ -from .base import FileAssetType, AssetPresignedUrlResponseType, UserAssetFileEnumType +from .base import ( + FileAssetAssetType, + FileAssetEntityType, + FileAssetType, + AssetPresignedUrlResponseType, +) from .user import UserAssetEnumType from .workspace import WorkspaceAssetEnumType from .project import ProjectAssetEnumType diff --git a/apiserver/plane/graphql/types/asset/base.py b/apiserver/plane/graphql/types/asset/base.py index 699e5f3e1b..0f856fffa2 100644 --- a/apiserver/plane/graphql/types/asset/base.py +++ b/apiserver/plane/graphql/types/asset/base.py @@ -1,6 +1,7 @@ # Python imports from typing import Optional from enum import Enum +from datetime import datetime # Strawberry imports import strawberry @@ -11,35 +12,8 @@ from strawberry.scalars import JSON from plane.db.models import FileAsset -@strawberry_django.type(FileAsset) -class FileAssetType: - id: strawberry.ID - attributes: JSON - asset: str - entity_type: str - size: float - is_uploaded: bool - storage_metadata: Optional[JSON] - user: strawberry.ID - workspace: strawberry.ID - project: Optional[strawberry.ID] - issue: Optional[strawberry.ID] - comment: Optional[strawberry.ID] - page: Optional[strawberry.ID] - draft_issue: Optional[strawberry.ID] - is_archived: bool - is_deleted: bool - deleted_at: Optional[str] - created_by: Optional[strawberry.ID] - updated_by: Optional[strawberry.ID] - created_at: str - updated_at: str - # model properties - asset_url: Optional[str] - - @strawberry.enum -class UserAssetFileEnumType(Enum): +class FileAssetAssetType(Enum): IMAGE_JPEG = "image/jpeg" IMAGE_PNG = "image/png" IMAGE_WEBP = "image/webp" @@ -50,6 +24,66 @@ class UserAssetFileEnumType(Enum): return self.value +@strawberry.enum +class FileAssetEntityType(Enum): + USER_COVER = "USER_COVER" + USER_AVATAR = "USER_AVATAR" + + WORKSPACE_LOGO = "WORKSPACE_LOGO" + + PROJECT_COVER = "PROJECT_COVER" + PROJECT_DESCRIPTION = "PROJECT_DESCRIPTION" + PROJECT_ATTACHMENT = "PROJECT_ATTACHMENT" + + PAGE_DESCRIPTION = "PAGE_DESCRIPTION" + + ISSUE_DESCRIPTION = "ISSUE_DESCRIPTION" + DRAFT_ISSUE_DESCRIPTION = "DRAFT_ISSUE_DESCRIPTION" + COMMENT_DESCRIPTION = "COMMENT_DESCRIPTION" + ISSUE_ATTACHMENT = "ISSUE_ATTACHMENT" + DRAFT_ISSUE_ATTACHMENT = "DRAFT_ISSUE_ATTACHMENT" + + INITIATIVE_DESCRIPTION = "INITIATIVE_DESCRIPTION" + INITIATIVE_ATTACHMENT = "INITIATIVE_ATTACHMENT" + INITIATIVE_COMMENT_DESCRIPTION = "INITIATIVE_COMMENT_DESCRIPTION" + + TEAM_SPACE_DESCRIPTION = "TEAM_SPACE_DESCRIPTION" + TEAM_SPACE_COMMENT_DESCRIPTION = "TEAM_SPACE_COMMENT_DESCRIPTION" + + def __str__(self): + return self.value + + +@strawberry_django.type(FileAsset) +class FileAssetType: + id: strawberry.ID + attributes: JSON + asset: str + size: float + entity_type: FileAssetEntityType + entity_identifier: Optional[strawberry.ID] + storage_metadata: Optional[JSON] + user: strawberry.ID + workspace: strawberry.ID + draft_issue: Optional[strawberry.ID] + project: Optional[strawberry.ID] + issue: Optional[strawberry.ID] + comment: Optional[strawberry.ID] + page: Optional[strawberry.ID] + is_uploaded: bool + is_archived: bool + is_deleted: bool + deleted_at: Optional[str] + external_id: Optional[strawberry.ID] + external_source: Optional[str] + created_by: Optional[strawberry.ID] + updated_by: Optional[strawberry.ID] + created_at: Optional[datetime] + updated_at: Optional[datetime] + # model properties + asset_url: Optional[str] + + @strawberry.type class AssetPresignedUrlResponseType: upload_data: JSON diff --git a/apiserver/plane/graphql/types/asset/project.py b/apiserver/plane/graphql/types/asset/project.py index 2380c7bea8..c038befb1e 100644 --- a/apiserver/plane/graphql/types/asset/project.py +++ b/apiserver/plane/graphql/types/asset/project.py @@ -4,15 +4,19 @@ from enum import Enum # Strawberry imports import strawberry +# module imports +from plane.graphql.types.asset.base import FileAssetEntityType + @strawberry.enum class ProjectAssetEnumType(Enum): - PROJECT_COVER = "PROJECT_COVER" - ISSUE_ATTACHMENT = "ISSUE_ATTACHMENT" - ISSUE_DESCRIPTION = "ISSUE_DESCRIPTION" - PAGE_DESCRIPTION = "PAGE_DESCRIPTION" - COMMENT_DESCRIPTION = "COMMENT_DESCRIPTION" - DRAFT_ISSUE_DESCRIPTION = "DRAFT_ISSUE_DESCRIPTION" + PROJECT_COVER = FileAssetEntityType.PROJECT_COVER.value + PAGE_DESCRIPTION = FileAssetEntityType.PAGE_DESCRIPTION.value + ISSUE_DESCRIPTION = FileAssetEntityType.ISSUE_DESCRIPTION.value + DRAFT_ISSUE_DESCRIPTION = FileAssetEntityType.DRAFT_ISSUE_DESCRIPTION.value + COMMENT_DESCRIPTION = FileAssetEntityType.COMMENT_DESCRIPTION.value + ISSUE_ATTACHMENT = FileAssetEntityType.ISSUE_ATTACHMENT.value + DRAFT_ISSUE_ATTACHMENT = FileAssetEntityType.DRAFT_ISSUE_ATTACHMENT.value def __str__(self): return self.value diff --git a/apiserver/plane/graphql/types/asset/user.py b/apiserver/plane/graphql/types/asset/user.py index 7354416d04..3e837b318d 100644 --- a/apiserver/plane/graphql/types/asset/user.py +++ b/apiserver/plane/graphql/types/asset/user.py @@ -4,11 +4,14 @@ from enum import Enum # Strawberry imports import strawberry +# module imports +from plane.graphql.types.asset.base import FileAssetEntityType + @strawberry.enum class UserAssetEnumType(Enum): - USER_AVATAR = "USER_AVATAR" - USER_COVER = "USER_COVER" + USER_AVATAR = FileAssetEntityType.USER_AVATAR.value + USER_COVER = FileAssetEntityType.USER_COVER.value def __str__(self): return self.value diff --git a/apiserver/plane/graphql/types/asset/workspace.py b/apiserver/plane/graphql/types/asset/workspace.py index 89042ac79f..2f469e4b15 100644 --- a/apiserver/plane/graphql/types/asset/workspace.py +++ b/apiserver/plane/graphql/types/asset/workspace.py @@ -4,12 +4,15 @@ from enum import Enum # Strawberry imports import strawberry +# module imports +from plane.graphql.types.asset.base import FileAssetEntityType + @strawberry.enum class WorkspaceAssetEnumType(Enum): - WORKSPACE_LOGO = "WORKSPACE_LOGO" - PROJECT_COVER = "PROJECT_COVER" - PAGE_DESCRIPTION = "PAGE_DESCRIPTION" + WORKSPACE_LOGO = FileAssetEntityType.WORKSPACE_LOGO.value + PROJECT_COVER = FileAssetEntityType.PROJECT_COVER.value + PAGE_DESCRIPTION = FileAssetEntityType.PAGE_DESCRIPTION.value def __str__(self): return self.value diff --git a/apiserver/plane/graphql/types/issues/attachment.py b/apiserver/plane/graphql/types/issues/attachment.py index a8193addb5..be105348eb 100644 --- a/apiserver/plane/graphql/types/issues/attachment.py +++ b/apiserver/plane/graphql/types/issues/attachment.py @@ -3,62 +3,11 @@ from typing import Optional # Strawberry imports import strawberry -import strawberry_django from strawberry.scalars import JSON -# Module Imports -from plane.db.models import FileAsset - -@strawberry_django.type(FileAsset) -class IssueAttachmentType: - id: strawberry.ID - user: strawberry.ID - workspace: strawberry.ID - draft_issue: strawberry.ID - project: strawberry.ID - issue: strawberry.ID - comment: strawberry.ID - page: strawberry.ID - - attributes: Optional[JSON] - asset: str - entity_type: str - is_deleted: bool - is_archived: bool - - size: float - is_uploaded: bool - storage_metadata: Optional[JSON] +@strawberry.type +class IssueAttachmentPresignedUrlResponseType: + upload_data: JSON + attachment_id: str asset_url: Optional[str] - - external_id: strawberry.ID - external_source: str - - @strawberry.field - def user(self): - return self.user_id - - @strawberry.field - def workspace(self): - return self.workspace_id - - @strawberry.field - def draft_issue(self): - return self.draft_issue_id - - @strawberry.field - def project(self): - return self.project_id - - @strawberry.field - def issue(self): - return self.external_id - - @strawberry.field - def comment(self): - return self.comment_id - - @strawberry.field - def page(self): - return self.page_id