Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Refresh s3 links in projects and issues #3165

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dff69f5
build: inlcude bsoup library
Dec 7, 2023
3de418c
feat(utils): create class for aws s3 management
Dec 7, 2023
1c03ab2
feat(utils): create functions to parse texto to html and update s3 url
Dec 7, 2023
1c272c2
feat(project): refresh cover_image s3 url in project serializers
Dec 7, 2023
ed4a0e7
feat(issue): refresh content url in issue serializers
Dec 7, 2023
dfca75b
feat(utils): include validation if minio is true
Dec 12, 2023
5319dfc
feat(issue): create class for refresh description_html s3 links
Dec 12, 2023
f8bd404
feat(utils): add validation if s3 obj link is expired
Dec 12, 2023
6f57763
feat(s3): create static method to verify if url file has expired
Dec 12, 2023
b3eceb8
feat(s3): create static method to verify if url file has expired
Dec 12, 2023
268c563
feat(project): create class to refresh project cover image
Dec 12, 2023
ec9fcbc
fix: fix typo
Dec 12, 2023
7e38796
feat(s3): create upload, delete and edit refresh url methods
Dec 13, 2023
f349010
feat(s3): use s3 for all operations
Dec 13, 2023
ded65df
build: inlcude AWS_S3_MAX_AGE_SECONDS variable if use minio is true
Dec 13, 2023
a9d62e3
Merge branch 'master-copy' into Feat/Refresh-s3-links-in-projects-and…
Dec 15, 2023
0f9331a
feat(s3): change variable name
Dec 16, 2023
6079147
feat(project): change code for project serializer
Dec 16, 2023
fe3e6e3
feat(issue): change code for issue serializer
Dec 16, 2023
b7c3d4c
build(settings): include AWS_S3_SIGNATURE_VERSION, AWS_S3_BUCKET_AUTH…
Dec 16, 2023
f9b0053
feat(issue): include validation if bucket is private
Dec 16, 2023
c09ca64
feat(project): include validation if bucket is private
Dec 16, 2023
64904f6
feat: delete unused imports
Dec 16, 2023
ebdde98
docs: update docs
Dec 16, 2023
5c62db3
build: set AWS_S3_BUCKET_AUTH default to false
Dec 16, 2023
583fd14
feat: include minio info
Dec 16, 2023
69d0316
build: include s3 info variables in .env file
daniel-alba17 Jan 18, 2024
f3c2314
build: include s3 info variables in .env file
daniel-alba17 Jan 18, 2024
4ff8681
feat(s3): include refresh_url function in comment serializer
daniel-alba17 Jan 18, 2024
c12acd3
feat(s3): add verify for s3 url
daniel-alba17 Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions ENV_SETUP.md
Expand Up @@ -27,6 +27,8 @@ AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
AWS_S3_BUCKET_AUTH=False
AWS_QUERYSTRING_AUTH=False
# Maximum file upload limit
FILE_SIZE_LIMIT=5242880
Expand Down Expand Up @@ -94,6 +96,8 @@ AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
AWS_S3_BUCKET_AUTH=False
AWS_QUERYSTRING_AUTH=False
# Maximum file upload limit
FILE_SIZE_LIMIT=5242880
Expand Down
2 changes: 2 additions & 0 deletions apiserver/.env.example
Expand Up @@ -31,6 +31,8 @@ AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
AWS_S3_BUCKET_AUTH=False
AWS_QUERYSTRING_AUTH=False
# Maximum file upload limit
FILE_SIZE_LIMIT=5242880

Expand Down
17 changes: 9 additions & 8 deletions apiserver/plane/api/serializers/issue.py
Expand Up @@ -21,12 +21,14 @@
IssueActivity,
ProjectMember,
)

from .base import BaseSerializer
from .cycle import CycleSerializer, CycleLiteSerializer
from .module import ModuleSerializer, ModuleLiteSerializer
from .user import UserLiteSerializer
from .state import StateLiteSerializer


class IssueSerializer(BaseSerializer):
assignees = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(
Expand Down Expand Up @@ -67,13 +69,13 @@ def validate(self, data):
and data.get("start_date", None) > data.get("target_date", None)
):
raise serializers.ValidationError("Start date cannot exceed target date")

try:
if(data.get("description_html", None) is not None):
if data.get("description_html", None) is not None:
parsed = html.fromstring(data["description_html"])
parsed_str = html.tostring(parsed, encoding='unicode')
parsed_str = html.tostring(parsed, encoding="unicode")
data["description_html"] = parsed_str

except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")

Expand Down Expand Up @@ -324,11 +326,11 @@ class Meta:

def validate(self, data):
try:
if(data.get("comment_html", None) is not None):
if data.get("comment_html", None) is not None:
parsed = html.fromstring(data["comment_html"])
parsed_str = html.tostring(parsed, encoding='unicode')
parsed_str = html.tostring(parsed, encoding="unicode")
data["comment_html"] = parsed_str

except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
return data
Expand Down Expand Up @@ -362,7 +364,6 @@ class Meta:


class LabelLiteSerializer(BaseSerializer):

class Meta:
model = Label
fields = [
Expand Down
7 changes: 3 additions & 4 deletions apiserver/plane/api/serializers/project.py
Expand Up @@ -2,12 +2,11 @@
from rest_framework import serializers

# Module imports
from plane.db.models import Project, ProjectIdentifier, WorkspaceMember, State, Estimate
from plane.db.models import Project, ProjectIdentifier, WorkspaceMember
from .base import BaseSerializer


class ProjectSerializer(BaseSerializer):

total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
Expand All @@ -21,7 +20,7 @@ class Meta:
fields = "__all__"
read_only_fields = [
"id",
'emoji',
"emoji",
"workspace",
"created_at",
"updated_at",
Expand Down Expand Up @@ -89,4 +88,4 @@ class Meta:
"emoji",
"description",
]
read_only_fields = fields
read_only_fields = fields
89 changes: 59 additions & 30 deletions apiserver/plane/app/serializers/issue.py
@@ -1,5 +1,6 @@
# Django imports
from django.utils import timezone
from django.conf import settings

# Third Party imports
from rest_framework import serializers
Expand Down Expand Up @@ -31,9 +32,23 @@
IssueVote,
IssueRelation,
)
from plane.utils.parse_html import parse_text_to_html, refresh_url_content


class IssueFlatSerializer(BaseSerializer):
class BaseIssueSerializerMixin:
"""abstract class for refresh s3 link in description htlm images"""

def refresh_html_content(self, instance, html, html_field_name="description_html"):
if settings.AWS_S3_BUCKET_AUTH:
html = parse_text_to_html(html)
refreshed, html = refresh_url_content(html)

if refreshed:
setattr(instance, html_field_name, html)
instance.save()


class IssueFlatSerializer(BaseSerializer, BaseIssueSerializerMixin):
## Contain only flat fields

class Meta:
Expand All @@ -51,6 +66,10 @@ class Meta:
"is_draft",
]

def to_representation(self, instance):
self.refresh_html_content(instance, instance.description_html)
return super().to_representation(instance)


class IssueProjectLiteSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(source="project", read_only=True)
Expand Down Expand Up @@ -100,8 +119,8 @@ class Meta:

def to_representation(self, instance):
data = super().to_representation(instance)
data['assignees'] = [str(assignee.id) for assignee in instance.assignees.all()]
data['labels'] = [str(label.id) for label in instance.labels.all()]
data["assignees"] = [str(assignee.id) for assignee in instance.assignees.all()]
data["labels"] = [str(label.id) for label in instance.labels.all()]
return data

def validate(self, data):
Expand Down Expand Up @@ -232,7 +251,6 @@ class Meta:
fields = "__all__"



class IssuePropertySerializer(BaseSerializer):
class Meta:
model = IssueProperty
Expand Down Expand Up @@ -268,7 +286,6 @@ class Meta:


