From c64977094112e3a125d115c7845d1ea234f01633 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 20:08:55 +0330 Subject: [PATCH 01/12] order cart payment update added signals for order --- backend/account/models.py | 9 ++++++++- backend/core/settings/base.py | 2 +- backend/order/apps.py | 3 +++ backend/order/signals.py | 23 +++++++++++++++++++++++ backend/order/urls.py | 2 +- backend/order/views.py | 13 +++++++++++++ 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 backend/order/signals.py diff --git a/backend/account/models.py b/backend/account/models.py index b270618..da7d7cf 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -241,4 +241,11 @@ class SecurityBreachAttemptModel(models.Model): return f'تلاش نفوذ از {self.ip_address} در {self.city}, {self.country}' class Meta: verbose_name = "تلاش نفوذ" - verbose_name_plural = "تلاش‌های نفوذ" \ No newline at end of file + verbose_name_plural = "تلاش‌های نفوذ" + +# class NotifModel(models.Model): +# subject = models.CharField(max_length=100) +# description = models.TextField() + +# def __str__(self): +# return f'{self.subject[:30]}' \ No newline at end of file diff --git a/backend/core/settings/base.py b/backend/core/settings/base.py index 1c00999..ababbb6 100644 --- a/backend/core/settings/base.py +++ b/backend/core/settings/base.py @@ -143,7 +143,7 @@ USE_TZ = True # Static Files Configuration # ============================================================================== STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "custom_static"), + # os.path.join(BASE_DIR, "custom_static"), BASE_DIR / "core" / "static", ] diff --git a/backend/order/apps.py b/backend/order/apps.py index 60f4dff..eca896d 100644 --- a/backend/order/apps.py +++ b/backend/order/apps.py @@ -5,3 +5,6 @@ class OrderConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'order' verbose_name = 'سفارش' + + def ready(self): + import order.signals \ No newline at end of file diff --git a/backend/order/signals.py b/backend/order/signals.py new file mode 100644 index 0000000..b2234b9 --- /dev/null +++ b/backend/order/signals.py @@ -0,0 +1,23 @@ +from django.db.models.signals import pre_save +from django.dispatch import receiver +from .models import OrderModel + +@receiver(pre_save, sender=OrderModel) +def order_status_changed(sender, instance, **kwargs): + if instance.pk: + previous = OrderModel.objects.get(pk=instance.pk) + + print(f'pervios {previous.status} instance {instance.status}') + if previous.status != instance.status: + send_change_status_notif(instance) + + +def send_change_status_notif(order): + pass + +def update_cart_price_fields(order): + pass + +def update_sell_data(order): + pass + diff --git a/backend/order/urls.py b/backend/order/urls.py index 969c612..e81de18 100644 --- a/backend/order/urls.py +++ b/backend/order/urls.py @@ -10,7 +10,7 @@ urlpatterns = [ path('cart/discount', ApplyDiscountView.as_view()), path('cart/all', CartItemClear.as_view()), path('cart/item/', CartItemViews.as_view(), name='change-item-cart'), - path('payment', PaymentView.as_view(), name='payment'), + path('cart/payment', PaymentView.as_view(), name='payment'), path('callback', callback_view, name='callback-gateway'), path('', OrderGetView.as_view(), name='order-get'), ] diff --git a/backend/order/views.py b/backend/order/views.py index 2f2c5b5..08c39b6 100644 --- a/backend/order/views.py +++ b/backend/order/views.py @@ -176,9 +176,22 @@ class OrderGetView(APIView): order_ser = self.serializer_class(order_object, context={'request': request}) return Response(order_ser.data, status=status.HTTP_200_OK) + + +from rest_framework import serializers + +class BankTypeSerializer(serializers.Serializer): + gateway_type = serializers.ChoiceField(choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']) + + class PaymentView(APIView): permission_classes = [IsAuthenticated] + @extend_schema( + request=BankTypeSerializer, + description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']" + ) def post(self, request): + print(request.data.get('gateway_type')) cart_order = get_object_or_404(OrderModel, user=request.user, status='CART') amount = 5000 user_mobile_number = request.user.phone From f28736312a85d1df461fd5c9043be6602e060861 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 20:12:08 +0330 Subject: [PATCH 02/12] new pip line --- .github/workflows/deploy.yaml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 6c287c1..68d8ac9 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -8,28 +8,35 @@ on: jobs: deploy: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Copy files to server - uses: appleboy/scp-action@v0.1.6 + uses: appleboy/scp-action@v0.1.10 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SSH_USER }} password: ${{ secrets.SSH_PASSWORD }} source: "." target: "/root/hshop/" + rm: true - - name: SSH command to build and start Docker - uses: appleboy/ssh-action@v0.1.6 + - name: Build and start Docker containers + uses: appleboy/ssh-action@v0.1.10 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SSH_USER }} password: ${{ secrets.SSH_PASSWORD }} script: | cd /root/hshop/ - docker compose down - docker compose build - docker compose up -d \ No newline at end of file + + docker compose down --remove-orphans --timeout 60 + + docker compose up --build --detach --remove-orphans + + docker image prune -af + + docker compose ps \ No newline at end of file From 8af10e92fe85eb40c8ac6a9cc25405b97621a331 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 20:23:48 +0330 Subject: [PATCH 03/12] fix version ci cd --- .github/workflows/deploy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 68d8ac9..f96531f 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Copy files to server - uses: appleboy/scp-action@v0.1.10 + uses: appleboy/scp-action@v0.1.6 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SSH_USER }} @@ -25,7 +25,7 @@ jobs: rm: true - name: Build and start Docker containers - uses: appleboy/ssh-action@v0.1.10 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SSH_USER }} From 67fee905288f29044e2b8942fad903d83e47c15b Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 20:55:14 +0330 Subject: [PATCH 04/12] update payment view --- backend/order/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/order/views.py b/backend/order/views.py index 08c39b6..060326a 100644 --- a/backend/order/views.py +++ b/backend/order/views.py @@ -186,8 +186,8 @@ class BankTypeSerializer(serializers.Serializer): class PaymentView(APIView): permission_classes = [IsAuthenticated] + serializer_class = BankTypeSerializer @extend_schema( - request=BankTypeSerializer, description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']" ) def post(self, request): From 52067934153e624de470241c88e11ad8976a7a94 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 20:58:23 +0330 Subject: [PATCH 05/12] clean code and add unsubscribe --- backend/account/urls.py | 3 +++ backend/account/views.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/backend/account/urls.py b/backend/account/urls.py index f3cb639..958cb0c 100644 --- a/backend/account/urls.py +++ b/backend/account/urls.py @@ -2,6 +2,8 @@ from django.urls import path from . import views from djoser.urls.jwt import views as djoser_jwt_views + + urlpatterns = [ path('profile', views.ProfileView.as_view()), path('verify', djoser_jwt_views.TokenVerifyView.as_view(), name='jwt-verify'), @@ -13,6 +15,7 @@ urlpatterns = [ path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'), path('address/', views.GetIDUserAddressView.as_view(), name='get-ID-address'), path('subscribe', views.SubscribeView.as_view(), name='subscibe'), + path('unsubscribe', views.UnsubscribeView.as_view(), name='unsubscibe'), path('attack/view/', views.ChangeViewAttack.as_view(), name='attack-view'), path('logout', views.LogoutView.as_view(), name='logout'), ] \ No newline at end of file diff --git a/backend/account/views.py b/backend/account/views.py index 2b52687..4c37a3b 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -11,12 +11,11 @@ from django.shortcuts import get_object_or_404, redirect from rest_framework_simplejwt.tokens import RefreshToken import ghasedak_sms from django.views import View -# this works only need to be used -# class APIView(APIView): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# if AllowAny in self.permission_classes or not self.permission_classes: -# self.authentication_classes = [] +from rest_framework import serializers +from rest_framework_simplejwt.tokens import RefreshToken + + + class SendOTPView(APIView): permission_classes = [AllowAny] @extend_schema( @@ -190,6 +189,25 @@ class SubscribeView(APIView): return Response(status=status.HTTP_400_BAD_REQUEST) +class UnsubscribeSerializer(serializers.Serializer): + end_point = serializers.CharField() + +class UnsubscribeView(APIView): + permission_classes = [IsAuthenticated] + serializer_class = UnsubscribeSerializer + def post(self, request): + endpoint = request.data.get("end_point") + if not endpoint: + return Response({"detail": "اند پوینت لازم است"}, status=status.HTTP_400_BAD_REQUEST) + + deleted, _ = PushSubscription.objects.filter(user=request.user, endpoint=endpoint).delete() + + if deleted: + return Response({"detail": "با موفقیت اشتراک پاک شد"}, status=status.HTTP_200_OK) + + return Response({"detail": "اند پوینت پیدا نشد"}, status=status.HTTP_404_NOT_FOUND) + + class ChangeViewAttack(View): def get(self, request, pk): attack = get_object_or_404(SecurityBreachAttemptModel, pk=pk) @@ -198,8 +216,6 @@ class ChangeViewAttack(View): return redirect('admin:account_securitybreachattemptmodel_changelist') -from rest_framework import serializers -from rest_framework_simplejwt.tokens import RefreshToken class LogoutSerializer(serializers.Serializer): refresh_token = serializers.CharField(help_text="Refresh token to be blacklisted") From 843caa9ae1746512530a69589b199b55a48f5611 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 21:05:36 +0330 Subject: [PATCH 06/12] test .env.local writer --- .github/workflows/deploy.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f96531f..aebe0d1 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -24,6 +24,16 @@ jobs: target: "/root/hshop/" rm: true + - name: Deploy environment file + uses: appleboy/ssh-action@v0.1.6 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SSH_USER }} + password: ${{ secrets.SSH_PASSWORD }} + script: | + mkdir -p /root/hshop/backend/ + printf "%s" "${{ secrets.ENV_FILE_CONTENT }}" > /root/hshop/backend/.env.local + - name: Build and start Docker containers uses: appleboy/ssh-action@v0.1.6 with: From 15a85a0065e6d8e4c3752781eef52c87db0685ea Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 21:27:40 +0330 Subject: [PATCH 07/12] remove print form signal --- backend/order/signals.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/order/signals.py b/backend/order/signals.py index b2234b9..c650883 100644 --- a/backend/order/signals.py +++ b/backend/order/signals.py @@ -7,7 +7,6 @@ def order_status_changed(sender, instance, **kwargs): if instance.pk: previous = OrderModel.objects.get(pk=instance.pk) - print(f'pervios {previous.status} instance {instance.status}') if previous.status != instance.status: send_change_status_notif(instance) From 7b699ba9dbbd4b2c92e96f897476164a988804e1 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 21:31:04 +0330 Subject: [PATCH 08/12] alter bank model migratoins --- .../migrations/0007_alter_bank_order.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 backend/azbankgateways/migrations/0007_alter_bank_order.py diff --git a/backend/azbankgateways/migrations/0007_alter_bank_order.py b/backend/azbankgateways/migrations/0007_alter_bank_order.py new file mode 100644 index 0000000..dc9f777 --- /dev/null +++ b/backend/azbankgateways/migrations/0007_alter_bank_order.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.2 on 2025-03-19 17:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('azbankgateways', '0006_bank_order'), + ('order', '0023_remove_ordermodel_bank_records'), + ] + + operations = [ + migrations.AlterField( + model_name='bank', + name='order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bank_records', to='order.ordermodel'), + ), + ] From 45b347e10c63e067472945ec95f10678e90e6f7a Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 21:55:46 +0330 Subject: [PATCH 09/12] fix GetOrderPermission bug --- backend/order/permissons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/order/permissons.py b/backend/order/permissons.py index af60575..5c05b4b 100644 --- a/backend/order/permissons.py +++ b/backend/order/permissons.py @@ -20,7 +20,7 @@ class GetOrderPermission(BasePermission): def has_object_permission(self, request, view, obj): if obj.user != request.user: return False - if obj.status != 'CART': + if obj.status == 'CART': self.message = "سفارش در وضعیت سبد خرید است" return False return True \ No newline at end of file From 1d5c1ac60d2ecda28398cd1c6758c7996ffc6119 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 22:06:22 +0330 Subject: [PATCH 10/12] set address view and permiosn --- backend/order/permissons.py | 16 +++++++++++++++- backend/order/urls.py | 3 ++- backend/order/views.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/backend/order/permissons.py b/backend/order/permissons.py index 5c05b4b..bef19d4 100644 --- a/backend/order/permissons.py +++ b/backend/order/permissons.py @@ -23,4 +23,18 @@ class GetOrderPermission(BasePermission): if obj.status == 'CART': self.message = "سفارش در وضعیت سبد خرید است" return False - return True \ No newline at end of file + return True + + + +from rest_framework.permissions import BasePermission + +class SetAddressPermissions(BasePermission): + message = "این ادرس متعلق به شما نیست." + + def has_object_permission(self, request, view, obj): + if obj.user != request.user: + self.message = "این ادرس متعلق به شما نیست." + return False + return True + \ No newline at end of file diff --git a/backend/order/urls.py b/backend/order/urls.py index e81de18..6ec9fd4 100644 --- a/backend/order/urls.py +++ b/backend/order/urls.py @@ -1,12 +1,13 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include -from .views import CartItemViews, CartView, OrderlistView, CartItemClear, ApplyDiscountView, OrderGetView +from .views import CartItemViews, CartView, OrderlistView, CartItemClear, ApplyDiscountView, OrderGetView, SetAddressForCartView from .views import PaymentView, callback_view urlpatterns = [ path('all', OrderlistView.as_view(), name='order-list'), path('cart', CartView.as_view()), + path('cart/set-address', SetAddressForCartView.as_view()), path('cart/discount', ApplyDiscountView.as_view()), path('cart/all', CartItemClear.as_view()), path('cart/item/', CartItemViews.as_view(), name='change-item-cart'), diff --git a/backend/order/views.py b/backend/order/views.py index 060326a..5e32129 100644 --- a/backend/order/views.py +++ b/backend/order/views.py @@ -7,13 +7,16 @@ from .serializers import * # from cart.models import from rest_framework import status from .models import OrderItemModel, OrderModel, DiscountCode -from .permissons import CanDeleteCartItemPermissions, GetOrderPermission +from .permissons import CanDeleteCartItemPermissions, GetOrderPermission, SetAddressPermissions from azbankgateways import bankfactories, models as bank_models from azbankgateways.exceptions import AZBankGatewaysException from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes from utils.pagination import StructurePagination from order.models import OrderModel from django.urls import reverse +from account.models import UserAddressModel + + # try: # pass # except DiscountNotAvailableError: @@ -249,4 +252,29 @@ def callback_view(request): return HttpResponse( "پرداخت با شکست مواجه شده است. اگر پول کم شده است ظرف مدت ۴۸ ساعت پول به حساب شما بازخواهد گشت." - ) \ No newline at end of file + ) + + + +class SetAddressSerilizer(serializers.Serializer): + address_id = serializers.IntegerField() + +class SetAddressForCartView(APIView): + serializer_class = SetAddressSerilizer + permission_classes = [IsAuthenticated, SetAddressPermissions] + def post(self, request): + address_id = request.data.get('address_id', None) + if not address_id: + return Response({'detail': 'address_id را ارسال کنید'}, status=status.HTTP_400_BAD_REQUEST) + address_object = get_object_or_404(UserAddressModel, pk=address_id) + permission = SetAddressPermissions() + if not permission.has_object_permission(request, self, address_object): + return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN) + + cart_order, created = OrderModel.objects.get_or_create( + user=request.user, + status='CART' + ) + cart_order.address = address_object + cart_order.save() + return Response({'detail': 'ادرس با موفقیت انتخاب شد'}) \ No newline at end of file From c1cba6c4949721da10af1aa58793680d06fa6f27 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 22:06:38 +0330 Subject: [PATCH 11/12] base signal update --- backend/order/signals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/order/signals.py b/backend/order/signals.py index c650883..7d53ab5 100644 --- a/backend/order/signals.py +++ b/backend/order/signals.py @@ -20,3 +20,5 @@ def update_cart_price_fields(order): def update_sell_data(order): pass +def update_quantity(order): + pass From e18d83340afe9618051c80e4edbc13a5c7165402 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Wed, 19 Mar 2025 22:07:17 +0330 Subject: [PATCH 12/12] set the first address as the main address as defualt --- backend/account/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/account/views.py b/backend/account/views.py index 4c37a3b..9799d2d 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -142,7 +142,9 @@ class CreateAddressView(generics.CreateAPIView): permission_classes = [permissions.IsAuthenticated] def perform_create(self, serializer): - serializer.save(user=self.request.user) + user = self.request.user + is_first_address = not UserAddressModel.objects.filter(user=user).exists() + serializer.save(user=user, is_main=is_first_address) class EditAddressView(generics.UpdateAPIView): queryset = UserAddressModel.objects.all()