From 50424b7e226e4f6873af13a89fc07be19bb032bb Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 21 Aug 2025 00:29:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#88]=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=EC=97=90=EC=84=9C=20redis=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.py | 1 + 1 file changed, 1 insertion(+) 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', From b5476d9cb09e8b2b7d4603e2dc6af6be4787234a Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 21 Aug 2025 00:30:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#88]=20get=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=95=84=EB=8B=90=20=EB=95=8C,=20is=5Fpublished=3D?= =?UTF-8?q?False=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/permissions.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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: From 3152346ec739c92c0b1a51f37c3d231066e964bc Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 21 Aug 2025 00:31:04 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#88]=2024=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=82=B4=20=EC=A1=B0=ED=9A=8C=EC=88=98=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/services.py | 28 ++++++++++++++++++++++++++++ nocodetools/views.py | 9 ++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) 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: