From d2cc4c2ec6a4c2178d6d793a333f9748fb8bb1fd Mon Sep 17 00:00:00 2001 From: Haydar Ghasemi Date: Sat, 15 Aug 2026 09:43:47 +0330 Subject: [PATCH] FIX(winofy): add gooyal auth add gooyal auth --- .env.example | 40 ++++++++++++++++++++++++++++++++++++++-- apps/catalog/views.py | 9 ++++++++- apps/locations/views.py | 7 ++++++- apps/orders/views.py | 39 ++++++++++++++++++++++++++++++++------- apps/reviews/views.py | 14 ++++++++++++-- apps/stores/views.py | 14 ++++++++++++-- 6 files changed, 108 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index dafb604..efdeeb2 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,11 @@ SECRET_KEY=change-me DEBUG=True -ALLOWED_HOSTS=localhost,127.0.0.1 +ALLOWED_HOSTS=winofy-staging.winsoo.ir,localhost,127.0.0.1 DB_NAME=winofy_dev DB_USER= DB_PASSWORD= DB_HOST=localhost -DB_PORT=5432 REDIS_URL=redis://localhost:6379/1 @@ -26,3 +25,40 @@ OAUTH2_PROVIDER_SCOPES= # macOS only — not needed on Linux where GDAL is on the system path # GDAL_LIBRARY_PATH=/opt/homebrew/lib/libgdal.dylib # GEOS_LIBRARY_PATH=/opt/homebrew/lib/libgeos_c.dylib + + - API_BASE_PATH=api/ + - SECRET_KEY=change-me + - ALLOWED_HOSTS=* + - CSRF_TRUSTED_ORIGINS=http://*,https://*,http://*.winsoo.ir,https://*.winsoo.ir + - TZ=Asia/Tehran + - DB_NAME=reservation_db + - DB_USER=root + - DB_HOST=reservation_db + - DB_PASSWORD=123456 + - DEBUG=true + - BASE_OAUTH2_PROVIDER_PUBLIC_URL=https://accounts-staging.gooyal.ir/oauth2 + - BASE_OAUTH2_PROVIDER_PRIVATE_URL=https://accounts-staging.gooyal.ir/oauth2 + - CLIENT_ID=ULKUBOKeBGeP0hkTlzbfHhHXe4qfPZcg9H44qqh1 + - CLIENT_SECRET=Cq8ZVkkQdP8IHOKxum4lkBVZOXE9OLmqWY1oseePOQh7KmCjYryWbgN5aeqwtXr6EzK8ttdbFm5Yt25ApZkb6ceze3TOPlObQ125UARin9A7DbjuDOHPar2tEafPWHlL + - SCOPES=wallet.wallet:get_balance wallet.application.deposit:verify wallet.application.deposit:submit wallet.application.withdraw:submit wallet.application.withdraw:verify walle> + - OAUTH2_PROVIDER_BASE_PUBLIC_URL=https://accounts-staging.gooyal.ir/oauth2 + - OAUTH2_PROVIDER_BASE_PRIVATE_URL=https://accounts-staging.gooyal.ir/oauth2 + - OAUTH2_PROVIDER_CLIENT_ID=ULKUBOKeBGeP0hkTlzbfHhHXe4qfPZcg9H44qqh1 + - OAUTH2_PROVIDER_CLIENT_SECRET=Cq8ZVkkQdP8IHOKxum4lkBVZOXE9OLmqWY1oseePOQh7KmCjYryWbgN5aeqwtXr6EzK8ttdbFm5Yt25ApZkb6ceze3TOPlObQ125UARin9A7DbjuDOHPar2tEafPWHlL + - OAUTH2_PROVIDER_SCOPES=wallet.wallet:get_balance wallet.application.deposit:verify wallet.application.deposit:submit wallet.application.withdraw:submit wallet.application.withd> + - BASE_REDIS_URL=redis://redis:6379/3 + - REDIS_BASE_URL=redis://redis:6379/3 + - MINIO_ENDPOINT=drive.gooyal.ir + - MINIO_ACCESS_KEY=Irg3u493z63iJG3wJxah + - MINIO_SECRET_KEY=VctFgYVsvBSYd8gHanblS0QnRjot5tUFvtQLl3TX + - MINIO_USE_HTTPS=True + - MINIO_BUCKET_NAME=winsoo-reservation-media-files-bucket + - NOTIFICATIONS_EVENT_QUEUE=5527eced-a7c9-49d6-a59b-0bc40a81a1d6 + - NOTIFICATIONS_OPERATOR_USER_UUID=7123eece-efda-4821-83a5-27dfa7bfd663 + - NOTIFICATIONS_BASE_PUBLIC_URL=https://notifications-staging.gooyal.ir + - WALLET_BASE_PUBLIC_URL=https://wallet-staging.gooyal.ir + - WALLET_RIAL=af7d967f-30c0-409b-9066-2549f2da5e5e + - WALLET_RIAL_WALLET=af7d967f-30c0-409b-9066-2549f2da5e5e + - WALLET_REWARD=e7c9d4d1-4d1f-43b2-96f7-4d4a168f480d + - WALLET_REWARD_WALLET=e7c9d4d1-4d1f-43b2-96f7-4d4a168f480d + - LOKI_BASE_PUBLIC_URL=https://loki.winsoo.ir:443 diff --git a/apps/catalog/views.py b/apps/catalog/views.py index 4073713..339a660 100644 --- a/apps/catalog/views.py +++ b/apps/catalog/views.py @@ -4,6 +4,8 @@ from rest_framework.decorators import action from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements + from apps.core.permissions import IsStoreOwner from apps.stores.models import Store @@ -60,8 +62,13 @@ class SellerProductViewSet(viewsets.ModelViewSet): """Seller's own product management (S06 list, S07 add, S08 inventory).""" schema_tags = ['Seller · Products'] - permission_classes = [IsAuthenticated, IsStoreOwner] serializer_class = SellerProductSerializer + # permission_classes = [IsAuthenticated, IsStoreOwner] + # TODO: IsStoreOwner? + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): diff --git a/apps/locations/views.py b/apps/locations/views.py index 6e9ab39..f156533 100644 --- a/apps/locations/views.py +++ b/apps/locations/views.py @@ -1,6 +1,8 @@ from rest_framework import mixins, viewsets from rest_framework.permissions import AllowAny, IsAuthenticated +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements + from .models import Address, City, Neighborhood from .serializers import AddressSerializer, CitySerializer, NeighborhoodSerializer @@ -28,8 +30,11 @@ class NeighborhoodViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, view class AddressViewSet(viewsets.ModelViewSet): schema_tags = ['Addresses'] - permission_classes = [IsAuthenticated] serializer_class = AddressSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): diff --git a/apps/orders/views.py b/apps/orders/views.py index 8a2aaa6..8f6c713 100644 --- a/apps/orders/views.py +++ b/apps/orders/views.py @@ -6,6 +6,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements + from apps.core.permissions import IsStoreOwner from . import services @@ -27,8 +29,11 @@ class CartView(APIView): """The authenticated user's cart, grouped by store (C07).""" schema_tags = ['Cart'] - permission_classes = [IsAuthenticated] serializer_class = CartSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get(self, request): cart, _ = Cart.objects.get_or_create(user=request.user) @@ -45,8 +50,11 @@ class CartItemView(APIView): """Add/update/remove a single product line in the authenticated user's cart.""" schema_tags = ['Cart'] - permission_classes = [IsAuthenticated] serializer_class = CartItemWriteSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } @extend_schema(request=CartItemWriteSerializer, responses=CartItemSerializer) def post(self, request): @@ -86,8 +94,11 @@ class CheckoutView(APIView): """Splits the authenticated customer's multi-store cart into per-store orders (C08).""" schema_tags = ['Checkout'] - permission_classes = [IsAuthenticated] serializer_class = CheckoutSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } @extend_schema(request=CheckoutSerializer, responses=OrderGroupSerializer) def post(self, request): @@ -102,8 +113,11 @@ class OrderGroupViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewse """Customer order history — each group may contain orders from several stores.""" schema_tags = ['Orders'] - permission_classes = [IsAuthenticated] serializer_class = OrderGroupSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -115,8 +129,11 @@ class OrderViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): """Customer-facing single-order tracking (C09) + cancel.""" schema_tags = ['Orders'] - permission_classes = [IsAuthenticated] serializer_class = OrderSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -136,8 +153,13 @@ class SellerOrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, views """Seller order management (S09 list w/ status tabs, S10 detail + stepper actions).""" schema_tags = ['Seller · Orders'] - permission_classes = [IsAuthenticated, IsStoreOwner] serializer_class = OrderSerializer + # permission_classes = [IsAuthenticated, IsStoreOwner] + # TODO: IsStoreOwner + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -184,8 +206,11 @@ class NotificationViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, view """Shared notification feed (S16 for sellers; same model serves the customer app).""" schema_tags = ['Notifications'] - permission_classes = [IsAuthenticated] serializer_class = NotificationSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): diff --git a/apps/reviews/views.py b/apps/reviews/views.py index f3c9ca0..3d58eda 100644 --- a/apps/reviews/views.py +++ b/apps/reviews/views.py @@ -3,6 +3,7 @@ from rest_framework.decorators import action from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements from apps.core.permissions import IsStoreOwner from .models import Review @@ -14,10 +15,14 @@ class ReviewViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.Gen schema_tags = ['Reviews'] serializer_class = ReviewSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_permissions(self): if self.action == 'create': - return [IsAuthenticated()] + return [IsAuthenticatedOrTokenMatchesOASRequirements] return [AllowAny()] def get_queryset(self): @@ -32,8 +37,13 @@ class SellerReviewViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """Reviews left for the authenticated seller's store, with reply support.""" schema_tags = ['Seller · Reviews'] - permission_classes = [IsAuthenticated, IsStoreOwner] serializer_class = ReviewSerializer + # permission_classes = [IsAuthenticated, IsStoreOwner] + # TODO: + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get_queryset(self): if getattr(self, 'swagger_fake_view', False): diff --git a/apps/stores/views.py b/apps/stores/views.py index 8c7d130..6ff4f7a 100644 --- a/apps/stores/views.py +++ b/apps/stores/views.py @@ -9,6 +9,8 @@ from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements + from apps.core.permissions import IsStoreOwner from .models import Store, StoreCategory, StoreWorkingHours @@ -70,8 +72,11 @@ class SellerStoreView(APIView): """The authenticated seller's own store — GET/PATCH to manage it, POST to create it.""" schema_tags = ['Seller · Store'] - permission_classes = [IsAuthenticated] serializer_class = SellerStoreSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get(self, request): store = get_object_or_404(Store, owner=request.user) @@ -102,8 +107,13 @@ class SellerStoreWorkingHoursView(APIView): """Bulk get/set the authenticated seller's weekly working hours (S14).""" schema_tags = ['Seller · Store'] - permission_classes = [IsAuthenticated, IsStoreOwner] serializer_class = StoreWorkingHoursSerializer + # permission_classes = [IsAuthenticated, IsStoreOwner] + # TODO: + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "POST": [[]], + } def get(self, request): hours = StoreWorkingHours.objects.filter(store=request.user.store)