mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB - 471] dev: caching users and workspace apis (#3707)
* dev: caching users and workspace apis * dev: cache user and config apis * dev: update caching function to use user_id instead of token * dev: update caching layer * dev: update caching logic * dev: format caching file * dev: refactor caching to include name space and user id as key * dev: cache project cover image endpoint
This commit is contained in:
parent
549f6d0943
commit
ed8782757d
@ -12,13 +12,14 @@ from rest_framework.response import Response
|
|||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
from plane.license.utils.instance_value import get_configuration_value
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
from plane.utils.cache import cache_response
|
||||||
|
|
||||||
class ConfigurationEndpoint(BaseAPIView):
|
class ConfigurationEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
# Get all the configuration
|
# Get all the configuration
|
||||||
(
|
(
|
||||||
@ -136,6 +137,7 @@ class MobileConfigurationEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
(
|
(
|
||||||
GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID,
|
||||||
|
@ -11,7 +11,7 @@ from plane.app.serializers import (
|
|||||||
EstimatePointSerializer,
|
EstimatePointSerializer,
|
||||||
EstimateReadSerializer,
|
EstimateReadSerializer,
|
||||||
)
|
)
|
||||||
|
from plane.utils.cache import invalidate_cache
|
||||||
|
|
||||||
class ProjectEstimatePointEndpoint(BaseAPIView):
|
class ProjectEstimatePointEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
@ -49,6 +49,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
serializer = EstimateReadSerializer(estimates, many=True)
|
serializer = EstimateReadSerializer(estimates, many=True)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
if not request.data.get("estimate", False):
|
if not request.data.get("estimate", False):
|
||||||
return Response(
|
return Response(
|
||||||
@ -114,6 +115,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||||
def partial_update(self, request, slug, project_id, estimate_id):
|
def partial_update(self, request, slug, project_id, estimate_id):
|
||||||
if not request.data.get("estimate", False):
|
if not request.data.get("estimate", False):
|
||||||
return Response(
|
return Response(
|
||||||
@ -182,6 +184,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
|
||||||
def destroy(self, request, slug, project_id, estimate_id):
|
def destroy(self, request, slug, project_id, estimate_id):
|
||||||
estimate = Estimate.objects.get(
|
estimate = Estimate.objects.get(
|
||||||
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
||||||
|
@ -78,6 +78,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
|
|||||||
from plane.utils.grouper import group_results
|
from plane.utils.grouper import group_results
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from plane.utils.cache import invalidate_cache
|
||||||
|
|
||||||
|
|
||||||
class IssueListEndpoint(BaseAPIView):
|
class IssueListEndpoint(BaseAPIView):
|
||||||
@ -1001,6 +1002,21 @@ class LabelViewSet(BaseViewSet):
|
|||||||
ProjectMemberPermission,
|
ProjectMemberPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.filter_queryset(
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(project__project_projectmember__member=self.request.user)
|
||||||
|
.select_related("project")
|
||||||
|
.select_related("workspace")
|
||||||
|
.select_related("parent")
|
||||||
|
.distinct()
|
||||||
|
.order_by("sort_order")
|
||||||
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
try:
|
try:
|
||||||
serializer = LabelSerializer(data=request.data)
|
serializer = LabelSerializer(data=request.data)
|
||||||
@ -1020,22 +1036,13 @@ class LabelViewSet(BaseViewSet):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||||
return self.filter_queryset(
|
def partial_update(self, request, *args, **kwargs):
|
||||||
super()
|
return super().partial_update(request, *args, **kwargs)
|
||||||
.get_queryset()
|
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
|
||||||
.filter(project_id=self.kwargs.get("project_id"))
|
def destroy(self, request, *args, **kwargs):
|
||||||
.filter(
|
return super().destroy(request, *args, **kwargs)
|
||||||
project__project_projectmember__member=self.request.user,
|
|
||||||
project__project_projectmember__is_active=True,
|
|
||||||
)
|
|
||||||
.select_related("project")
|
|
||||||
.select_related("workspace")
|
|
||||||
.select_related("parent")
|
|
||||||
.distinct()
|
|
||||||
.order_by("sort_order")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BulkDeleteIssuesEndpoint(BaseAPIView):
|
class BulkDeleteIssuesEndpoint(BaseAPIView):
|
||||||
|
@ -65,7 +65,7 @@ from plane.db.models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from plane.bgtasks.project_invitation_task import project_invitation
|
from plane.bgtasks.project_invitation_task import project_invitation
|
||||||
|
from plane.utils.cache import cache_response
|
||||||
|
|
||||||
class ProjectViewSet(WebhookMixin, BaseViewSet):
|
class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||||
serializer_class = ProjectListSerializer
|
serializer_class = ProjectListSerializer
|
||||||
@ -1045,6 +1045,8 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Cache the below api for 24 hours
|
||||||
|
@cache_response(60 * 60 * 24, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
files = []
|
files = []
|
||||||
s3 = boto3.client(
|
s3 = boto3.client(
|
||||||
|
@ -9,14 +9,13 @@ from rest_framework.response import Response
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView
|
from . import BaseViewSet
|
||||||
from plane.app.serializers import StateSerializer
|
from plane.app.serializers import StateSerializer
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
WorkspaceEntityPermission,
|
|
||||||
)
|
)
|
||||||
from plane.db.models import State, Issue
|
from plane.db.models import State, Issue
|
||||||
|
from plane.utils.cache import invalidate_cache
|
||||||
|
|
||||||
class StateViewSet(BaseViewSet):
|
class StateViewSet(BaseViewSet):
|
||||||
serializer_class = StateSerializer
|
serializer_class = StateSerializer
|
||||||
@ -41,6 +40,7 @@ class StateViewSet(BaseViewSet):
|
|||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
serializer = StateSerializer(data=request.data)
|
serializer = StateSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
@ -61,6 +61,7 @@ class StateViewSet(BaseViewSet):
|
|||||||
return Response(state_dict, status=status.HTTP_200_OK)
|
return Response(state_dict, status=status.HTTP_200_OK)
|
||||||
return Response(states, status=status.HTTP_200_OK)
|
return Response(states, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||||
def mark_as_default(self, request, slug, project_id, pk):
|
def mark_as_default(self, request, slug, project_id, pk):
|
||||||
# Select all the states which are marked as default
|
# Select all the states which are marked as default
|
||||||
_ = State.objects.filter(
|
_ = State.objects.filter(
|
||||||
@ -71,6 +72,7 @@ class StateViewSet(BaseViewSet):
|
|||||||
).update(default=True)
|
).update(default=True)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||||
def destroy(self, request, slug, project_id, pk):
|
def destroy(self, request, slug, project_id, pk):
|
||||||
state = State.objects.get(
|
state = State.objects.get(
|
||||||
~Q(name="Triage"),
|
~Q(name="Triage"),
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
# Django imports
|
||||||
|
from django.db.models import Q, Count, Case, When, IntegerField
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
UserSerializer,
|
UserSerializer,
|
||||||
@ -15,9 +17,7 @@ from plane.app.views.base import BaseViewSet, BaseAPIView
|
|||||||
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
|
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
|
||||||
from plane.license.models import Instance, InstanceAdmin
|
from plane.license.models import Instance, InstanceAdmin
|
||||||
from plane.utils.paginator import BasePaginator
|
from plane.utils.paginator import BasePaginator
|
||||||
|
from plane.utils.cache import cache_response, invalidate_cache
|
||||||
|
|
||||||
from django.db.models import Q, F, Count, Case, When, IntegerField
|
|
||||||
|
|
||||||
|
|
||||||
class UserEndpoint(BaseViewSet):
|
class UserEndpoint(BaseViewSet):
|
||||||
@ -27,6 +27,7 @@ class UserEndpoint(BaseViewSet):
|
|||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
@cache_response(60 * 60)
|
||||||
def retrieve(self, request):
|
def retrieve(self, request):
|
||||||
serialized_data = UserMeSerializer(request.user).data
|
serialized_data = UserMeSerializer(request.user).data
|
||||||
return Response(
|
return Response(
|
||||||
@ -34,10 +35,12 @@ class UserEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cache_response(60 * 60)
|
||||||
def retrieve_user_settings(self, request):
|
def retrieve_user_settings(self, request):
|
||||||
serialized_data = UserMeSettingsSerializer(request.user).data
|
serialized_data = UserMeSettingsSerializer(request.user).data
|
||||||
return Response(serialized_data, status=status.HTTP_200_OK)
|
return Response(serialized_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@cache_response(60 * 60)
|
||||||
def retrieve_instance_admin(self, request):
|
def retrieve_instance_admin(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
is_admin = InstanceAdmin.objects.filter(
|
is_admin = InstanceAdmin.objects.filter(
|
||||||
@ -47,6 +50,11 @@ class UserEndpoint(BaseViewSet):
|
|||||||
{"is_instance_admin": is_admin}, status=status.HTTP_200_OK
|
{"is_instance_admin": is_admin}, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/users/me/")
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/users/me/")
|
||||||
def deactivate(self, request):
|
def deactivate(self, request):
|
||||||
# Check all workspace user is active
|
# Check all workspace user is active
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
@ -145,6 +153,8 @@ class UserEndpoint(BaseViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/users/me/")
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
user = User.objects.get(pk=request.user.id, is_active=True)
|
user = User.objects.get(pk=request.user.id, is_active=True)
|
||||||
user.is_onboarded = request.data.get("is_onboarded", False)
|
user.is_onboarded = request.data.get("is_onboarded", False)
|
||||||
@ -155,6 +165,8 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/users/me/")
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
user = User.objects.get(pk=request.user.id, is_active=True)
|
user = User.objects.get(pk=request.user.id, is_active=True)
|
||||||
user.is_tour_completed = request.data.get("is_tour_completed", False)
|
user.is_tour_completed = request.data.get("is_tour_completed", False)
|
||||||
@ -165,6 +177,7 @@ class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
queryset = IssueActivity.objects.filter(
|
queryset = IssueActivity.objects.filter(
|
||||||
actor=request.user
|
actor=request.user
|
||||||
|
@ -57,6 +57,8 @@ from plane.app.serializers import (
|
|||||||
WorkspaceEstimateSerializer,
|
WorkspaceEstimateSerializer,
|
||||||
StateSerializer,
|
StateSerializer,
|
||||||
LabelSerializer,
|
LabelSerializer,
|
||||||
|
CycleSerializer,
|
||||||
|
ModuleSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.views.base import BaseAPIView
|
from plane.app.views.base import BaseAPIView
|
||||||
from . import BaseViewSet
|
from . import BaseViewSet
|
||||||
@ -77,7 +79,6 @@ from plane.db.models import (
|
|||||||
Label,
|
Label,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
CycleIssue,
|
CycleIssue,
|
||||||
IssueReaction,
|
|
||||||
WorkspaceUserProperties,
|
WorkspaceUserProperties,
|
||||||
Estimate,
|
Estimate,
|
||||||
EstimatePoint,
|
EstimatePoint,
|
||||||
@ -91,17 +92,11 @@ from plane.app.permissions import (
|
|||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
WorkspaceUserPermission,
|
WorkspaceUserPermission,
|
||||||
ProjectLitePermission,
|
|
||||||
)
|
)
|
||||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
||||||
from plane.app.serializers.module import (
|
from plane.utils.cache import cache_response, invalidate_cache
|
||||||
ModuleSerializer,
|
|
||||||
)
|
|
||||||
from plane.app.serializers.cycle import (
|
|
||||||
CycleSerializer,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceViewSet(BaseViewSet):
|
class WorkSpaceViewSet(BaseViewSet):
|
||||||
@ -151,7 +146,8 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
.annotate(total_issues=issue_count)
|
.annotate(total_issues=issue_count)
|
||||||
.select_related("owner")
|
.select_related("owner")
|
||||||
)
|
)
|
||||||
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
try:
|
try:
|
||||||
serializer = WorkSpaceSerializer(data=request.data)
|
serializer = WorkSpaceSerializer(data=request.data)
|
||||||
@ -197,6 +193,20 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
status=status.HTTP_410_GONE,
|
status=status.HTTP_410_GONE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserWorkSpacesEndpoint(BaseAPIView):
|
class UserWorkSpacesEndpoint(BaseAPIView):
|
||||||
search_fields = [
|
search_fields = [
|
||||||
@ -206,6 +216,7 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
|||||||
"owner",
|
"owner",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
fields = [
|
fields = [
|
||||||
field
|
field
|
||||||
@ -403,6 +414,8 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||||||
]
|
]
|
||||||
"""Invitation response endpoint the user can respond to the invitation"""
|
"""Invitation response endpoint the user can respond to the invitation"""
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
def post(self, request, slug, pk):
|
def post(self, request, slug, pk):
|
||||||
workspace_invite = WorkspaceMemberInvite.objects.get(
|
workspace_invite = WorkspaceMemberInvite.objects.get(
|
||||||
pk=pk, workspace__slug=slug
|
pk=pk, workspace__slug=slug
|
||||||
@ -499,6 +512,9 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
|
|||||||
.annotate(total_members=Count("workspace__workspace_member"))
|
.annotate(total_members=Count("workspace__workspace_member"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
invitations = request.data.get("invitations", [])
|
invitations = request.data.get("invitations", [])
|
||||||
workspace_invitations = WorkspaceMemberInvite.objects.filter(
|
workspace_invitations = WorkspaceMemberInvite.objects.filter(
|
||||||
@ -569,6 +585,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
.select_related("member")
|
.select_related("member")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def list(self, request, slug):
|
def list(self, request, slug):
|
||||||
workspace_member = WorkspaceMember.objects.get(
|
workspace_member = WorkspaceMember.objects.get(
|
||||||
member=request.user,
|
member=request.user,
|
||||||
@ -593,6 +610,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
|
||||||
def partial_update(self, request, slug, pk):
|
def partial_update(self, request, slug, pk):
|
||||||
workspace_member = WorkspaceMember.objects.get(
|
workspace_member = WorkspaceMember.objects.get(
|
||||||
pk=pk,
|
pk=pk,
|
||||||
@ -635,6 +653,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
|
||||||
def destroy(self, request, slug, pk):
|
def destroy(self, request, slug, pk):
|
||||||
# Check the user role who is deleting the user
|
# Check the user role who is deleting the user
|
||||||
workspace_member = WorkspaceMember.objects.get(
|
workspace_member = WorkspaceMember.objects.get(
|
||||||
@ -699,6 +718,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
workspace_member.save()
|
workspace_member.save()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
|
||||||
def leave(self, request, slug):
|
def leave(self, request, slug):
|
||||||
workspace_member = WorkspaceMember.objects.get(
|
workspace_member = WorkspaceMember.objects.get(
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
@ -1550,6 +1570,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
|||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
labels = Label.objects.filter(
|
labels = Label.objects.filter(
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
@ -1565,6 +1586,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
|
|||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
states = State.objects.filter(
|
states = State.objects.filter(
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
@ -1580,6 +1602,7 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
|
|||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
estimate_ids = Project.objects.filter(
|
estimate_ids = Project.objects.filter(
|
||||||
workspace__slug=slug, estimate__isnull=False
|
workspace__slug=slug, estimate__isnull=False
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import uuid
|
import uuid
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -30,9 +24,9 @@ from plane.license.api.serializers import (
|
|||||||
from plane.license.api.permissions import (
|
from plane.license.api.permissions import (
|
||||||
InstanceAdminPermission,
|
InstanceAdminPermission,
|
||||||
)
|
)
|
||||||
from plane.db.models import User, WorkspaceMember, ProjectMember
|
from plane.db.models import User
|
||||||
from plane.license.utils.encryption import encrypt_data
|
from plane.license.utils.encryption import encrypt_data
|
||||||
|
from plane.utils.cache import cache_response, invalidate_cache
|
||||||
|
|
||||||
class InstanceEndpoint(BaseAPIView):
|
class InstanceEndpoint(BaseAPIView):
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
@ -44,6 +38,7 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
AllowAny(),
|
AllowAny(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
# get the instance
|
# get the instance
|
||||||
@ -58,6 +53,7 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
data["is_activated"] = True
|
data["is_activated"] = True
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
# Get the instance
|
# Get the instance
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
@ -75,6 +71,7 @@ class InstanceAdminEndpoint(BaseAPIView):
|
|||||||
InstanceAdminPermission,
|
InstanceAdminPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
# Create an instance admin
|
# Create an instance admin
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
email = request.data.get("email", False)
|
email = request.data.get("email", False)
|
||||||
@ -104,6 +101,7 @@ class InstanceAdminEndpoint(BaseAPIView):
|
|||||||
serializer = InstanceAdminSerializer(instance_admin)
|
serializer = InstanceAdminSerializer(instance_admin)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@ -115,6 +113,7 @@ class InstanceAdminEndpoint(BaseAPIView):
|
|||||||
serializer = InstanceAdminSerializer(instance_admins, many=True)
|
serializer = InstanceAdminSerializer(instance_admins, many=True)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def delete(self, request, pk):
|
def delete(self, request, pk):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
instance_admin = InstanceAdmin.objects.filter(
|
instance_admin = InstanceAdmin.objects.filter(
|
||||||
@ -128,6 +127,7 @@ class InstanceConfigurationEndpoint(BaseAPIView):
|
|||||||
InstanceAdminPermission,
|
InstanceAdminPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_response(60 * 60 * 2, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance_configurations = InstanceConfiguration.objects.all()
|
instance_configurations = InstanceConfiguration.objects.all()
|
||||||
serializer = InstanceConfigurationSerializer(
|
serializer = InstanceConfigurationSerializer(
|
||||||
@ -135,6 +135,8 @@ class InstanceConfigurationEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/configs/", user=False)
|
||||||
|
@invalidate_cache(path="/api/mobile-configs/", user=False)
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
configurations = InstanceConfiguration.objects.filter(
|
configurations = InstanceConfiguration.objects.filter(
|
||||||
key__in=request.data.keys()
|
key__in=request.data.keys()
|
||||||
@ -170,6 +172,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
# Check instance first
|
# Check instance first
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
@ -260,6 +263,7 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
if instance is None:
|
if instance is None:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Development settings"""
|
"""Development settings"""
|
||||||
|
|
||||||
from .common import * # noqa
|
from .common import * # noqa
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
@ -14,7 +15,11 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
|||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
|
"LOCATION": REDIS_URL,
|
||||||
|
"OPTIONS": {
|
||||||
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
84
apiserver/plane/utils/cache.py
Normal file
84
apiserver/plane/utils/cache.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
from django.core.cache import cache
|
||||||
|
# from django.utils.encoding import force_bytes
|
||||||
|
# import hashlib
|
||||||
|
from functools import wraps
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cache_key(custom_path, auth_header=None):
|
||||||
|
"""Generate a cache key with the given params"""
|
||||||
|
if auth_header:
|
||||||
|
key_data = f"{custom_path}:{auth_header}"
|
||||||
|
else:
|
||||||
|
key_data = custom_path
|
||||||
|
return key_data
|
||||||
|
|
||||||
|
|
||||||
|
def cache_response(timeout=60 * 60, path=None, user=True):
|
||||||
|
"""decorator to create cache per user"""
|
||||||
|
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Function to generate cache key
|
||||||
|
auth_header = (
|
||||||
|
None if request.user.is_anonymous else str(request.user.id) if user else None
|
||||||
|
)
|
||||||
|
custom_path = path if path is not None else request.get_full_path()
|
||||||
|
key = generate_cache_key(custom_path, auth_header)
|
||||||
|
cached_result = cache.get(key)
|
||||||
|
if cached_result is not None:
|
||||||
|
print("Cache Hit")
|
||||||
|
return Response(
|
||||||
|
cached_result["data"], status=cached_result["status"]
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Cache Miss")
|
||||||
|
response = view_func(instance, request, *args, **kwargs)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
cache.set(
|
||||||
|
key,
|
||||||
|
{"data": response.data, "status": response.status_code},
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
return _wrapped_view
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_cache(path=None, url_params=False, user=True):
|
||||||
|
"""invalidate cache per user"""
|
||||||
|
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Invalidate cache before executing the view function
|
||||||
|
if url_params:
|
||||||
|
path_with_values = path
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
path_with_values = path_with_values.replace(
|
||||||
|
f":{key}", str(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_path = path_with_values
|
||||||
|
else:
|
||||||
|
custom_path = (
|
||||||
|
path if path is not None else request.get_full_path()
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_header = (
|
||||||
|
None if request.user.is_anonymous else str(request.user.id) if user else None
|
||||||
|
)
|
||||||
|
key = generate_cache_key(custom_path, auth_header)
|
||||||
|
cache.delete(key)
|
||||||
|
print("Invalidating cache")
|
||||||
|
# Execute the view function
|
||||||
|
return view_func(instance, request, *args, **kwargs)
|
||||||
|
|
||||||
|
return _wrapped_view
|
||||||
|
|
||||||
|
return decorator
|
Loading…
Reference in New Issue
Block a user