From 00e2c234090f99569351762056ab06182cfc3a04 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Tue, 12 Aug 2025 12:22:05 +0330 Subject: [PATCH] validation for pos device - some changes in pos device models --- Rasaddam_Backend/settings.py | 3 +- ...ization_en_name_alter_organization_name.py | 23 +++++ apps/authentication/models.py | 3 +- apps/pos_device/middlewares.py | 85 +++++++++++++++---- ...eviceactivationcode_expires_at_and_more.py | 34 ++++++++ ...4_alter_deviceactivationcode_expires_at.py | 18 ++++ apps/pos_device/models.py | 8 +- .../pos/api/v1/serializers/device.py | 0 apps/pos_device/pos/api/v1/urls.py | 10 +++ apps/pos_device/pos/api/v1/viewsets/device.py | 8 ++ apps/pos_device/urls.py | 3 +- apps/pos_device/web/api/v1/viewsets/device.py | 2 +- 12 files changed, 174 insertions(+), 23 deletions(-) create mode 100644 apps/authentication/migrations/0033_organization_en_name_alter_organization_name.py create mode 100644 apps/pos_device/migrations/0053_alter_deviceactivationcode_expires_at_and_more.py create mode 100644 apps/pos_device/migrations/0054_alter_deviceactivationcode_expires_at.py create mode 100644 apps/pos_device/pos/api/v1/serializers/device.py create mode 100644 apps/pos_device/pos/api/v1/viewsets/device.py diff --git a/Rasaddam_Backend/settings.py b/Rasaddam_Backend/settings.py index 474ba12..85405d0 100644 --- a/Rasaddam_Backend/settings.py +++ b/Rasaddam_Backend/settings.py @@ -106,7 +106,8 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'apps.authentication.middlewares.BlockedTokenMiddleware', 'crum.CurrentRequestUserMiddleware', - 'apps.log.middlewares.SaveLog' + 'apps.log.middlewares.SaveLog', + 'apps.pos_device.middlewares.PosDeviceValidationMiddleware' ] ROOT_URLCONF = 'Rasaddam_Backend.urls' diff --git a/apps/authentication/migrations/0033_organization_en_name_alter_organization_name.py b/apps/authentication/migrations/0033_organization_en_name_alter_organization_name.py new file mode 100644 index 0000000..2cdee10 --- /dev/null +++ b/apps/authentication/migrations/0033_organization_en_name_alter_organization_name.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0 on 2025-08-12 08:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0032_organization_has_pos'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='en_name', + field=models.CharField(max_length=150, null=True), + ), + migrations.AlterField( + model_name='organization', + name='name', + field=models.CharField(max_length=250, null=True), + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index a0e050c..fa54094 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -92,7 +92,8 @@ class OrganizationType(BaseModel): class Organization(BaseModel): - name = models.CharField(max_length=50) + name = models.CharField(max_length=250, null=True) + en_name = models.CharField(max_length=150, null=True) type = models.ForeignKey( 'OrganizationType', on_delete=models.CASCADE, diff --git a/apps/pos_device/middlewares.py b/apps/pos_device/middlewares.py index 414a352..acc2e46 100644 --- a/apps/pos_device/middlewares.py +++ b/apps/pos_device/middlewares.py @@ -1,12 +1,27 @@ -from rest_framework.exceptions import APIException +import traceback from django.utils.timezone import now - -from apps.pos_device.models import DeviceVersion, ProviderCompany, Sessions +from django.http import JsonResponse +from rest_framework import status +from apps.pos_device.models import Sessions, DeviceVersion +from apps.authentication.models import Organization -class POSDeviceMiddleware: +class POSDeviceException(Exception): + """Custom Exception for POS Validation""" + def __init__(self, message, code=400): + self.message = message + self.code = code + super().__init__(message) + + +def get_client_ip(request): + forwarded = request.META.get('HTTP_X_FORWARDED_FOR') + return forwarded.split(',')[0] if forwarded else request.META.get('REMOTE_ADDR') + + +class PosDeviceValidationMiddleware: REQUIRED_HEADERS = [ - 'device-id', 'device-mac', 'device-serial', 'device-name', + 'device-mac', 'device-serial', 'device-name', 'device-sdk', 'device-provider', 'device-version', 'device-vname', 'device-lng', 'device-lot' # noqa ] @@ -14,18 +29,58 @@ class POSDeviceMiddleware: def __init__(self, get_response): self.get_response = get_response - def __call__(self, request, *args, **kwargs): - pass + def __call__(self, request): + try: + if request.path.startswith("/pos_device/pos/"): + self.validate_request(request) - def is_post_request(self, request): # noqa - """ check if is pos request """ + return self.get_response(request) + except POSDeviceException as e: + return JsonResponse({'message': e.message}, status=e.code) - has_device_headers = request.headers.get('device-id') and request.headers.get('device-mac') - is_pos_api_path = request.path.startswith('/api/pos/') - return has_device_headers or is_pos_api_path + # for response 500 errors in json format + except Exception as e: + if request.path.startswith('/pos_device/pos/'): + return JsonResponse( + {"message": str(e), "traceback": traceback.format_exc()}, + status=500 + ) + raise - def validate_pos_request(self, request): - """ validate request headers from pos device """ + def validate_request(self, request): + headers = request.headers.kiani + data = {key: headers.get(key) for key in self.REQUIRED_HEADERS} - data = {key: request.headers.get(key) for key in self.REQUIRED_HEADERS} + missing = [key for key, value in data.items() if not value] + if missing: + raise POSDeviceException(f'پارامترهای ارسالی ناقص هستند: {", ".join(missing)}') # noqa + provider_name = data['device-provider'] + organization = Organization.objects.filter(en_name=provider_name).first() # noqa + if not organization: + raise POSDeviceException('شرکت پرداخت الکترونیک پشتیبانی نمی‌شود!', code=402) # noqa + if not organization.active: + raise POSDeviceException('شرکت پرداخت الکترونیک مسدود شده است!', code=402) # noqa + + versions = DeviceVersion.objects.filter(organization=organization) + if not versions.exists(): + raise POSDeviceException('هیچ نسخه‌ای برای این شرکت ثبت نشده است!', code=402) # noqa + + current_version = versions.filter(code=data['device-version']).first() + if not current_version or current_version.remove: + raise POSDeviceException( + f'نسخه {data["device-vname"]} منقضی شده است. لطفا بروزرسانی کنید.', code=402 # noqa + ) + + session = Sessions.objects.filter( + device__serial=data['device-serial'], + sdk=data['device-sdk'] + ).first() + + if session: + session.session_last_seen_date = now() + session.lng = data['device-lng'] + session.lot = data['device-lot'] + session.version = data['device-version'] + session.ip = get_client_ip(request) + session.save() diff --git a/apps/pos_device/migrations/0053_alter_deviceactivationcode_expires_at_and_more.py b/apps/pos_device/migrations/0053_alter_deviceactivationcode_expires_at_and_more.py new file mode 100644 index 0000000..515cc0e --- /dev/null +++ b/apps/pos_device/migrations/0053_alter_deviceactivationcode_expires_at_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0 on 2025-08-12 08:48 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0052_alter_deviceactivationcode_expires_at'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(default=datetime.datetime(2025, 8, 12, 12, 18, 2, 264809)), + ), + migrations.AlterField( + model_name='sessions', + name='latitude', + field=models.FloatField(default=0), + ), + migrations.AlterField( + model_name='sessions', + name='longitude', + field=models.FloatField(default=0), + ), + migrations.AlterField( + model_name='sessions', + name='name', + field=models.CharField(max_length=250, null=True), + ), + ] diff --git a/apps/pos_device/migrations/0054_alter_deviceactivationcode_expires_at.py b/apps/pos_device/migrations/0054_alter_deviceactivationcode_expires_at.py new file mode 100644 index 0000000..81b055f --- /dev/null +++ b/apps/pos_device/migrations/0054_alter_deviceactivationcode_expires_at.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-08-12 08:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pos_device', '0053_alter_deviceactivationcode_expires_at_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceactivationcode', + name='expires_at', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/apps/pos_device/models.py b/apps/pos_device/models.py index d112e41..c6d8ca9 100644 --- a/apps/pos_device/models.py +++ b/apps/pos_device/models.py @@ -80,7 +80,7 @@ class DeviceActivationCode(BaseModel): null=True ) code = models.CharField(max_length=10, null=True, unique=True) - expires_at = models.DateTimeField(default=datetime.datetime.now()) + expires_at = models.DateTimeField(auto_now_add=True) is_used = models.BooleanField(default=False) def __str__(self): @@ -123,15 +123,15 @@ class Sessions(BaseModel): related_name='devices', null=True ) - name = models.CharField(max_length=125, null=True) + name = models.CharField(max_length=250, null=True) password = models.CharField(max_length=25, null=True) version = models.IntegerField(default=0) mac = models.CharField(max_length=50, null=True) ip = models.CharField(max_length=15, default='0.0.0.0') sdk = models.TextField(null=True) serial = models.TextField(null=True) - latitude = models.DecimalField(max_digits=20, decimal_places=10, null=True) - longitude = models.DecimalField(max_digits=20, decimal_places=10, null=True) + latitude = models.FloatField(default=0) + longitude = models.FloatField(default=0) def __str__(self): return f'Session: {self.name}-{self.version}-{self.id}' diff --git a/apps/pos_device/pos/api/v1/serializers/device.py b/apps/pos_device/pos/api/v1/serializers/device.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pos_device/pos/api/v1/urls.py b/apps/pos_device/pos/api/v1/urls.py index e69de29..c28983a 100644 --- a/apps/pos_device/pos/api/v1/urls.py +++ b/apps/pos_device/pos/api/v1/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .viewsets.device import TestViewSet + +router = DefaultRouter() +router.register('test', TestViewSet, basename='test') + +urlpatterns = [ + path('v1/', include(router.urls)) +] diff --git a/apps/pos_device/pos/api/v1/viewsets/device.py b/apps/pos_device/pos/api/v1/viewsets/device.py new file mode 100644 index 0000000..54f6d5b --- /dev/null +++ b/apps/pos_device/pos/api/v1/viewsets/device.py @@ -0,0 +1,8 @@ +from rest_framework import viewsets +from rest_framework.response import Response + + +class TestViewSet(viewsets.ModelViewSet): + + def list(self, request, *args, **kwargs): + return Response("Hello from the outsiiiiiiiiide") # noqa diff --git a/apps/pos_device/urls.py b/apps/pos_device/urls.py index 9fbf25e..937ec75 100644 --- a/apps/pos_device/urls.py +++ b/apps/pos_device/urls.py @@ -1,5 +1,6 @@ from django.urls import path, include urlpatterns = [ - path('web/', include('apps.pos_device.web.api.v1.urls')) + path('web/', include('apps.pos_device.web.api.v1.urls')), + path('pos/', include('apps.pos_device.pos.api.v1.urls')) ] diff --git a/apps/pos_device/web/api/v1/viewsets/device.py b/apps/pos_device/web/api/v1/viewsets/device.py index b52adb1..77045ea 100644 --- a/apps/pos_device/web/api/v1/viewsets/device.py +++ b/apps/pos_device/web/api/v1/viewsets/device.py @@ -121,7 +121,7 @@ class DeviceViewSet(viewsets.ModelViewSet, AdminFilterMixin): @transaction.atomic def psp_organizations(self, request): """ list of psp organizations """ - + print(request.path) organizations = Organization.objects.filter(type__key='PSP') # paginate devices