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
from .base import BaseAPIView
from plane.license.utils.instance_value import get_configuration_value
from ...utils.cache import cache_path_response
class ConfigurationEndpoint(BaseAPIView):
@ -19,6 +20,7 @@ class ConfigurationEndpoint(BaseAPIView):
AllowAny,
]
@cache_path_response(60 * 60 * 2)
def get(self, request):
# Get all the configuration
(
@ -136,6 +138,7 @@ class MobileConfigurationEndpoint(BaseAPIView):
AllowAny,
]
@cache_path_response(60 * 60 * 2)
def get(self, request):
(
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
from rest_framework.response import Response
from rest_framework import status
# Module imports
from plane.app.serializers import (
UserSerializer,
@ -15,10 +16,7 @@ from plane.app.views.base import BaseViewSet, BaseAPIView
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
from plane.license.models import Instance, InstanceAdmin
from plane.utils.paginator import BasePaginator
from django.db.models import Q, F, Count, Case, When, IntegerField
from ...utils.cache import cache_user_response, invalidate_user_cache
class UserEndpoint(BaseViewSet):
serializer_class = UserSerializer
@ -27,6 +25,7 @@ class UserEndpoint(BaseViewSet):
def get_object(self):
return self.request.user
@cache_user_response(60*15)
def retrieve(self, request):
serialized_data = UserMeSerializer(request.user).data
return Response(
@ -34,10 +33,12 @@ class UserEndpoint(BaseViewSet):
status=status.HTTP_200_OK,
)
@cache_user_response(60*15)
def retrieve_user_settings(self, request):
serialized_data = UserMeSettingsSerializer(request.user).data
return Response(serialized_data, status=status.HTTP_200_OK)
@cache_user_response(60*15)
def retrieve_instance_admin(self, request):
instance = Instance.objects.first()
is_admin = InstanceAdmin.objects.filter(
@ -47,6 +48,11 @@ class UserEndpoint(BaseViewSet):
{"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):
# Check all workspace user is active
user = self.get_object()
@ -145,6 +151,8 @@ class UserEndpoint(BaseViewSet):
class UpdateUserOnBoardedEndpoint(BaseAPIView):
@invalidate_user_cache("/api/users/me")
def patch(self, request):
user = User.objects.get(pk=request.user.id, is_active=True)
user.is_onboarded = request.data.get("is_onboarded", False)
@ -155,6 +163,8 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
class UpdateUserTourCompletedEndpoint(BaseAPIView):
@invalidate_user_cache("/api/users/me")
def patch(self, request):
user = User.objects.get(pk=request.user.id, is_active=True)
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.license.utils.encryption import encrypt_data
from plane.utils.cache import cache_path_response, invalidate_path_cache
class InstanceEndpoint(BaseAPIView):
@ -44,6 +45,7 @@ class InstanceEndpoint(BaseAPIView):
AllowAny(),
]
@cache_path_response(60 * 60 * 2)
def get(self, request):
instance = Instance.objects.first()
# get the instance
@ -58,6 +60,7 @@ class InstanceEndpoint(BaseAPIView):
data["is_activated"] = True
return Response(data, status=status.HTTP_200_OK)
@invalidate_path_cache
def patch(self, request):
# Get the instance
instance = Instance.objects.first()
@ -104,6 +107,7 @@ class InstanceAdminEndpoint(BaseAPIView):
serializer = InstanceAdminSerializer(instance_admin)
return Response(serializer.data, status=status.HTTP_201_CREATED)
@invalidate_path_cache("/api/instances/")
def get(self, request):
instance = Instance.objects.first()
if instance is None:
@ -115,6 +119,7 @@ class InstanceAdminEndpoint(BaseAPIView):
serializer = InstanceAdminSerializer(instance_admins, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_path_cache("/api/instances/")
def delete(self, request, pk):
instance = Instance.objects.first()
instance_admin = InstanceAdmin.objects.filter(
@ -135,6 +140,8 @@ class InstanceConfigurationEndpoint(BaseAPIView):
)
return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_path_cache("/api/configs/")
@invalidate_path_cache("/api/mobile-configs/")
def patch(self, request):
configurations = InstanceConfiguration.objects.filter(
key__in=request.data.keys()
@ -170,6 +177,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
AllowAny,
]
@invalidate_path_cache("/api/instances/")
def post(self, request):
# Check instance first
instance = Instance.objects.first()
@ -260,6 +268,7 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
AllowAny,
]
@invalidate_path_cache("/api/instances/")
def post(self, request):
instance = Instance.objects.first()
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