diff --git a/config/settings.py b/config/settings.py index e34f0e6..f9d0b64 100644 --- a/config/settings.py +++ b/config/settings.py @@ -125,6 +125,7 @@ CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': f'redis://:{REDIS_PASSWORD}@redis:6379/1', + # 'LOCATION': f'redis://127.0.0.1:6379/1', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'SERIALIZER': 'django_redis.serializers.json.JSONSerializer', diff --git a/nocodetools/permissions.py b/nocodetools/permissions.py index 30b032f..1489bf2 100644 --- a/nocodetools/permissions.py +++ b/nocodetools/permissions.py @@ -3,8 +3,26 @@ from rest_framework.permissions import BasePermission from projects.models import Project, ProjectTeamList from portfolios.models import Portfolio +from .services import NocodetoolObjectMapService + UNSAFE_REQUEST = ["POST", "PUT", "PATCH", "DELETE"] +class IsNotPublished(BasePermission): + def has_permission(self, request, view): + if request.method not in UNSAFE_REQUEST: + return True + + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + + if not related_type or not related_id: + return False + + if obj := NocodetoolObjectMapService.mapping_model_instance(related_type, related_id): + if not obj.is_published: + return True + return False + class IsOwnerOrMemberInCreateAndUpdateAndDelete(BasePermission): def has_permission(self, request, view): if request.method not in UNSAFE_REQUEST: diff --git a/nocodetools/services.py b/nocodetools/services.py index b19203b..36df671 100644 --- a/nocodetools/services.py +++ b/nocodetools/services.py @@ -7,6 +7,11 @@ from portfolios.models import Portfolio from projects.serializers import ProjectNocodetoolSerializer from portfolios.serializers import PortfolioNocodetoolSerializer +from django.utils import timezone +from django.db.models import F + +from django_redis import get_redis_connection + NOCODETOOL_MODEL_MAP = { 'project': Project, 'portfolio': Portfolio, @@ -27,3 +32,26 @@ class NocodetoolObjectMapService: def mapping_model_serializer(related_type: str): return NOCODETOOL_SERIALIZER_MAP.get(related_type, None) + + +class NocodetoolHitService: + def hit_once(obj, request, ttl=60*60*24): + user_key = request.user.id if request.user.is_authenticated else request.session.session_key + + if not user_key: + request.session.save() + user_key = request.session.session_key() + + today = timezone.localdate().isoformat() + key = f"viewed:obj:{obj.id}:{today}" + + redis_conn = get_redis_connection("default") + + added = redis_conn.sadd(key, user_key) + if added: + if redis_conn.ttl(key) == -1: + redis_conn.expire(key, ttl) + + obj.__class__.objects.filter(id=obj.id).update(view_count=F("view_count") + 1) + return True + return False diff --git a/nocodetools/views.py b/nocodetools/views.py index 76251b2..9f38de4 100644 --- a/nocodetools/views.py +++ b/nocodetools/views.py @@ -7,8 +7,8 @@ from django.db import transaction from .models import Code, Page, Element from .serializers import CodeSerializer -from .permissions import IsOwnerOrMemberInCreateAndUpdateAndDelete -from .services import NocodetoolObjectMapService +from .permissions import IsOwnerOrMemberInCreateAndUpdateAndDelete, IsNotPublished +from .services import NocodetoolObjectMapService, NocodetoolHitService from users.models import User from portfolios.models import Portfolio @@ -18,13 +18,16 @@ from bson import ObjectId class NoCodeToolAPIView(APIView): - permission_classes = [IsAuthenticated, IsOwnerOrMemberInCreateAndUpdateAndDelete] + permission_classes = [IsAuthenticated, IsNotPublished, IsOwnerOrMemberInCreateAndUpdateAndDelete] + @transaction.atomic def get(self, request): related_type = request.query_params.get("type") related_id = request.query_params.get("id") code_id = None if obj := NocodetoolObjectMapService.mapping_model_instance(related_type, related_id): + if obj.is_published: + NocodetoolHitService.hit_once(obj, request) code_id = ObjectId(obj.code_id) if not code_id: