mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
dev: mini cache framework and caching for users and instance configuration
This commit is contained in:
parent
453d4d9e3e
commit
ff78ef8f61
@ -12,6 +12,7 @@ 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 ...utils.cache import cache_path_response
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationEndpoint(BaseAPIView):
|
class ConfigurationEndpoint(BaseAPIView):
|
||||||
@ -19,6 +20,7 @@ class ConfigurationEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_path_response(60 * 60 * 2)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
# Get all the configuration
|
# Get all the configuration
|
||||||
(
|
(
|
||||||
@ -136,6 +138,7 @@ class MobileConfigurationEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_path_response(60 * 60 * 2)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
(
|
(
|
||||||
GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID,
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
# Django imports
|
||||||
|
from django.db.models import Q, F, 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,10 +16,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 ...utils.cache import cache_user_response, invalidate_user_cache
|
||||||
|
|
||||||
from django.db.models import Q, F, Count, Case, When, IntegerField
|
|
||||||
|
|
||||||
|
|
||||||
class UserEndpoint(BaseViewSet):
|
class UserEndpoint(BaseViewSet):
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
@ -27,6 +25,7 @@ class UserEndpoint(BaseViewSet):
|
|||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
@cache_user_response(60*15)
|
||||||
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 +33,12 @@ class UserEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cache_user_response(60*15)
|
||||||
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_user_response(60*15)
|
||||||
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 +48,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_user_cache("/api/users/me/")
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@invalidate_user_cache("/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 +151,8 @@ class UserEndpoint(BaseViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
@invalidate_user_cache("/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 +163,8 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
@invalidate_user_cache("/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)
|
||||||
|
@ -32,6 +32,7 @@ from plane.license.api.permissions import (
|
|||||||
)
|
)
|
||||||
from plane.db.models import User, WorkspaceMember, ProjectMember
|
from plane.db.models import User, WorkspaceMember, ProjectMember
|
||||||
from plane.license.utils.encryption import encrypt_data
|
from plane.license.utils.encryption import encrypt_data
|
||||||
|
from plane.utils.cache import cache_path_response, invalidate_path_cache
|
||||||
|
|
||||||
|
|
||||||
class InstanceEndpoint(BaseAPIView):
|
class InstanceEndpoint(BaseAPIView):
|
||||||
@ -44,6 +45,7 @@ class InstanceEndpoint(BaseAPIView):
|
|||||||
AllowAny(),
|
AllowAny(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cache_path_response(60 * 60 * 2)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
# get the instance
|
# get the instance
|
||||||
@ -58,6 +60,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_path_cache
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
# Get the instance
|
# Get the instance
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
@ -104,6 +107,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)
|
||||||
|
|
||||||
|
@invalidate_path_cache("/api/instances/")
|
||||||
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 +119,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_path_cache("/api/instances/")
|
||||||
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(
|
||||||
@ -135,6 +140,8 @@ class InstanceConfigurationEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@invalidate_path_cache("/api/configs/")
|
||||||
|
@invalidate_path_cache("/api/mobile-configs/")
|
||||||
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 +177,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@invalidate_path_cache("/api/instances/")
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
# Check instance first
|
# Check instance first
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
@ -260,6 +268,7 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
|
|||||||
AllowAny,
|
AllowAny,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@invalidate_path_cache("/api/instances/")
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
if instance is None:
|
if instance is None:
|
||||||
|
93
apiserver/plane/utils/cache.py
Normal file
93
apiserver/plane/utils/cache.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from django.utils.http import http_date
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cache_key(custom_path, auth_header=None):
|
||||||
|
if auth_header:
|
||||||
|
key_data = f'{custom_path}:{auth_header}'
|
||||||
|
else:
|
||||||
|
key_data = custom_path
|
||||||
|
return hashlib.md5(force_bytes(key_data)).hexdigest()
|
||||||
|
|
||||||
|
def cache_user_response(timeout, path=None):
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Function to generate cache key
|
||||||
|
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
||||||
|
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:
|
||||||
|
return Response(cached_result['data'], status=cached_result['status'])
|
||||||
|
|
||||||
|
response = view_func(instance, request, *args, **kwargs)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
cache.set(key, {'data': response.data, 'status': response.status_code}, timeout)
|
||||||
|
response['Cache-Control'] = f'max-age={timeout}'
|
||||||
|
expires_time = datetime.utcnow() + timedelta(seconds=timeout)
|
||||||
|
response['Expires'] = http_date(expires_time.timestamp())
|
||||||
|
|
||||||
|
return response
|
||||||
|
return _wrapped_view
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def invalidate_user_cache(path):
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Invalidate cache before executing the view function
|
||||||
|
custom_path = path if path is not None else request.get_full_path()
|
||||||
|
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
||||||
|
key = generate_cache_key(custom_path, auth_header)
|
||||||
|
cache.delete(key)
|
||||||
|
|
||||||
|
# Execute the view function
|
||||||
|
return view_func(instance, request, *args, **kwargs)
|
||||||
|
return _wrapped_view
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def cache_path_response(timeout, path=None):
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Function to generate cache key
|
||||||
|
custom_path = path if path is not None else request.get_full_path()
|
||||||
|
key = generate_cache_key(custom_path, None)
|
||||||
|
cached_result = cache.get(key)
|
||||||
|
if cached_result is not None:
|
||||||
|
return Response(cached_result['data'], status=cached_result['status'])
|
||||||
|
|
||||||
|
response = view_func(instance, request, *args, **kwargs)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
cache.set(key, {'data': response.data, 'status': response.status_code}, timeout)
|
||||||
|
response['Cache-Control'] = f'max-age={timeout}'
|
||||||
|
expires_time = datetime.utcnow() + timedelta(seconds=timeout)
|
||||||
|
response['Expires'] = http_date(expires_time.timestamp())
|
||||||
|
|
||||||
|
return response
|
||||||
|
return _wrapped_view
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def invalidate_path_cache(path=None):
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def _wrapped_view(instance, request, *args, **kwargs):
|
||||||
|
# Invalidate cache before executing the view function
|
||||||
|
custom_path = path if path is not None else request.get_full_path()
|
||||||
|
key = generate_cache_key(custom_path, None)
|
||||||
|
cache.delete(key)
|
||||||
|
|
||||||
|
# Execute the view function
|
||||||
|
return view_func(instance, request, *args, **kwargs)
|
||||||
|
return _wrapped_view
|
||||||
|
return decorator
|
||||||
|
|
Loading…
Reference in New Issue
Block a user