dev: mini cache framework and caching for users and instance configuration

This commit is contained in:
pablohashescobar 2024-02-16 14:00:38 +05:30
parent 453d4d9e3e
commit ff78ef8f61
4 changed files with 120 additions and 5 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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:

View 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