class IssueLabelSerializer(BaseSerializer):

class Meta:
model = IssueLabel
fields = "__all__"
Expand All @@ -283,30 +300,19 @@ class IssueRelationSerializer(BaseSerializer):

class Meta:
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
fields = ["issue_detail", "relation_type", "related_issue", "issue", "id"]
read_only_fields = [
"workspace",
"project",
]


class RelatedIssueSerializer(BaseSerializer):
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")

class Meta:
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
fields = ["issue_detail", "relation_type", "related_issue", "issue", "id"]
read_only_fields = [
"workspace",
"project",
Expand Down Expand Up @@ -424,9 +430,8 @@ class Meta:


class IssueReactionSerializer(BaseSerializer):

actor_detail = UserLiteSerializer(read_only=True, source="actor")

class Meta:
model = IssueReaction
fields = "__all__"
Expand Down Expand Up @@ -459,7 +464,6 @@ class Meta:


class IssueVoteSerializer(BaseSerializer):

actor_detail = UserLiteSerializer(read_only=True, source="actor")

class Meta:
Expand All @@ -468,7 +472,7 @@ class Meta:
read_only_fields = fields


class IssueCommentSerializer(BaseSerializer):
class IssueCommentSerializer(BaseSerializer, BaseIssueSerializerMixin):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
Expand All @@ -489,6 +493,10 @@ class Meta:
"updated_at",
]

def to_representation(self, instance):
self.refresh_html_content(instance, instance.comment_html, "comment_html")
return super().to_representation(instance)


class IssueStateFlatSerializer(BaseSerializer):
state_detail = StateLiteSerializer(read_only=True, source="state")
Expand All @@ -506,7 +514,7 @@ class Meta:


# Issue Serializer with state details
class IssueStateSerializer(DynamicBaseSerializer):
class IssueStateSerializer(DynamicBaseSerializer, BaseIssueSerializerMixin):
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
Expand All @@ -520,15 +528,23 @@ class Meta:
model = Issue
fields = "__all__"

def to_representation(self, instance):
self.refresh_html_content(instance, instance.description_html)
return super().to_representation(instance)


class IssueSerializer(BaseSerializer):
class IssueSerializer(BaseSerializer, BaseIssueSerializerMixin):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
parent_detail = IssueStateFlatSerializer(read_only=True, source="parent")
label_details = LabelSerializer(read_only=True, source="labels", many=True)
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True)
issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True)
related_issues = IssueRelationSerializer(
read_only=True, source="issue_relation", many=True
)
issue_relations = RelatedIssueSerializer(
read_only=True, source="issue_related", many=True
)
issue_cycle = IssueCycleDetailSerializer(read_only=True)
issue_module = IssueModuleDetailSerializer(read_only=True)
issue_link = IssueLinkSerializer(read_only=True, many=True)
Expand All @@ -548,8 +564,12 @@ class Meta:
"updated_at",
]

def to_representation(self, instance):
self.refresh_html_content(instance, instance.description_html)
return super().to_representation(instance)


class IssueLiteSerializer(DynamicBaseSerializer):
class IssueLiteSerializer(DynamicBaseSerializer, BaseIssueSerializerMixin):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
Expand Down Expand Up @@ -577,11 +597,17 @@ class Meta:
"updated_at",
]

def to_representation(self, instance):
self.refresh_html_content(instance, instance.description_html)
return super().to_representation(instance)

class IssuePublicSerializer(BaseSerializer):

class IssuePublicSerializer(BaseSerializer, BaseIssueSerializerMixin):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions")
reactions = IssueReactionSerializer(
read_only=True, many=True, source="issue_reactions"
)
votes = IssueVoteSerializer(read_only=True, many=True)

class Meta:
Expand All @@ -603,6 +629,9 @@ class Meta:
]
read_only_fields = fields

def to_representation(self, instance):
self.refresh_html_content(instance, instance.description_html)
return super().to_representation(instance)


class IssueSubscriberSerializer(BaseSerializer):
Expand Down