Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 135 additions & 15 deletions apiserver/plane/app/views/asset/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from plane.settings.storage import S3Storage
from plane.app.permissions import allow_permission, ROLE
from plane.utils.cache import invalidate_cache_directly


class UserAssetsV2Endpoint(BaseAPIView):
Expand All @@ -35,7 +36,7 @@ def asset_delete(self, asset_id):
asset.save()
return

def entity_asset_save(self, asset_id, entity_type, asset):
def entity_asset_save(self, asset_id, entity_type, asset, request):
# User Avatar
if entity_type == FileAsset.EntityTypeContext.USER_AVATAR:
user = User.objects.get(id=asset.user_id)
Expand All @@ -46,6 +47,18 @@ def entity_asset_save(self, asset_id, entity_type, asset):
# Save the new avatar
user.avatar_asset_id = asset_id
user.save()
invalidate_cache_directly(
path="/api/users/me/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/settings/",
url_params=False,
user=True,
request=request,
)
return
# User Cover
if entity_type == FileAsset.EntityTypeContext.USER_COVER:
Expand All @@ -57,21 +70,57 @@ def entity_asset_save(self, asset_id, entity_type, asset):
# Save the new cover image
user.cover_image_asset_id = asset_id
user.save()
invalidate_cache_directly(
path="/api/users/me/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/settings/",
url_params=False,
user=True,
request=request,
)
return
return

def entity_asset_delete(self, entity_type, asset):
def entity_asset_delete(self, entity_type, asset, request):
# User Avatar
if entity_type == FileAsset.EntityTypeContext.USER_AVATAR:
user = User.objects.get(id=asset.user_id)
user.avatar_asset_id = None
user.save()
invalidate_cache_directly(
path="/api/users/me/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/settings/",
url_params=False,
user=True,
request=request,
)
return
# User Cover
if entity_type == FileAsset.EntityTypeContext.USER_COVER:
user = User.objects.get(id=asset.user_id)
user.cover_image_asset_id = None
user.save()
invalidate_cache_directly(
path="/api/users/me/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/settings/",
url_params=False,
user=True,
request=request,
)
return
return

Expand All @@ -82,6 +131,9 @@ def post(self, request):
size = int(request.data.get("size", settings.FILE_SIZE_LIMIT))
entity_type = request.data.get("entity_type", False)

# Check if the file size is within the limit
size_limit = min(size, settings.FILE_SIZE_LIMIT)

# Check if the entity type is allowed
if not entity_type or entity_type not in ["USER_AVATAR", "USER_COVER"]:
return Response(
Expand All @@ -103,9 +155,6 @@ def post(self, request):
status=status.HTTP_400_BAD_REQUEST,
)

# Get the size limit
size_limit = min(settings.FILE_SIZE_LIMIT, size)

# asset key
asset_key = f"{uuid.uuid4().hex}-{name}"

Expand Down Expand Up @@ -153,7 +202,12 @@ def patch(self, request, asset_id):
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(asset_id, asset.entity_type, asset)
self.entity_asset_save(
asset_id=asset_id,
entity_type=asset.entity_type,
asset=asset,
request=request,
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
Expand All @@ -165,7 +219,9 @@ def delete(self, request, asset_id):
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(asset.entity_type, asset)
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)

Expand All @@ -174,16 +230,19 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
"""This endpoint is used to upload cover images/logos etc for workspace, projects and users."""

def get_entity_id_field(self, entity_type, entity_id):
# Workspace Logo
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
return {
"workspace_id": entity_id,
}

# Project Cover
if entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
return {
"project_id": entity_id,
}

# User Avatar and Cover
if entity_type in [
FileAsset.EntityTypeContext.USER_AVATAR,
FileAsset.EntityTypeContext.USER_COVER,
Expand All @@ -192,6 +251,7 @@ def get_entity_id_field(self, entity_type, entity_id):
"user_id": entity_id,
}

# Issue Attachment and Description
if entity_type in [
FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
FileAsset.EntityTypeContext.ISSUE_DESCRIPTION,
Expand All @@ -200,11 +260,13 @@ def get_entity_id_field(self, entity_type, entity_id):
"issue_id": entity_id,
}

# Page Description
if entity_type == FileAsset.EntityTypeContext.PAGE_DESCRIPTION:
return {
"page_id": entity_id,
}

# Comment Description
if entity_type == FileAsset.EntityTypeContext.COMMENT_DESCRIPTION:
return {
"comment_id": entity_id,
Expand All @@ -222,7 +284,7 @@ def asset_delete(self, asset_id):
asset.save()
return

def entity_asset_save(self, asset_id, entity_type, asset):
def entity_asset_save(self, asset_id, entity_type, asset, request):
# Workspace Logo
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
workspace = Workspace.objects.filter(id=asset.workspace_id).first()
Expand All @@ -235,6 +297,24 @@ def entity_asset_save(self, asset_id, entity_type, asset):
workspace.logo = ""
workspace.logo_asset_id = asset_id
workspace.save()
invalidate_cache_directly(
path="/api/workspaces/",
url_params=False,
user=False,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/workspaces/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/instances/",
url_params=False,
user=False,
request=request,
)
return

# Project Cover
Expand All @@ -253,14 +333,32 @@ def entity_asset_save(self, asset_id, entity_type, asset):
else:
return

def entity_asset_delete(self, entity_type, asset):
def entity_asset_delete(self, entity_type, asset, request):
# Workspace Logo
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
workspace = Workspace.objects.get(id=asset.workspace_id)
if workspace is None:
return
workspace.logo_asset_id = None
workspace.save()
invalidate_cache_directly(
path="/api/workspaces/",
url_params=False,
user=False,
request=request,
)
invalidate_cache_directly(
path="/api/users/me/workspaces/",
url_params=False,
user=True,
request=request,
)
invalidate_cache_directly(
path="/api/instances/",
url_params=False,
user=False,
request=request,
)
return
# Project Cover
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
Expand Down Expand Up @@ -322,7 +420,9 @@ def post(self, request, slug):
workspace=workspace,
created_by=request.user,
entity_type=entity_type,
**self.get_entity_id_field(entity_type, entity_identifier),
**self.get_entity_id_field(
entity_type=entity_type, entity_id=entity_identifier
),
)

# Get the presigned URL
Expand Down Expand Up @@ -355,7 +455,12 @@ def patch(self, request, slug, asset_id):
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(asset_id, asset.entity_type, asset)
self.entity_asset_save(
asset_id=asset_id,
entity_type=asset.entity_type,
asset=asset,
request=request,
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
Expand All @@ -367,7 +472,9 @@ def delete(self, request, slug, asset_id):
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(asset.entity_type, asset)
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)

Expand Down Expand Up @@ -454,7 +561,7 @@ def post(self, request, slug, asset_id):
class ProjectAssetEndpoint(BaseAPIView):
"""This endpoint is used to upload cover images/logos etc for workspace, projects and users."""

def get_entity_id_fiekd(self, entity_type, entity_id):
def get_entity_id_field(self, entity_type, entity_id):
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
return {
"workspace_id": entity_id,
Expand Down Expand Up @@ -490,6 +597,11 @@ def get_entity_id_fiekd(self, entity_type, entity_id):
return {
"comment_id": entity_id,
}

if entity_type == FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION:
return {
"draft_issue_id": entity_id,
}
return {}

@allow_permission(
Expand All @@ -513,7 +625,7 @@ def post(self, request, slug, project_id):
)

# Check if the file type is allowed
allowed_types = ["image/jpeg", "image/png", "image/webp"]
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
if type not in allowed_types:
return Response(
{
Expand Down Expand Up @@ -545,7 +657,7 @@ def post(self, request, slug, project_id):
created_by=request.user,
entity_type=entity_type,
project_id=project_id,
**self.get_entity_id_fiekd(entity_type, entity_identifier),
**self.get_entity_id_field(entity_type, entity_identifier),
)

# Get the presigned URL
Expand Down Expand Up @@ -688,4 +800,12 @@ def post(self, request, slug, project_id, entity_id):
page_id=entity_id,
)

if (
asset.entity_type
== FileAsset.EntityTypeContext.DRAFT_ISSUE_DESCRIPTION
):
assets.update(
draft_issue_id=entity_id,
)

return Response(status=status.HTTP_204_NO_CONTENT)
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/workspace/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def get(self, request, slug, user_id):
)
.annotate(
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.order_by()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.15 on 2024-10-12 18:45

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("db", "0079_auto_20241009_0619"),
]

operations = [
migrations.AddField(
model_name="fileasset",
name="draft_issue",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="assets",
to="db.draftissue",
),
),
migrations.AlterField(
model_name="fileasset",
name="entity_type",
field=models.CharField(
blank=True,
choices=[
("ISSUE_ATTACHMENT", "Issue Attachment"),
("ISSUE_DESCRIPTION", "Issue Description"),
("COMMENT_DESCRIPTION", "Comment Description"),
("PAGE_DESCRIPTION", "Page Description"),
("USER_COVER", "User Cover"),
("USER_AVATAR", "User Avatar"),
("WORKSPACE_LOGO", "Workspace Logo"),
("PROJECT_COVER", "Project Cover"),
("DRAFT_ISSUE_ATTACHMENT", "Draft Issue Attachment"),
("DRAFT_ISSUE_DESCRIPTION", "Draft Issue Description"),
],
max_length=255,
null=True,
),
),
]
Loading