From cdbb2e11edca04078a6a4e69376b91bcc653ac41 Mon Sep 17 00:00:00 2001 From: mostafa7171 Date: Sun, 18 Jan 2026 12:05:56 +0330 Subject: [PATCH] first push --- .env.local | 25 + .env.prod | 24 + .gitignore | 1 + .idea/.gitignore | 8 + .idea/ArtaSystem.iml | 28 + .idea/dataSources.xml | 12 + .idea/inspectionProfiles/Project_Default.xml | 79 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/sqldialects.xml | 6 + .idea/vcs.xml | 6 + ArtaSystem/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 150 bytes .../__pycache__/settings.cpython-39.pyc | Bin 0 -> 5687 bytes ArtaSystem/__pycache__/urls.cpython-39.pyc | Bin 0 -> 1018 bytes ArtaSystem/__pycache__/wsgi.cpython-39.pyc | Bin 0 -> 559 bytes ArtaSystem/asgi.py | 16 + ArtaSystem/settings.py | 264 ++++++++ ArtaSystem/urls.py | 23 + ArtaSystem/wsgi.py | 16 + Authentication/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 154 bytes .../__pycache__/admin.cpython-39.pyc | Bin 0 -> 195 bytes .../__pycache__/apps.cpython-39.pyc | Bin 0 -> 447 bytes .../__pycache__/models.cpython-39.pyc | Bin 0 -> 1656 bytes .../__pycache__/serializers.cpython-39.pyc | Bin 0 -> 1482 bytes Authentication/__pycache__/sms.cpython-39.pyc | Bin 0 -> 765 bytes .../__pycache__/urls.cpython-39.pyc | Bin 0 -> 2093 bytes .../__pycache__/views.cpython-39.pyc | Bin 0 -> 12008 bytes Authentication/admin.py | 3 + Authentication/apps.py | 6 + Authentication/migrations/0001_initial.py | 65 ++ Authentication/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-39.pyc | Bin 0 -> 2267 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 165 bytes Authentication/models.py | 41 ++ Authentication/serializers.py | 31 + Authentication/sms.py | 17 + Authentication/tests.py | 3 + Authentication/urls.py | 60 ++ Authentication/views.py | 635 ++++++++++++++++++ Core/ArvanStorage/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 157 bytes .../__pycache__/arvan_storage.cpython-39.pyc | Bin 0 -> 1830 bytes Core/ArvanStorage/arvan_storage.py | 60 ++ Core/__init__.py | 0 Core/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 144 bytes Core/__pycache__/admin.cpython-39.pyc | Bin 0 -> 185 bytes Core/__pycache__/apps.cpython-39.pyc | Bin 0 -> 417 bytes Core/__pycache__/models.cpython-39.pyc | Bin 0 -> 1213 bytes Core/admin.py | 3 + Core/apps.py | 6 + Core/migrations/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 155 bytes Core/models.py | 33 + Core/tests.py | 3 + Core/views.py | 1 + Dockerfile | 59 ++ Notification/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 152 bytes Notification/__pycache__/admin.cpython-39.pyc | Bin 0 -> 193 bytes Notification/__pycache__/apps.cpython-39.pyc | Bin 0 -> 441 bytes .../__pycache__/models.cpython-39.pyc | Bin 0 -> 3002 bytes .../__pycache__/serializers.cpython-39.pyc | Bin 0 -> 1122 bytes Notification/__pycache__/urls.cpython-39.pyc | Bin 0 -> 541 bytes Notification/__pycache__/views.cpython-39.pyc | Bin 0 -> 4414 bytes Notification/admin.py | 3 + Notification/apps.py | 6 + Notification/migrations/0001_initial.py | 80 +++ Notification/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-39.pyc | Bin 0 -> 3094 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 163 bytes Notification/models.py | 89 +++ Notification/najva/__init__.py | 0 .../najva/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 158 bytes .../send_notif_to_segments.cpython-39.pyc | Bin 0 -> 2019 bytes Notification/najva/get_segments_detail.py | 17 + Notification/najva/send_notif_to_segments.py | 75 +++ Notification/serializers.py | 18 + Notification/tests.py | 3 + Notification/urls.py | 12 + Notification/views.py | 157 +++++ README.md | 2 + Wallet/__init__.py | 0 Wallet/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 146 bytes Wallet/__pycache__/admin.cpython-39.pyc | Bin 0 -> 187 bytes Wallet/__pycache__/apps.cpython-39.pyc | Bin 0 -> 423 bytes Wallet/__pycache__/errors.cpython-39.pyc | Bin 0 -> 596 bytes Wallet/__pycache__/models.cpython-39.pyc | Bin 0 -> 12376 bytes Wallet/__pycache__/processor.cpython-39.pyc | Bin 0 -> 4291 bytes Wallet/admin.py | 3 + Wallet/apps.py | 6 + Wallet/errors.py | 10 + Wallet/migrations/0001_initial.py | 204 ++++++ Wallet/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-39.pyc | Bin 0 -> 5964 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 157 bytes Wallet/models.py | 404 +++++++++++ Wallet/processor.py | 147 ++++ Wallet/serializers/__init__.py | 0 Wallet/serializers/customer.py | 29 + Wallet/tests.py | 3 + Wallet/views.py | 133 ++++ __pycache__/manage.cpython-39.pyc | Bin 0 -> 818 bytes docker-compose.yml | 7 + liara.json | 7 + manage.py | 22 + requirements.txt | 91 +++ 109 files changed, 3083 insertions(+) create mode 100644 .env.local create mode 100644 .env.prod create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/ArtaSystem.iml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/sqldialects.xml create mode 100644 .idea/vcs.xml create mode 100644 ArtaSystem/__init__.py create mode 100644 ArtaSystem/__pycache__/__init__.cpython-39.pyc create mode 100644 ArtaSystem/__pycache__/settings.cpython-39.pyc create mode 100644 ArtaSystem/__pycache__/urls.cpython-39.pyc create mode 100644 ArtaSystem/__pycache__/wsgi.cpython-39.pyc create mode 100644 ArtaSystem/asgi.py create mode 100644 ArtaSystem/settings.py create mode 100644 ArtaSystem/urls.py create mode 100644 ArtaSystem/wsgi.py create mode 100644 Authentication/__init__.py create mode 100644 Authentication/__pycache__/__init__.cpython-39.pyc create mode 100644 Authentication/__pycache__/admin.cpython-39.pyc create mode 100644 Authentication/__pycache__/apps.cpython-39.pyc create mode 100644 Authentication/__pycache__/models.cpython-39.pyc create mode 100644 Authentication/__pycache__/serializers.cpython-39.pyc create mode 100644 Authentication/__pycache__/sms.cpython-39.pyc create mode 100644 Authentication/__pycache__/urls.cpython-39.pyc create mode 100644 Authentication/__pycache__/views.cpython-39.pyc create mode 100644 Authentication/admin.py create mode 100644 Authentication/apps.py create mode 100644 Authentication/migrations/0001_initial.py create mode 100644 Authentication/migrations/__init__.py create mode 100644 Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc create mode 100644 Authentication/migrations/__pycache__/__init__.cpython-39.pyc create mode 100644 Authentication/models.py create mode 100644 Authentication/serializers.py create mode 100644 Authentication/sms.py create mode 100644 Authentication/tests.py create mode 100644 Authentication/urls.py create mode 100644 Authentication/views.py create mode 100644 Core/ArvanStorage/__init__.py create mode 100644 Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc create mode 100644 Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc create mode 100644 Core/ArvanStorage/arvan_storage.py create mode 100644 Core/__init__.py create mode 100644 Core/__pycache__/__init__.cpython-39.pyc create mode 100644 Core/__pycache__/admin.cpython-39.pyc create mode 100644 Core/__pycache__/apps.cpython-39.pyc create mode 100644 Core/__pycache__/models.cpython-39.pyc create mode 100644 Core/admin.py create mode 100644 Core/apps.py create mode 100644 Core/migrations/__init__.py create mode 100644 Core/migrations/__pycache__/__init__.cpython-39.pyc create mode 100644 Core/models.py create mode 100644 Core/tests.py create mode 100644 Core/views.py create mode 100644 Dockerfile create mode 100644 Notification/__init__.py create mode 100644 Notification/__pycache__/__init__.cpython-39.pyc create mode 100644 Notification/__pycache__/admin.cpython-39.pyc create mode 100644 Notification/__pycache__/apps.cpython-39.pyc create mode 100644 Notification/__pycache__/models.cpython-39.pyc create mode 100644 Notification/__pycache__/serializers.cpython-39.pyc create mode 100644 Notification/__pycache__/urls.cpython-39.pyc create mode 100644 Notification/__pycache__/views.cpython-39.pyc create mode 100644 Notification/admin.py create mode 100644 Notification/apps.py create mode 100644 Notification/migrations/0001_initial.py create mode 100644 Notification/migrations/__init__.py create mode 100644 Notification/migrations/__pycache__/0001_initial.cpython-39.pyc create mode 100644 Notification/migrations/__pycache__/__init__.cpython-39.pyc create mode 100644 Notification/models.py create mode 100644 Notification/najva/__init__.py create mode 100644 Notification/najva/__pycache__/__init__.cpython-39.pyc create mode 100644 Notification/najva/__pycache__/send_notif_to_segments.cpython-39.pyc create mode 100644 Notification/najva/get_segments_detail.py create mode 100644 Notification/najva/send_notif_to_segments.py create mode 100644 Notification/serializers.py create mode 100644 Notification/tests.py create mode 100644 Notification/urls.py create mode 100644 Notification/views.py create mode 100644 README.md create mode 100644 Wallet/__init__.py create mode 100644 Wallet/__pycache__/__init__.cpython-39.pyc create mode 100644 Wallet/__pycache__/admin.cpython-39.pyc create mode 100644 Wallet/__pycache__/apps.cpython-39.pyc create mode 100644 Wallet/__pycache__/errors.cpython-39.pyc create mode 100644 Wallet/__pycache__/models.cpython-39.pyc create mode 100644 Wallet/__pycache__/processor.cpython-39.pyc create mode 100644 Wallet/admin.py create mode 100644 Wallet/apps.py create mode 100644 Wallet/errors.py create mode 100644 Wallet/migrations/0001_initial.py create mode 100644 Wallet/migrations/__init__.py create mode 100644 Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc create mode 100644 Wallet/migrations/__pycache__/__init__.cpython-39.pyc create mode 100644 Wallet/models.py create mode 100644 Wallet/processor.py create mode 100644 Wallet/serializers/__init__.py create mode 100644 Wallet/serializers/customer.py create mode 100644 Wallet/tests.py create mode 100644 Wallet/views.py create mode 100644 __pycache__/manage.cpython-39.pyc create mode 100644 docker-compose.yml create mode 100644 liara.json create mode 100644 manage.py create mode 100644 requirements.txt diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..3ce9a73 --- /dev/null +++ b/.env.local @@ -0,0 +1,25 @@ +SECRET_KEY=django-insecure-&-inwstj0rt25wjb=#t8jq&96vb=h4y@udy&$^gj)ih-da3r4j +DEBUG=True +ALLOWED_HOSTS=testbackend.rasadyaar.ir,test.rasadyaar.ir,127.0.0.1,rasadyaar.ir,rasadyar.net,userbackend.rasadyar.com,rasadyar.com +DB_NAME=users +DB_USER=postgres +DB_PASSWORD=ShVaaU0yhBNET7lpUFT2aMDGdhOQLSzYxsOLnJRePGL1rQLWVv2iyIwRex3o7uoz +DB_HOST=31.7.78.133 +DB_PORT=14362 +CELERY_BROKER_URL=redis://redis://localhost:6379 +CELERY_RESULT_BACKEND=redis://redis://localhost:6379 +CELERY_ACCEPT_CONTENT=application/json +CELERY_TASK_SERIALIZER=json +CELERY_RESULT_SERIALIZER=json +CELERY_TIMEZONE=Asia/Tehran +CORS_ORIGIN_ALLOW_ALL=True +CORS_ORIGIN_WHITELIST=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com +CORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com +SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https +SECURE_SSL_REDIRECT=False +SESSION_COOKIE_SECURE=True +CSRF_COOKIE_SECURE=True +REDIS_URL=redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0 + + +ENV RUNNING_IN_DOCKER=0 \ No newline at end of file diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..d37988a --- /dev/null +++ b/.env.prod @@ -0,0 +1,24 @@ +SECRET_KEY=django-insecure-&-inwstj0rt25wjb=#t8jq&96vb=h4y@udy&$^gj)ih-da3r4j +DEBUG=True +ALLOWED_HOSTS=testbackend.rasadyaar.ir,test.rasadyaar.ir,127.0.0.1,rasadyaar.ir,rasadyar.net,userbackend.rasadyar.com,rasadyar.com +DB_NAME=users +DB_USER=postgres +DB_PASSWORD=ShVaaU0yhBNET7lpUFT2aMDGdhOQLSzYxsOLnJRePGL1rQLWVv2iyIwRex3o7uoz +DB_HOST=31.7.78.133 +DB_PORT=14362 +CELERY_BROKER_URL=redis://redis://localhost:6379 +CELERY_RESULT_BACKEND=redis://redis://localhost:6379 +CELERY_ACCEPT_CONTENT=application/json +CELERY_TASK_SERIALIZER=json +CELERY_RESULT_SERIALIZER=json +CELERY_TIMEZONE=Asia/Tehran +CORS_ORIGIN_ALLOW_ALL=True +CORS_ORIGIN_WHITELIST=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com +CORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com +SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https,http +SECURE_SSL_REDIRECT=False +SESSION_COOKIE_SECURE=True +CSRF_COOKIE_SECURE=True +REDIS_URL=redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0 + +ENV RUNNING_IN_DOCKER=0 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfa6a22 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +# Created by .ignore support plugin (hsz.mobi) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/ArtaSystem.iml b/.idea/ArtaSystem.iml new file mode 100644 index 0000000..936bdcb --- /dev/null +++ b/.idea/ArtaSystem.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..8059b2c --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://31.7.78.133:14362/users + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ff59c29 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,79 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..62a2f26 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4a68777 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..6df4889 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ArtaSystem/__init__.py b/ArtaSystem/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtaSystem/__pycache__/__init__.cpython-39.pyc b/ArtaSystem/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d5ec53f7fcc14aa797bd157e8796068032201c8 GIT binary patch literal 150 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 Mg!ZE@eL|nof6w->T>t<8 literal 0 HcmV?d00001 diff --git a/ArtaSystem/asgi.py b/ArtaSystem/asgi.py new file mode 100644 index 0000000..6e34487 --- /dev/null +++ b/ArtaSystem/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ArtaSystem project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ArtaSystem.settings') + +application = get_asgi_application() diff --git a/ArtaSystem/settings.py b/ArtaSystem/settings.py new file mode 100644 index 0000000..296d0fe --- /dev/null +++ b/ArtaSystem/settings.py @@ -0,0 +1,264 @@ +""" +Django settings for ArtaSystem project. + +Generated by 'django-admin startproject' using Django 3.2.14. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" +import socket +from pathlib import Path +import os +from dotenv import load_dotenv + +BASE_DIR = Path(__file__).resolve().parent.parent + +loc_ip = socket.gethostbyname(socket.gethostname()) + + +if not os.getenv("RUNNING_IN_DOCKER"): + dotenv_path = BASE_DIR / ".env.local" + load_dotenv(dotenv_path) + +SECRET_KEY = os.environ.get("SECRET_KEY") + +DEBUG = os.environ.get("DEBUG") + +ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(',') +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.admin', + 'django.contrib.sessions', + 'oauth2_provider', + 'rest_framework', + 'django_filters', + 'corsheaders', + 'Authentication.apps.AuthenticationConfig', + 'Notification.apps.NotificationConfig', + 'Core.apps.CoreConfig', + 'Wallet.apps.WalletConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'oauth2_provider.middleware.OAuth2TokenMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'ArtaSystem.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'ArtaSystem.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.environ.get("DB_NAME"), + 'USER': os.environ.get("DB_USER"), + 'PASSWORD': os.environ.get("DB_PASSWORD"), + 'HOST': os.environ.get("DB_HOST"), + 'PORT': os.environ.get("DB_PORT"), + } + +} +# DATABASES['default'] = DATABASES['Auth_db'] +# DATABASE_ROUTERS = ['ArtaSystem.dbrouter.MyDatabaseRouter'] + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.BasicAuthentication', + # 'rest_framework.authentication.SessionAuthentication', + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + 'rest_framework.authentication.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] +} + +OAUTH2_PROVIDER = { + # other OAUTH2 settings + 'REFRESH_TOKEN_EXPIRE_SECONDS': 360000, + 'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.JSONOAuthLibCore', + 'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'}, + 'ACCESS_TOKEN_EXPIRE_SECONDS': 360000 +} + +AUTHENTICATION_BACKENDS = [ + 'oauth2_provider.backends.OAuth2Backend', + # Uncomment following if you want to access the admin + 'django.contrib.auth.backends.ModelBackend', +] + +# CACHES = { +# "default": { +# "BACKEND": "django_redis.cache.RedisCache", +# "LOCATION": "redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0", +# "OPTIONS": { +# "CLIENT_CLASS": "django_redis.client.DefaultClient", +# +# }, +# "KEY_PREFIX": "You have successfully set up a key-value pair!" +# } +# } + + + + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION":"redis://default:wHM2fSW8EXtsoTjHxLZyyaRsD8IJm4tOU108252rizfmUYrp709PuCLUhr9mmYDK@31.7.78.133:14353/0", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + "KEY_PREFIX": "You have successfully set up a key-value pair!" + } +} + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +# TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Tehran' + +USE_I18N = True + +USE_L10N = False + +USE_TZ = False + +DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' + +# Cache time to live is 2 minutes. +CACHE_TTL = 60 * 2 +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'static' + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DATA_UPLOAD_MAX_MEMORY_SIZE = 50242880 + +# CELERY STUFF +BROKER_URL = 'redis://localhost:6379' +CELERY_RESULT_BACKEND = 'redis://localhost:6379' +CELERY_ACCEPT_CONTENT = ['application/json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'Asia/Tehran' + +if DEBUG: + MIDDLEWARE += [ + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ] + INSTALLED_APPS += [ + 'debug_toolbar', + # 'oauth2_provider', + # 'rest_framework', + # 'corsheaders' + ] + INTERNAL_IPS = [ + # ... + "51.89.107.194", + # "127.0.0.1", + + # ... + ] + + hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) + INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + + DEBUG_TOOLBAR_PANELS = [ + 'debug_toolbar.panels.history.HistoryPanel', + 'debug_toolbar.panels.versions.VersionsPanel', + 'debug_toolbar.panels.timer.TimerPanel', + 'debug_toolbar.panels.settings.SettingsPanel', + 'debug_toolbar.panels.headers.HeadersPanel', + 'debug_toolbar.panels.request.RequestPanel', + 'debug_toolbar.panels.sql.SQLPanel', + 'debug_toolbar.panels.staticfiles.StaticFilesPanel', + 'debug_toolbar.panels.templates.TemplatesPanel', + 'debug_toolbar.panels.cache.CachePanel', + 'debug_toolbar.panels.signals.SignalsPanel', + 'debug_toolbar.panels.logging.LoggingPanel', + 'debug_toolbar.panels.redirects.RedirectsPanel', + 'debug_toolbar.panels.profiling.ProfilingPanel', + ] + + # this is the main reason for not showing up the toolbar + import mimetypes + + mimetypes.add_type("application/javascript", ".js", True) + + DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': False, + } + + +CORS_ORIGIN_ALLOW_ALL = os.environ.get("CORS_ORIGIN_ALLOW_ALL", "False").lower() == "true" +CORS_ORIGIN_WHITELIST = os.environ.get("CORS_ORIGIN_WHITELIST").split(',') + +CORS_ALLOWED_ORIGINS = os.environ.get("CORS_ORIGIN_WHITELIST").split(',') + +SECURE_PROXY_SSL_HEADER = os.environ.get("SECURE_PROXY_SSL_HEADER").split(',') +SECURE_SSL_REDIRECT = os.environ.get("SECURE_SSL_REDIRECT") +SESSION_COOKIE_SECURE = os.environ.get("SESSION_COOKIE_SECURE") +CSRF_COOKIE_SECURE = os.environ.get("CSRF_COOKIE_SECURE") diff --git a/ArtaSystem/urls.py b/ArtaSystem/urls.py new file mode 100644 index 0000000..cd668ef --- /dev/null +++ b/ArtaSystem/urls.py @@ -0,0 +1,23 @@ +"""ArtaSystem URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('Authentication.urls')), + path('', include('Notification.urls')), +] diff --git a/ArtaSystem/wsgi.py b/ArtaSystem/wsgi.py new file mode 100644 index 0000000..82cfc4a --- /dev/null +++ b/ArtaSystem/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ArtaSystem project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ArtaSystem.settings') + +application = get_wsgi_application() diff --git a/Authentication/__init__.py b/Authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication/__pycache__/__init__.cpython-39.pyc b/Authentication/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24ac63a3a0e34575b3779658ab81f1af7c5b2d22 GIT binary patch literal 154 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6w>*(#g`k0;a1{nNC3ZF^Gc(44TX@fuanW zjJH@5Q*tx&{4|-O_)@YG^V0M6lJoOQiZYXmKnAR2C}IXuVB(jJvsFw{VsTfX>n?iZc-wUotF~hSXz>inpcvUoLG{XpBDo*Uaz3?7Kcr4eoARh NsvRTHz|TO;007K>F!%rf literal 0 HcmV?d00001 diff --git a/Authentication/__pycache__/apps.cpython-39.pyc b/Authentication/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40e82ec1b18d521ce6601de314db56aae984eaaa GIT binary patch literal 447 zcmY*Wy-ve05Vqq~YD>$2Iw2vkW*`p$2(?s#tqWTe$#Q#cNJ-*gCqr4`S&(?3E?${< z1t#v=O0*~4`F_6p&OWQ*u+LC}&3XQb=Qjn15@Ik$b(d&>0WVm|6VBNqg8;+_1EKl5 zNCF6-Sk(DJ6pOg;P3n4Ts%4%nD03(rgE6Wbq0ubiz_6Pq0)iw2fh?gPP1@#JS53Z9 zO>U|k)7u5CRF#<+=5c927cRcfGenw)To;g>9yiURj_*oYsZvYn`%+?0TaZ7H^0igP zzH$b7soJ8E3Yp}RsC-DZFJ^mq#jW|hnO>(>IR$HFWo^w$f?2$1o3>8dtGPnIg7o+X ysj6!i*J~?~h8F3_ba}|PnOP+K$^J=%v;j>c?El=IM_=T-x4Q@2(E=S|2jUNnqjDPn literal 0 HcmV?d00001 diff --git a/Authentication/__pycache__/models.cpython-39.pyc b/Authentication/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfc43dd211c49b4a72b7083a78ccffe768c8ab9c GIT binary patch literal 1656 zcmbVML5~|X6t-t3lbK|)o0g@8MFMd_Q*$wu#CKkLJz$?C0e9d*8Dd40;4c@W)}1#)SNd zV0%OWzJ%9)4@MG6HJMXJDf)p9<{=BcAL?k{VV!x*VoDwn8OhE|B0DO45wb4eSatz- zJx*kDOh)M%evyo*i|3V8+JcvUC9HakXcV~U2dj+hJTledD#y_6fob8e=U^0JR1y}b z@FJ2_1~-(!AK&`1_d`Dq3Ea*n-iJhGd=s#A2lZZ&Gb+0>d6}^OD-scwVal#Q(S<7O zlj>5H>rdgoA?w~;{K&N`XZ88|7noZA4(4PPm;}BqDi>Pk6x_`S!NL6Z;kAcgYEsi{ z@*=nhu4zH8gJ0>-p5SA$qEAMl>)6GWG6u>6KWD`=wF=>@^qGrs4^EA+LN0~jSEjn)vdS-&Ib8s;pG*b#r93@a)aP)bA{TX0m8VTD##c*& z=?yW(01Tl&4Y!Uy#wp}17Pjfj0j z250~hqcVYry&bggQQEEzchH?3ba!XpTRZ5$i?}EIAmX>Zi0^@jGnZj!eJio*_40%b zP!GIlG3YI=N)A1!YGpDwrOS-cqE^zEjO}eA`3zfgM9qf~#P-{2Y*yD2W=$W=8tMMO z9bg8qc@!F~kNE%%>e8SvjW46Gs;?z3d<^eKuy?kP*(mnDc?UPXiv~Nd5ohmVWmGXM zzf((tjp@3RqvOfZ*Wb7Zsd4*{MY%kw(0U1*4hBW0(yD37xH-Fd59fPlg|T%jZ)nEG zWpK+)3$0kc^o=GoFwO?IActFzML86NCD5zSiZWNtQQyZr|4X~b22x-@0+Y}K8qt6T z|HKh~z5jO{gOXoAfoJdu++(S{5ap~I%c-Y;^(R2VxM_Za7nD0}Bl4=OO)(t{xFkMxz;*!srJK+nw{w($xHrTK;%>D%w1dzS} literal 0 HcmV?d00001 diff --git a/Authentication/__pycache__/serializers.cpython-39.pyc b/Authentication/__pycache__/serializers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f25dc3cc5f40c793e2a7c0b47c11325931000fae GIT binary patch literal 1482 zcmbtUy>8nu5GEz+$3OXL>a>8nbm-!tet@DV+6G9_Azsohf&qdSnMAQHC6t83S<|%- zk_YO-wNqcAQ}3uaQS2BfPyssJ9mV^|@4l1R*k}`YoS*%q=MwS@gXY5^_y}(?0V9c| zDVfoTQuLkFnYoc`eK%z@FY;!7+#cGQnM@1({H`Ak(_b zv}OB<3_8C7hYYCc6iO!|O|F$LAoLD&UY!pd)7eWCm6c!fsme_E8+`ReLY$QI7R=AQP(&5Kc&BeaE>!`vy_!VGVsCix8>v~IKilMl!PUISJ=5E>Qe7?+TLjthK&ONTjtP~luS zrCYe?_yUzAMrDn;jKsLCYiHMGwSpA&qQ>gX`&27EO$u+fc8oDh0-gQ!d;@A%*Yjz0 zIX?;Jr}LBQE5OsB`d(e#oK;tWnN?qc`47MlVP-dHL3If+*Pu>8398RF!357Tu$=k#MEx2M+M?!J0@lPj#y| z$@t+Dnq{LnqB<67`=B)*k6SxJCaqkKcp3@DnQ`B}>Ah{b#v5|VxKxHPs;Q~(a~bbU znswtUElu5~QEhz5_j0bZGTt3BwM@pTHbnCS-Slll#50)Q5DCj0?`}AQbK^o^<7IR* z5|l|7e!+&TOr(m>kbDS+Kb^HsUs6TcgvvoC#qNNK=p&bX`#7y_3OHr`)m#r@%e&S3 z_KCBJSybQ~2%Qb=ZUoBR9w5R#UcdyCzr^(&f_-Pnk&nRJbfmqxTYTI7p0Jl|{1=H} O`G~*7wwAqB-~9)cMfmyv literal 0 HcmV?d00001 diff --git a/Authentication/__pycache__/urls.cpython-39.pyc b/Authentication/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..309daf1354f92b497a46f4c3d24a84da4130fd3d GIT binary patch literal 2093 zcmZuxOK;mo5GE<=LF@hUD~aR0qoy+3G;NAD2;$UjQJ`>vr0!v1K%liFn>HW9F6Gp` z`fQ*-AvyLh?X{<(w;o&cF*~9hTZsrQzxigkGduh3de!QJfxkc2`~D+DziH#*uZ6}l z{L6}E7~J4yV1y3frt5c33CuvTnG@7Q-dB6fU!6(*XPR${T~N!s=m}7ofolym*#F z-sB~44zIkv`o`c6UpUJlQ%2?*F_ny|LYdbxT!#*CWVi_x-pX(rs=SloF4XyAhL@np zmovNqHNKkRE70OsGkgu&d@aM*VUe$A_y#QTn;E_Z%lx*+`!%)(g~RK=m_orC)_Vu* zU8j2QX0i8>xk1dV>ifvtB&L_ee1Oa?Vjg77hsfL}rcZhDj}6SEGi;#e4td-x_9JBO z67w;|S`=&YhhSlk@12^b7Jl~$T5J5#$l{;!&(1939z*xk+Rw53+?H@P=tsj~!l7rXqKGF62#K&HAxZpbUvBqmsw80Fm*`RXAl~;QRUaOB(LM~uo|La* z!Id*SfZ@>~PR9Dnh!bk>ND$v~h!Q_J`N@Y@FCkGinZyDH=RwXlK8EW^Rhq@B$}Hi) z3j)>3vQ02(5XO6c0IIz`3HLz!Fxng@{xRt9rD_WZ<6{_j!y!mHNa7=iRC8JyeGEV2 zGNh`K;b)cU)$Vy%kQYL_;7yW)SokTp%sIyxVw1Z-bGj-|@6?&q>52mOG_B5TUAHUh znk!JtTv60UW3F+oC~4Z6wZj$qRHu1uyUNzDbm6?Wu5idZ!j_>|zfxzm3|EzAFs*vj zmA+o5bkoX3Gwr;mORmuOMT(u)+jyZ@u4rpV)f9xU{I;B24Bolq$Rne0JT@Q19)Q3@X=c_<%k7GYdC;(Tn_4V_eZ&`iwt-bQYby5=aYrc2Zg$%rk_>I6 zhIpV`qKCh}{OXHc;Yp95ctT6%aKPi?5r|!Vmil`h;)w5F-jiKD<;M6#m7at#p9JvK z#Hr*q4h^%SAM4pd$!z3WW@~D{JN{|6N|uHG?D@OkXscpc|JnZ*iZ!!imj3x4GLVpm literal 0 HcmV?d00001 diff --git a/Authentication/__pycache__/views.cpython-39.pyc b/Authentication/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..683ae9549e95ca5f84561b913e5f827934f956dc GIT binary patch literal 12008 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 L4ht9%P_+DSFL@Ts literal 0 HcmV?d00001 diff --git a/Authentication/admin.py b/Authentication/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Authentication/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Authentication/apps.py b/Authentication/apps.py new file mode 100644 index 0000000..dcc48d9 --- /dev/null +++ b/Authentication/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Authentication' diff --git a/Authentication/migrations/0001_initial.py b/Authentication/migrations/0001_initial.py new file mode 100644 index 0000000..5512586 --- /dev/null +++ b/Authentication/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.13 on 2023-09-17 15:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='ClientToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('client_name', models.CharField(max_length=50)), + ('client_id', models.CharField(max_length=50)), + ('client_secret', models.CharField(max_length=150)), + ('client_token', models.CharField(max_length=50)), + ('client_web_address', models.CharField(max_length=200, null=True)), + ('client_web_address_backend', models.CharField(max_length=200, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='clienttoken_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='clienttoken_modifiedby', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='UserIdentity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('first_name', models.CharField(max_length=100, null=True)), + ('last_name', models.CharField(max_length=100, null=True)), + ('mobile', models.CharField(max_length=20, null=True)), + ('national_id', models.CharField(max_length=20, null=True)), + ('national_code', models.CharField(max_length=20, null=True)), + ('city', models.CharField(max_length=100, null=True)), + ('province', models.CharField(max_length=100, null=True)), + ('client', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='client_identity', to='Authentication.clienttoken')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='useridentity_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='useridentity_modifiedby', to=settings.AUTH_USER_MODEL)), + ('role', models.ManyToManyField(related_name='identity_group', to='auth.Group')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_identity', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/Authentication/migrations/__init__.py b/Authentication/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc b/Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0391b8e79b72c118f2d1aa7d71c2573e080e6e9c GIT binary patch literal 2267 zcmcIlOLH4V5ZT1l4d$ad_6U>{vBRVbiy38IYTPww8UY5sTPAXvC&w+xgzC zImA5ByvzXnXr3;dh{iDsT9Ic6x5HW-$1DsYk?Dkl`q6$v+KoTU03N|F`p(cas?~I& z)eNH7OltjP(@Z=2vrdebQFEx(&JmMX)NSX94zEozBzs{s4dQ^TK#T1ekV#EWNnMbZ z0Oc=quqmXdsL%|cGFX=sDg&A&vt&-8YZIuV(0r;}n9waMbRA+WT^JB!c?#bEJSQ`v z*V>h~+n#SPkehN%~CG6e|e;D70C**nND8005)Ltul_Hqe@$im`fwGur|=zx z?+*3%rm#1K-J<`jMlFt9Sl$$e5KZ>ySJ#n|eIkt-Q{Fs5Gv-;3lO@{qi%D)Qb7 zvEEnKDq3rH#4mK%b@Bmp@`oa~ciFaw(8Lz*#V6wS>gt0vg#DOu)QezfG9JY!z#WP@ z_*>-Dpao|6O`p*qJ`7K3APh!!gt5EjS)u?=Le$_C$CQYCm$MG$eRN9uA_v|W_SrcV zg=du4!-y)k!UQc$%@W$gJwFx>B`n5up9(AJ`M${X0`{~=MLuzh2>6B#n1l+#GlU6* zf^El|{Y#~z@&KiRWd*DOmtpX3$Kmv~3v32EAJtLx+enu4H#)BW_^g=3& z)w<_WbVlnk2Tr4C@T>G`@Eh>Q=j4>-7mZLIH-I-H+>|I(?~D1wPIk4VSwOtp)qjCsV_-RI5grgrGSA5+%1f|Q`~?8>(CXFeTNq&y#}6A=4Ik~ahmI1Yu?kgl9A z51+B10Wp*8L@6bQrP0op*P#hz>D!WS3sf9}y=OC*)%c zSUiN*ni)=M@vzrDcV+iiAIonj-^jfs-H4(bVO6jl(hORBfL&frPBA-xEZG=HHsZ%bMkh&^qQj4XbunK(uSAX@AHvR?V%Ad$VraxjQ`T81U V!udF9o=;?tL=FrENXF3}^FNh`sXqV! literal 0 HcmV?d00001 diff --git a/Authentication/migrations/__pycache__/__init__.cpython-39.pyc b/Authentication/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..027790add94b8ca07049189972b409c1dcc7c276 GIT binary patch literal 165 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6wu*(#$6Do+3a literal 0 HcmV?d00001 diff --git a/Authentication/models.py b/Authentication/models.py new file mode 100644 index 0000000..47f4348 --- /dev/null +++ b/Authentication/models.py @@ -0,0 +1,41 @@ +from django.db import models +from Core.models import BaseModel +from django.contrib.auth.models import User, Group + + +# Create your models here. + +class ClientToken(BaseModel): + client_name = models.CharField(max_length=50) + client_id = models.CharField(max_length=50) + client_secret = models.CharField(max_length=150) + client_token = models.CharField(max_length=50) + client_web_address = models.CharField(max_length=200, null=True) + client_web_address_backend = models.CharField(max_length=200, null=True) + + def save(self, *args, **kwargs): + super(ClientToken, self).save(*args, **kwargs) + + +class UserIdentity(BaseModel): + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='user_identity') + role = models.ManyToManyField( + Group, + related_name='identity_group' + ) + client = models.ForeignKey( + ClientToken, + on_delete=models.CASCADE, + null=True, + related_name="client_identity" + ) + first_name = models.CharField(max_length=100, null=True) + last_name = models.CharField(max_length=100, null=True) + mobile = models.CharField(max_length=20, null=True) + national_id = models.CharField(max_length=20, null=True) + national_code = models.CharField(max_length=20, null=True) + city = models.CharField(max_length=100, null=True) + province = models.CharField(max_length=100, null=True) + + def save(self, *args, **kwargs): + super(UserIdentity, self).save(*args, **kwargs) diff --git a/Authentication/serializers.py b/Authentication/serializers.py new file mode 100644 index 0000000..afc89c9 --- /dev/null +++ b/Authentication/serializers.py @@ -0,0 +1,31 @@ +from rest_framework import serializers +from django.contrib.auth.models import Group +from Authentication.models import ClientToken, UserIdentity + + +class GroupSerializer(serializers.ModelSerializer): + class Meta: + model = Group + fields = '__all__' + + +class ClientTokenSerializer(serializers.ModelSerializer): + class Meta: + model = ClientToken + fields = ( + 'client_name', + ) + + +class UserIdentitySerializer(serializers.ModelSerializer): + client = ClientTokenSerializer(required=False) + + class Meta: + model = UserIdentity + exclude = ( + 'id', + 'created_by', + 'modified_by', + 'trash' + ) + extra_kwargs = {'role': {'required': False}, } diff --git a/Authentication/sms.py b/Authentication/sms.py new file mode 100644 index 0000000..6b03801 --- /dev/null +++ b/Authentication/sms.py @@ -0,0 +1,17 @@ +import requests + + +def send_otp_code(receptor, rand): + receptor = str(receptor) + message = 'سلام همراه عزیز کد پیامکی ارسالی برای شما :{}'.format(rand) + u = "http://webservice.sahandsms.com/newsmswebservice.asmx/SendPostUrl?username=pmstores&password=Aht00100&from=30002501&to={}&message={}".format( + receptor, message) + + url = u.format() + + payload = {} + headers = {"Content-Type": "application/x-www-form-urlencoded"} + + response = requests.request("GET", url, headers=headers, data=payload, verify=False) + + print(response.text) diff --git a/Authentication/tests.py b/Authentication/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Authentication/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Authentication/urls.py b/Authentication/urls.py new file mode 100644 index 0000000..629ec83 --- /dev/null +++ b/Authentication/urls.py @@ -0,0 +1,60 @@ +from django.urls import path, include +from rest_framework import routers +from django.conf import settings +import oauth2_provider.views as oauth2_views + +from Authentication.views import ( + register, + login, + change_password, + check_otp, + send_otp, + UserIdentityViewSet, store_send_otp, Find_User, Identity, register_all, change_user_mobile, NumberOfActiveUsers, + remove_access_token,check_user_exists, remove_user_role +) + +router = routers.DefaultRouter() +router.register('user_identity', UserIdentityViewSet, basename='user_identity') + +oauth2_endpoint_views = [ + path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"), + path('token/', oauth2_views.TokenView.as_view(), name="token"), + path('register/', register, name="register"), + path('register_all/', register_all, name="register_all"), + path('login/', login, name="login"), + path('change_password/', change_password, name="change_password"), + path('send_otp/', send_otp, name="send_otp"), + path('send/', store_send_otp, name="send"), + path('check_otp/', check_otp, name="check_otp"), + path('find/', Find_User, name="find"), + path('identity/', Identity, name="identity"), + path('active-users/', NumberOfActiveUsers, name="active-users"), + path('remove_access_token/', remove_access_token, name="remove_access_token"), + path('check_user_exists/', check_user_exists, name="check_user_exists"), + path('remove_user_role/', remove_user_role, name="remove_user_role"), + +] + +if settings.DEBUG: + # OAuth2 Application Management endpoints + oauth2_endpoint_views += [ + path('applications/', oauth2_views.ApplicationList.as_view(), name="list"), + path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"), + path('applications//', oauth2_views.ApplicationDetail.as_view(), name="detail"), + path('applications//delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"), + path('applications//update/', oauth2_views.ApplicationUpdate.as_view(), name="update"), + ] + + # OAuth2 Token Management endpoints + oauth2_endpoint_views += [ + path('authorized-tokens/', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"), + path('authorized-tokens//delete/', oauth2_views.AuthorizedTokenDeleteView.as_view(), + name="authorized-token-delete"), + ] + +urlpatterns = [ + path('', include(router.urls)), + path('api/', include((oauth2_endpoint_views, 'oauth2_provider.urls'), namespace="oauth2_provider")), + path('change_mobile_number/', change_user_mobile), + +] diff --git a/Authentication/views.py b/Authentication/views.py new file mode 100644 index 0000000..b716cb3 --- /dev/null +++ b/Authentication/views.py @@ -0,0 +1,635 @@ +import cryptocode +from django.core.cache import cache +from rest_framework.decorators import permission_classes, api_view +from .models import UserIdentity +from rest_framework.permissions import AllowAny +from django.contrib.auth.models import User, Group +from rest_framework.response import Response +from django.http import HttpResponse +from django.shortcuts import render +from rest_framework import status, viewsets +from oauth2_provider.models import AccessToken +import json +import requests +import random +import uuid +import cryptocode +from oauth2_provider.contrib.rest_framework import ( + TokenHasReadWriteScope, + OAuth2Authentication, ) +from rest_framework.decorators import authentication_classes +from Authentication.models import ClientToken +from Authentication.sms import send_otp_code +from .serializers import UserIdentitySerializer +from datetime import timedelta + +BASE_URL = "https://userbackend.rasadyar.com/api/" + +ARTA_CLIENT_ID = 'cpxlBf9GPPnk0nfOMLEa6fZyUrew6Z17wujOUMJr' +ARTA_CLIENT_SECRET = 'ONFoHxBCPOtIUw72QnLL4oa0wOKQNQ6h3Hc8pZrk3qHcR759hmgFn7fJZJMh1nQRWMeRGUHbRoTBFCIQn7OsiKrY7y4JM975T7mjM7WXJs3Ezl30gMAUgfpuEpzJgChz' + +CHICKEN_CLIENT_SECRET = '4EK8EAPBOGsUHTeTHpgXrjQwbOQKAnNnQIOHmZa3IlOYVafwV1rmoKHhJE91OmLJ201yp7UkGu5TikiesoZxhNj0FYOyTtC7YtcqvopdBO36e2PSnjuqkLt0yCmaK2ph' +CHICKEN_CLIENT_ID = 'DhL3VMce6p3CBPSTwBg1AJjcaREvddWoOP8G8pHc' + +LO_CHICKEN_CLIENT_SECRET = "xqZM6iTDe0XDS1mC8iVhahXqb2TWIZ07mx7yYOZrzTYHyHoFYIpvBm6IcM169fsGZ8uQs3gBHmicgbUMVXwbHyJIaCOeFp9SNK72E4v2OR51om3eH43VMQSK4pEKmMX6" +LO_CHICKEN_CLIENT_ID = "kSHxeTGASY8JsczTinnt5t820clWOKC3X1NHnMOi" + +HA_CHICKEN_CLIENT_SECRET = 'l2Gt9AgwOfIneoQU2hamnGYCOiIUdAY2nmLI9eCkNo7wXU6TvNEU93oHtk8IzSHzJc5vVkm9scJaAlWGbzumNenGsQbIESbA1mAsLXWoWSllZKCuGyCBTJtKQ7BhnHZ6' +HA_CHICKEN_CLIENT_ID = 'WwpP780hSemYh8K93MqeuZ3HAir3ahQxDTGG43nG' + +DM_CLIENT_ID = '2fDx0CopuiLnRz7YyCQD8nBXKjpxzqZg38Fcl02l' +DM_CLIENT_SECRET = 'PKStjauydu4k157bSaoPVenKHvLVtLI9Upn4JxU7tnHhuHPfAUp1abkfWp55orh7dFCXdE09E5CeWu7vBJsv1VpXz13EBl7OSW2LAceo3ztvq4FNAEVmEEt56cEmQzpF' + +INSPECTION_CLIENT_ID = 'R2Ox6eqrXPeh1KbeWLDO5MCapuOFpHDvstOOD1XC' +INSPECTION_CLIENT_SECRET = 'imFgEGkcs248XZkLE7JNMo6mwVkiUMGYUBenBAlgZFwW0lyCYILrmh5Akh8dpHbgpCYaSvuYepFu3WdUXY3ZXPDZq11KbqlrmjHwf8wuW2DUsa0oSDozDv4p9Lx3lJPO' + + +# # Create your views here. +# @api_view(["POST"]) +# @permission_classes([AllowAny]) +# def GernalSendOtp(request): +# mobile = request.data["mobile"] +# state = request.data["state"] +# try: +# user = User.objects.get(username__exact=mobile) +# user_identity = UserIdentity.objects.get(user) +# client = ClientToken.objects.get(key=user_identity.client.key) +# except User.DoesNotExist: +# return Response({'is_user': False}, status=status.HTTP_401_UNAUTHORIZED) +# if len(mobile) < 11 or len(mobile) > 11: +# return Response( +# { +# "pattern": "wrong", +# }, +# status=status.HTTP_403_FORBIDDEN, +# ) +# key = str(uuid.uuid4()) +# rand = random.randint(10000, 99000) +# cache.set(key, str(rand), timeout=120) +# if not User.objects.filter(username=mobile).exists(): +# receptor = mobile +# send_otp_code(receptor, rand) +# return Response( +# { +# "is_user": False, +# "key": key, +# }, +# status=status.HTTP_404_NOT_FOUND, +# ) +# +# if state == "forget_password": +# receptor = mobile +# send_otp_code(receptor, rand) +# return Response( +# { +# "is_user": True, +# "key": key, +# }, +# status=status.HTTP_200_OK, +# ) +# +# elif state == "change_password": +# receptor = mobile +# send_otp_code(receptor, rand) +# return Response( +# { +# "is_user": True, +# "key": key, +# }, +# status=status.HTTP_200_OK, +# ) +# +# elif state == "": +# return Response( +# { +# "is_user": True, +# }, +# status=status.HTTP_200_OK, +# ) + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def send_otp(request): + # frontend_url = request.headers.get("Origin") + # frontend_url = request.data.get("frontend_url", frontend_url) + # if "https://rasadyaar.ir" in frontend_url: + # return Response({'result': 'https://rasadyar.net'}, status.HTTP_401_UNAUTHORIZED) + mobile = request.data["mobile"] + state = request.data["state"] + try: + user = User.objects.get(username__exact=mobile) + user_identity = UserIdentity.objects.get(user=user) + except User.DoesNotExist: + return Response({'is_user': False}, status=status.HTTP_404_NOT_FOUND) + if len(mobile) < 11 or len(mobile) > 11: + return Response( + { + "pattern": "wrong", + }, + status=status.HTTP_403_FORBIDDEN, + ) + key = str(uuid.uuid4()) + rand = random.randint(10000, 99000) + cache.set(key, str(rand), timeout=120) + if not User.objects.filter(username=mobile).exists(): + receptor = mobile + # send_otp_code(receptor, rand) + return Response( + { + "is_user": False, + "key": key, + }, + status=status.HTTP_404_NOT_FOUND, + ) + + if state == "forget_password": + receptor = mobile + send_otp_code(receptor, rand) + return Response( + { + "is_user": True, + "key": key, + "address": user_identity.client.client_web_address, + "backend": user_identity.client.client_web_address_backend, + "api_key": user_identity.client.client_token, + + }, + status=status.HTTP_200_OK, + ) + + elif state == "change_password": + receptor = mobile + send_otp_code(receptor, rand) + return Response( + { + "is_user": True, + "key": key, + "address": user_identity.client.client_web_address, + "backend": user_identity.client.client_web_address_backend, + "api_key": user_identity.client.client_token, + }, + status=status.HTTP_200_OK, + ) + + elif state == "": + return Response( + { + "is_user": True, + "address": user_identity.client.client_web_address, + "backend": user_identity.client.client_web_address_backend, + "api_key": user_identity.client.client_token, + + }, + status=status.HTTP_200_OK, + ) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def store_send_otp(request): + mobile = request.data["mobile"] + key = str(uuid.uuid4()) + rand = random.randint(10000, 99000) + cache.set(key, str(rand), timeout=120) + receptor = mobile + send_otp_code(receptor, rand) + + return Response( + { + "key": key, + }, + status=status.HTTP_200_OK, + ) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def change_user_mobile(request): + first_mobile = request.data["first_mobile_number"] + second_mobile = request.data["second_mobile_number"] + user = User.objects.get(username=first_mobile) + user.username = second_mobile + user.save() + # user_identity=UserIdentity.objects.get(mobile=first_mobile) + # user_identity.mobile=second_mobile + # user_identity.save() + + return Response({"result": "number changed"}, status=status.HTTP_200_OK) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def check_otp(request): + key = request.data["key"] + code = cache.get(key) + if request.data["code"] == code: + return Response( + { + "code": True, + }, + status=status.HTTP_200_OK, + ) + else: + return Response( + { + "code": False, + }, + status=status.HTTP_403_FORBIDDEN, + ) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +# @permission_classes([TokenHasReadWriteScope]) +@authentication_classes([OAuth2Authentication]) +def change_password(request): + username = request.data["username"] + password = request.data["password"] + user = User.objects.get(username=username) + user.password = cryptocode.encrypt(password, password) + user.save() + + return Response({"password": "changed"}, status=status.HTTP_200_OK) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def register(request): + # if 'role' in request.data.keys() and 'tenant' in request.data.keys(): + # request.data.pop('role') + # request.data.pop('tenant') + + username = request.data["username"] + password = request.data["password"] + api_key = request.data["api_key"] + client = ClientToken.objects.get(client_token=api_key) + if User.objects.filter(username__exact=username).exists(): + return Response({"result": "user exist"}, status=status.HTTP_400_BAD_REQUEST) + + if 'first_name' in request.data.keys() and 'last_name' in request.data.keys(): + user = User( + username=username, password=cryptocode.encrypt(password, password), first_name=request.data['first_name'], + last_name=request.data['last_name'] + ) + else: + + user = User( + username=username, password=cryptocode.encrypt(password, password) + ) + user.save() + # if 'role' in request.data.keys(): + # group = Group.objects.get(name__exact=request.data['role']) + if not UserIdentity.objects.filter(user=user): + user_identity = UserIdentity( + user=user, + client=client + ) + user_identity.save() + if 'national_code' in request.data.keys(): + user_identity.national_id = request.data['national_code'] + if 'first_name' in request.data.keys() and 'last_name' in request.data.keys(): + user_identity.first_name = request.data['first_name'] + user_identity.last_name = request.data['last_name'] + user_identity.mobile = request.data['username'] + user_identity.save() + + # user_identity.role.add(group) + data = { + "username": str(user.username), + "password": user.password, + "client_id": client.client_id, + "client_secret": client.client_secret, + "grant_type": "client_credentials", + # "scope": "read" + "scope": "read write", + } + r = requests.post(url=BASE_URL + "token/", data=json.dumps(data), verify=False) + access = AccessToken.objects.get(token=r.json()["access_token"]) + access.user = user + access.save() + dict_info = { + "access_token": r.json()["access_token"], + "expires_in": r.json()["expires_in"], + "token_type": r.json()["token_type"], + "scope": r.json()["scope"], + "expire_time": access.expires, + } + # r.json()["expire_time"]=access.expires + return Response(dict_info, status=status.HTTP_200_OK) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def register_all(request): + username = request.data["username"] + password = request.data["password"] + api_key = request.data["api_key"] + client = ClientToken.objects.get(client_token=api_key) + if User.objects.filter(username__exact=username).exists(): + pass + + else: + if 'first_name' in request.data.keys() and 'last_name' in request.data.keys(): + user = User( + username=username, password=password, first_name=request.data['first_name'], + last_name=request.data['last_name'] + ) + else: + user = User( + username=username, password=password + ) + user.save() + if not UserIdentity.objects.filter(user=user): + user_identity = UserIdentity( + user=user, + client=client + ) + user_identity.save() + if 'national_code' in request.data.keys(): + user_identity.national_id = request.data['national_code'] + if 'first_name' in request.data.keys() and 'last_name' in request.data.keys(): + user_identity.first_name = request.data['first_name'] + user_identity.last_name = request.data['last_name'] + user_identity.mobile = request.data['username'] + user_identity.save() + + return Response("ok", status=status.HTTP_200_OK) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def login(request): + username = request.data['username'] + password = (request.data['password'],) + api_key = request.data["api_key"] + roles = [] + roles_from_request = [] + client = ClientToken.objects.get(client_token=api_key) + try: + user = User.objects.get(username__exact=username) + except User.DoesNotExist: + return Response({'is_user': False}, status=status.HTTP_401_UNAUTHORIZED) + + if 'role' in request.data.keys(): + if type(request.data['role']) is list: + roles_from_request = request.data['role'] + else: + roles_from_request.append(request.data['role']) + + if 'user_key' in request.data.keys(): + for item in roles_from_request: + group = Group.objects.get(name__exact=item) + if not UserIdentity.objects.filter(user=user, role=group): + if not UserIdentity.objects.filter(user=user).exists(): + user_identity = UserIdentity() + else: + user_identity = UserIdentity.objects.get(user=user) + user_identity.user = user + user_identity.key = request.data['user_key'] + user_identity.client = client + user_identity.save() + user_identity.role.add(group) + else: + user_identity = UserIdentity.objects.get(user=user) + user_identity.key = request.data['user_key'] + user_identity.client = client + user_identity.save() + for item in user_identity.role.all(): + roles.append(item.name) + decrypted_password = cryptocode.decrypt(user.password, password[0]) + if decrypted_password != password[0]: + return Response({'password': 'wrong'}, status=status.HTTP_401_UNAUTHORIZED) + data = { + "username": username, + "password": password, + "client_id": client.client_id, + "client_secret": client.client_secret, + "grant_type": "client_credentials", + "scope": "read write", + } + r = requests.post(url=BASE_URL + "token/", data=json.dumps(data), verify=False) + access = AccessToken.objects.get(token=r.json()["access_token"]) + access.user = user + access.save() + dict_info = { + "access_token": r.json()["access_token"], + "expires_in": r.json()["expires_in"], + "token_type": r.json()["token_type"], + "scope": r.json()["scope"], + "expire_time": access.expires, + "role": roles + } + return Response(dict_info, status=status.HTTP_200_OK) + + +class UserIdentityViewSet(viewsets.ModelViewSet): + queryset = UserIdentity.objects.all() + serializer_class = UserIdentitySerializer + permission_classes = [TokenHasReadWriteScope] + + def list(self, request, *args, **kwargs): + pass + + def retrieve(self, request, *args, **kwargs): + pass + + def create(self, request, *args, **kwargs): + edit_type = request.data['type'] + request.data.pop('type') + + if edit_type == 'check_user': + # return Response({'sss': 'exist'}, status=status.HTTP_201_CREATED) + # if user exists in system + if self.queryset.filter( + mobile=request.data['value'] + ).exists() or self.queryset.filter( + national_id=request.data['value'] + ).exists(): + + if self.queryset.filter( + mobile=request.data['value'] + ).exists(): + # contains user object + user = self.queryset.get( + mobile=request.data['value'], + ) + + if self.queryset.filter( + national_id=request.data['value'] + ).exists(): + # contains user object + user = self.queryset.get( + national_id=request.data['value'], + ) + serializer = self.serializer_class(user) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(status=status.HTTP_404_NOT_FOUND) + + def update(self, request, *args, **kwargs): + + # contains user identity object + user_identity = UserIdentity.objects.get(key=request.data['userprofile_key']) + request.data.pop('userprofile_key') # remove user key from data + + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + identity_obj = serializer.update(validated_data=request.data, instance=user_identity) + serializer = self.serializer_class(identity_obj) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, *args, **kwargs): + pass + + +@api_view(["GET"]) +@permission_classes([AllowAny]) +def Find_User(request): + data = request.GET["data"] + if UserIdentity.objects.filter(mobile=data).exists(): + user = UserIdentity.objects.get(mobile=data) + elif UserIdentity.objects.filter(national_id=data).exists(): + user = UserIdentity.objects.get(national_id=data) + else: + return Response({"result": "user not found"}, status=status.HTTP_401_UNAUTHORIZED) + + return Response({ + "firstname": user.first_name, + "lastname": user.last_name, + "national_id": user.national_id, + "mobile": user.mobile, + "city": user.city, + "province": user.province, + }) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def Identity(request): + user = UserIdentity.objects.get(user__username=request.data["mobile"]) + user.mobile = request.data["mobile"] + user.first_name = request.data["first_name"] + user.last_name = request.data["last_name"] + user.national_id = request.data["national_id"] + user.city = request.data["city"] + user.province = request.data["province"] + user.save() + return Response({"mobile": user.mobile, "first_name": user.first_name, "last_name": user.last_name}) + + +@api_view(["GET"]) +@permission_classes([AllowAny]) +def NumberOfActiveUsers(request): + from datetime import datetime + now=datetime.now().date() + access = AccessToken.objects.filter(expires__date__gte=now) + return Response({"number_of_active_users":len(access)}) + + +@api_view(["GET"]) +@permission_classes([AllowAny]) +def remove_access_token(request): + import datetime + token=request.GET.get('token') + now = datetime.datetime.now() + accesses = AccessToken.objects.filter(created__date__gte=now.date() - timedelta(days=3)) + if token is not None: + accesses=accesses.filter(token=token) + for access in accesses: + access.expires = now - timedelta(days=2) + access.save() + return Response("ok",status=status.HTTP_200_OK) + + +@api_view(["GET"]) +@permission_classes([AllowAny]) +def check_user_exists(request): + mobile = request.GET.get('mobile') + + if not mobile: + return Response( + {"error": "mobile parameter is required"}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + user = User.objects.get(username__exact=mobile) + return Response( + { + "exists": True, + "mobile": mobile, + "user_id": user.id + }, + status=status.HTTP_404_NOT_FOUND + ) + except User.DoesNotExist: + return Response( + { + "exists": False, + "mobile": mobile + }, + status=status.HTTP_200_OK + ) + + +@api_view(["POST"]) +@permission_classes([AllowAny]) +def remove_user_role(request): + mobile = request.data.get('mobile') + role = request.data.get('role') + + if not mobile: + return Response( + {"error": "mobile parameter is required"}, + status=status.HTTP_400_BAD_REQUEST + ) + + if not role: + return Response( + {"error": "role parameter is required"}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + user = User.objects.get(username__exact=mobile) + except User.DoesNotExist: + return Response( + {"error": "user not found"}, + status=status.HTTP_404_NOT_FOUND + ) + + try: + user_identity = UserIdentity.objects.get(user=user) + except UserIdentity.DoesNotExist: + return Response( + {"error": "user identity not found"}, + status=status.HTTP_404_NOT_FOUND + ) + + try: + group = Group.objects.get(name__exact=role) + except Group.DoesNotExist: + return Response( + {"error": "role not found"}, + status=status.HTTP_404_NOT_FOUND + ) + + if user_identity.role.filter(id=group.id).exists(): + user_identity.role.remove(group) + return Response( + { + "result": "role removed successfully", + "mobile": mobile, + "role": role + }, + status=status.HTTP_200_OK + ) + else: + return Response( + { + "error": "user does not have this role", + "mobile": mobile, + "role": role + }, + status=status.HTTP_400_BAD_REQUEST + ) diff --git a/Core/ArvanStorage/__init__.py b/Core/ArvanStorage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc b/Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59ebda9413a5c6d92a2c9f8989b6c7ea6a44a7e3 GIT binary patch literal 157 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6ws*(#ubg`4gh1kXCMf}I`T5y?wx8eo{Y;z98iGQ8eJ_e_ zgnrY5i-o}87PRbZ5DYOKBe(3ai;FRF3G^g3CZ=m*y=KIfiRD^_ZpQY+ah*cPan-G| z3bVdNZjIT@fxgbFtOk99;m4>|e+{>wgBDhm{Rtg$Q0jYe#1nZ}h*ZF^@wt@KM?9OR zNyYf!zUP{#T99cO-7i(Ozp#oxkOJ;(lL>&%pYO2EN#%r z%JC`aPta3%%M!oDNv-U+&=$%Ocxe|7#{7uKF3Ea}51)Zg+0EZTU>JsqA*B zIHE}=Qvna@1nFe)0lTr&8`6Hi%?E>CyC3#@?cp%&wY$CUc9#ye>5vcHTYeDi`oW+d z&|sVKotsDJ%|z^8qQ{v>gMeq5_ngnYh`Dd;DF~q8^1@2XR(70@Mo}_Sb)RNYu$LxbG*agN z!DkOt#ZP70Q;y(SIuij`)w?eOK9y0LC^JgJv_(`s>v?b14gMnd35j(`#vk|gZXODn zQ8uT-n~HSou{3zj#o;XDqU}@A6Lz?l3VwK72kvnhxWgif-gy|Ob7coCfP` z(o5)s-`o@!TxnGce_gv=%9KIlSXi(k8X%OZZIwl*Q;3o<^i+ixlrTk8k4PLTtK2Ns z^fn!yQf%n;^$}cnc5fVI@_m@hE`vbWDq^>WFGDRN_b$u^`i}FWU2vck)cOUKB63D< zEX~|pVNIB2MUs_qik6l%SCt&Wj4iE|t!H!lR9iT?vvQc$s(E!>%`HvF6M_-!sO8qG zeyVrXq@5FHz9QdmF6()H)qvGzUehONQZi&heFZU3Ho7SLZVTa}J_T9qteowvjMtcz z*WoPt1g!tJ^XH2@na+%J&?2vYhir(e@I=>mcyJFs94k!4gi1w@eMN-7s845p90hH` zDFdb%xA*QV^G?d0k?E@EN9(m{0+gM%xwSw literal 0 HcmV?d00001 diff --git a/Core/ArvanStorage/arvan_storage.py b/Core/ArvanStorage/arvan_storage.py new file mode 100644 index 0000000..661b5ec --- /dev/null +++ b/Core/ArvanStorage/arvan_storage.py @@ -0,0 +1,60 @@ +import io + +import boto3 +import logging + +from PIL import Image +from botocore.exceptions import ClientError +from django.http import HttpResponse +import base64 + + +# ARVAN_STORAGE_URL = "https://dmstore.s3.ir-thr-at1.arvanstorage.com/36bba98f-a813-4667-bd60-33aef708bcba.jpg?AWSAccessKeyId=d5739a44-e663-4f43-99f3-13121a62a9e6&Signature=KpBpHBtAS77Y3hHx53g6bmjlGpc%3D&Expires=1651552380" + + +def connect(): + logging.basicConfig(level=logging.INFO) + + try: + s3_resource = boto3.resource( + 's3', + endpoint_url='https://s3.ir-thr-at1.arvanstorage.com', + aws_access_key_id='d5739a44-e663-4f43-99f3-13121a62a9e6', + aws_secret_access_key='bcc8bc64cac2de7711f8c5d3a4b0123f28319f97fb9e0e9b8fbcfd7465678cdb' + ) + except Exception as exc: + logging.info(exc) + return s3_resource + + +def get_bucket_list(): + s3_resource = connect() + li = [] + try: + for bucket in s3_resource.buckets.all(): + logging.info(f'bucket_name: {bucket.name}') + li.append(bucket.name) + except ClientError as exc: + logging.error(exc) + return li[0] + + +def upload_object(image_data, bucket_name, object_name): + resource_connect = connect() + s3_resource = resource_connect + bucket = s3_resource.Bucket(bucket_name) + buffer = io.BytesIO() + imgdata = base64.b64decode(image_data) + img = Image.open(io.BytesIO(imgdata)) + new_img = img.resize((500, 500)) # x, y + new_img.save(buffer, format="PNG") + img_b64 = base64.b64encode(buffer.getvalue()) + with open(object_name, "wb") as fh: + fh.write(base64.standard_b64decode(img_b64)) + # base64.standard_b64decode(image_data) + with open(object_name, "rb") as fh: + bucket.put_object( + ACL='public-read', + Body=fh, + Key=object_name + ) diff --git a/Core/__init__.py b/Core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/__pycache__/__init__.cpython-39.pyc b/Core/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..753053633c2adbcf2a5faf38d075f5ab50dcdfb5 GIT binary patch literal 144 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6w%*(#g`k0;a1{nNC3ZF^Gc(44TX@fuanW zjJH@5Q*tx&{4|-O_)@YG^V0M6lJoOQiZYXmKnAR2C}IXuVB(jNvsFw{VsTfX>n?iZc-wUotF~hoL`h012$Bzpz;=nO>TZlX-=vgBhZ-7K+FID D&;l&C literal 0 HcmV?d00001 diff --git a/Core/__pycache__/apps.cpython-39.pyc b/Core/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82e241618a6566cabc68582a5f596ec01f313684 GIT binary patch literal 417 zcmYjOy-ve05Vm7iYD2pqCL|=*3`Al@2(?6ltqWTe$#Q#6l#<55ZiceL6Yv&15Q|qP zUV(|bwh}$*&iC_ucc-(SOk#!-eVx}I_a|fS#7ZHuZ^$1j_0sF+P&oxt>b}`1 zjeJ#7gCME)$=nFn*_q$#?51$qYuIY1I%ii3?DE;TqW8vKEj0Qq6g0QcUFXwo>jaYN oRG!R`OTNp@6A?}h>l4yNG=s4J)Ak;HLKxoe*S@0@dBTpxA6KenL;wH) literal 0 HcmV?d00001 diff --git a/Core/__pycache__/models.cpython-39.pyc b/Core/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..571f99c0f0ba1da56b69e100f5db407d96051b80 GIT binary patch literal 1213 zcmZuxOK%%D5Z?DgS~*VaB29YesYP{&F3G7V0y}=BMPnDJtqWm*phVhPnY-(dy9nfC z)2n|-{!DJY@S0QpLVD>pE4dEPlHh2*+2K6C9jzFR1|C-M*W>a-=y@Lre7Pck9UOZO zAwB79ukxAilOE`x3Rzf1Eb4Kn<0@fEuSYtqQkGU3%c`8^y)D)Q!o8m8AsfE*WGb^i zJ(($gQM?P;NG2!VIRAuP-q?3Z)kvl7IB?O_D&yjprs=NmAhW7%%lg8OLzhX>s<@W8b8Rb3CVLUoNul;MhMPl*fGOu|S23NX3gp`ZB-{Zz7h$mO+nhe3q~E z82SMEP!$VQ3vHwKPj6y&@7CU1>1H5v*f)XiBkllqAE4)!buK#Hx=hKk6*G;F>P~BybanZrLualKUEA=wdCP^A#KB<{3=UU2@V6PoG; z%EZjJM$Fsyo_UDSz4-ie@5eJEtdL7#_?2lETsHGdWzIUx%afUaUdyw+#;CK^EKRPK zE~3QWQjGyb=9@?OIUaADEsDEM;wuvL)aBFZFZ+k3($Ym;S4tO?eCEPSwREF>PmAr|ML4I4WW%1pWe?oo5G9ls^{;AaVhVe2cY69WpPn52&X0cEKX~QRz1@?&-TebL zSidzsTdrf^EdaRUMbl^{>Q$~7WZ2hz>34jrw6Cf79Ab!LzlX^DZNI>du!H^tenQSf+;=S|(3ayG%Jy`HS-H27wChCUK*c;|KA_h=G&Mt?Sw skwdX|=-N_S^9`BeBbxa>@BcO7vY(I!)9{?8%F^r={wDfBH$fKt1CIDIV*mgE literal 0 HcmV?d00001 diff --git a/Core/admin.py b/Core/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Core/apps.py b/Core/apps.py new file mode 100644 index 0000000..2e490ab --- /dev/null +++ b/Core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Core' diff --git a/Core/migrations/__init__.py b/Core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/migrations/__pycache__/__init__.cpython-39.pyc b/Core/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88e5c90d39c727eae7e2e64ae40a376f1db3324e GIT binary patch literal 155 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6wk*(# /etc/timezone && \ + dpkg-reconfigure -f noninteractive tzdata +# Set working directory +WORKDIR /app + +# ساخت sources.list جدید با mirror ArvanCloud (برای سرعت در ایران) +RUN echo "deb http://mirror.arvancloud.ir/debian bookworm main" > /etc/apt/sources.list \ + && echo "deb-src http://mirror.arvancloud.ir/debian bookworm main" >> /etc/apt/sources.list \ + && echo "deb https://mirror.arvancloud.ir/debian-security bookworm-security main" >> /etc/apt/sources.list \ + && echo "deb-src https://mirror.arvancloud.ir/debian-security bookworm-security main" >> /etc/apt/sources.list \ + && echo "deb http://mirror.arvancloud.ir/debian bookworm-updates main" >> /etc/apt/sources.list \ + && echo "deb-src http://mirror.arvancloud.ir/debian bookworm-updates main" >> /etc/apt/sources.list + +# Update + Install system deps (with apt cache) +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + python3-dev \ + libcairo2 \ + libcairo2-dev \ + libpango-1.0-0 \ + libpangoft2-1.0-0 \ + libpangocairo-1.0-0 \ + libpango1.0-dev \ + libgdk-pixbuf-2.0-0 \ + libffi-dev \ + libjpeg-dev \ + libpng-dev \ + libfreetype6 \ + libharfbuzz0b \ + shared-mime-info \ + fonts-dejavu \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Upgrade pip +RUN pip install --upgrade pip + +# Copy requirements +COPY ./requirements.txt . + +# Install Python dependencies با cache mount (سرعت pip رو بیشتر می‌کنه) +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install --no-cache-dir -r requirements.txt + +# Copy project files +COPY . . + +# Expose Django port +EXPOSE 8000 + +# Run Django development server +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/Notification/__init__.py b/Notification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/__pycache__/__init__.cpython-39.pyc b/Notification/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3e2b48ea77e93c00924bba7d18c917e6663ae3c GIT binary patch literal 152 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6w(*(#g`k0;a1{nNC3ZF^Gc(44TX@fuanW zjJH@5Q*tx&{4|-O_)@YG^V0M6lJoOQiZYXmKnAR2C}IXuVB(jRvsFw{VsTfX>n?iZc-wUotF~hmtT^ZmYJMbl9`_u12$Z*pz;=nO>TZlX-=vg LBha|dK+FIDV_7gA literal 0 HcmV?d00001 diff --git a/Notification/__pycache__/apps.cpython-39.pyc b/Notification/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d835718a315652dd44517a7406d5329c5f5f6bd0 GIT binary patch literal 441 zcmYjOy-ve05Vqq~YD>FROh`zq8ORPosHGBYUAje)EVt)WE@>R>WGE{<2^$Z@;+2V4 zVB)T=L{7T%{e1V`*;eE6kfB6h7v=-cZwk5+V=zVaS7?9%FWHV~oUUwUg!fe)*=?ceSit5kNXqItc*v&HmQ5J(hmN-lnwlRfStH#*s z$n$y$TUBjr3afNyL6<&#Fq>K1*hiyF$VT0kMbZm>DJ!+pQih?F*wL2ckEDEQRe7pR zKo_bl8>x^-7DNZJLMJZm6NrzTM2gktX551B{7vWsG2fMB$1n+ip$ODe4k z-x44v_ZRfoi~mXgKregk$+sSQYEhuyEG0#@k)j1cVTU`jv%}fhnQs&|n>7oM{p)Jn zty|V_#LPb~m^&ExE{L!MOROQ|Y-n?v5pO5X(B-a~J4t2eagSN|E#ZpFGfPyY`_$n+ zcu)A?eZvRf15pKEHGEandRDi7iZ57QTe~~CRJw9sW#dto>FVcEF2BlzOu%?Un&f>_ zoqxOuli$HaTAT@s+k$aNx`T>zge@F$>Dc9-^o92m6TS$ZxjcAo?J)rdo&~)6+;S~$ zT+!{#tcdsGepJL+x_vm3-3EC!CePfR-rcRP&HX4zNWlH|Bq?ROvQJ*a`K<>#51|u4 z$B1|UrKz+^+e&ej4qF}-7Dw4QDbykNAC?nXSQx47Nu2g&81?&dRPffriD2FEP+6R* zC^?Y*LJHoVDs4?9P+RK9#bKC@q>746P0e*SAbl_q&P=YH)t%X0n%ADQeJRTGct0J= zw3rx%6P@*$urw?M+?ZFL*_~6JT0Q*)SE;S-6Ic8FgDmdLT-S!t_hBN_{o+9TLheNf zmv+-}lAItnlq|+7?XyWP(2M3YHZXEZk|S$i^{f-NiDYuqXehDK&1JH8VyP=w|MS+y zr@Jc3BXJn15P=#04kk63IS**~)%8C6-&QcGCwI=YgNaczoDg(E~y zE;Z*Fk7Z-CVxlCYkKkbr6OWr!dsDFvL;E z3C$Z}`0Y4K$`xKusa(PWr^{7^e5h_5MCx8F6QP!gcMumv=Q1DG&528WnIu!UDE#UM z5#u18E5OJJpaSN$I(ExxIOe=Bum5kLJgtA}3|zs4eatv8V1UX84Q5DCovgdUJGOaE z)`bsTAYgKN!{9KyW54UXs6M3NT$M$OW~6ZRORP7@F`=tlz823>-_=I^arp_Zs*B)Gc;3eFz^9OHB!4Xm7yMvkVy-5w~=-y5f9hqq(i zSoiW6h+c57XWrYJ$ZEoouB+Zhl*__s)9eEf-Z@iZCjYy3sYL6l3Uc}j&FD(IPFY=M zKb2ej2TDqHY5gY1DPid;A&hDg`TqqN7m0jn0rp^Xn)6HZv3sSpM~(8|YwznhKh8kd znkl>M=gLmA62;YrWSM|ieFVa5CiVvA^}<(CdbphtEn(gu02R5SUUu%D%%7dIkZ)lX z9X(46+`6vB!)RY}H--TJs8GML$azi3CusO)?jJ)A}YqY`k21)UkjXKL%!#? zLC{5fm)VR4et&s}!|ZRbGLd1{W^LBOkKS$8nF0b?+U(Z!su)D+ezry@Q1PR+Xj~ku zm45*YCYj^w`Ua3{DyGJ+tCM}=QHg3MtR`sY{tQ^YAcRgjxLx+!&!`QUKG+6CH7f1Z GcIzK|Grk%C literal 0 HcmV?d00001 diff --git a/Notification/__pycache__/serializers.cpython-39.pyc b/Notification/__pycache__/serializers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa915aea6ffcaad910c108fad20a6e0f5af9f5f2 GIT binary patch literal 1122 zcmbtT!EO^V5VhC4&L-J5EkYc^g%eT^};4wq0-SQRMK6gtGgFu)Vl>#GNPgH zjmwqIZ;bP|6l+n)rd6FU^0}(>stl*?>|?bwC2SjOknyU1VaSX zL_|P_>Ile0n@BLtfJhvk|AqeuEHZ3hpSEBP6S1Zu(GdW$n-`A&j?Yc4LPttvt(4Od z52=);DvXq&CuLFTwZ(NN<=3^c+b=GG!$NXiX=4LlM) zWn;xX7Ou8#rB z`=ht30oHIghv}i40HMhrw&f-K^v4#%HblfGfynP!)Y)0|tWEtt-MMG?9;-3NrN9bI zIlMD!;xY_J8195`-5zDZd8l7{h=nqRUWpwp(H@7IiT<|m8N2#niBI8Iq~UQ-N;&0` z!`gJz3&+m*S}q*)(pBY_4qXTw8p>GjnhRhTiQ&6-{l%2^?sbh@eb6uogQm^)!ZbcM Zr~K>QA$_y$*|+#IJnEOx6PmFn{5K~b_A&qf literal 0 HcmV?d00001 diff --git a/Notification/__pycache__/urls.cpython-39.pyc b/Notification/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abf6e9e092f4004138c56d7ba793de0f34559770 GIT binary patch literal 541 zcmYjNJ8l~>5G8k4+V$!ar7#fW1}dT=oeje<;M6K2AO#^npv93)nM(>ot_;_P%iJL; zbD%VAQ~3&MlHn==M1qfR9_PauIU3cBR{rz7e&LM$O2+Bxk#R%ETLQ%tH>}};H<`$I zvNMx6g(y0nn_klw{f-x=Y$|a^yr=r6YHCq)2F16*cP54~+>KNTwJKHhH7A{`Gss9C zKlA5o%f%a2EZDSurJ8J-#bPhReJr(In^wV;#}Zu|06o#lLkQY#{rr{w&q#SUm%D?U zyP!9EErWLUi-yMq1o76Mh^ss4Q?4rARyGjXbP@+@C+*hFS~MOrN{Bf@93%L!+90_- zI()z)iT{1q&TcRbDxN#phWn4JJ-UNdfU^Tt_OW-ohW}nW3(FKR`UD%nC-#qh z#R_4oJ#`q1^pQ9qq#2N4{SSuc6xzY>i`&a3N-x!u#8h>+Qf~bKxNJS(hdW8!s^#e{ fmMP(E{}ijM#;Ml8bsC$W5KQ>+xU-5+^6{U)jfS22 literal 0 HcmV?d00001 diff --git a/Notification/__pycache__/views.cpython-39.pyc b/Notification/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4498a1a593aaea9a769317584257639ae7d0a79d GIT binary patch literal 4414 zcmb^!U2hx5@xFL>;+IJ3%aZlwGd>dTed62m0L*((1nb{iF>Awy5rHi zOW9P(C;O>@3K!@PkRF}i5)^&u%c5_2a$ft?00la;M^=<#2MJITH@7o4JG=9-GqZl7 zkk#O4{$noG7c}i_L`HuWAa~&5e*-`@sz(}0-#XD1ZV&_BMr6hov2^@qMw!?qwi;WJ z6T8G!<4iObXGu2Bk(|QqC?6L{0mcq>qhdTx#^Vwx#br`fw6UlXPmqatl1wW8Y%~>5 zlj(Sd%&70V=t4Y8X5%?Br@rT-i}5^}SK~so5HFHN7#GRXLyeBp(kqRYf^2j7rAaPP zeM_sB--1rsMpc(K3tBW_Fq&9v`mILtAdE!7?)vq8_yFYm=R9ERG-!!XbgPDR55nL% z4+NJs7ry9l;3;(4QQ}iC*=+`O@m2@fRa53S5)nQP>%It+mMo28+ue2`%V#JFtOw#R zaah)Cjm-2!;`veJ@t_gIF1Wn>P8Qw{>bTmF4`f-mb(wj{l1>}Yi{DvqD`24?g@?d3 zJ^JyK4$8T!QItGiZFO8i z^Vxyl;v!*wBdFDr_*xyUKl(EOxdRVh0T5^yU&IK^rgfY-(TE*5#0|z8I>}O<8m}~x zqb9ZBou?V_rY$dQ_{{@1 zs?S19AL)IoryW~8>qPHoM5dQ%+QMnN)I2h_G-?gz{YlLm11#Ij(TtjJ!+ht+>|4~O zW3RNHwWht)`*u%jT0NU)Y3|5)hs69q;y=$wES!^A{1FnzQ;Dy2wsJ;h>72~+50{B@ zj`Js4-%;8)@6|>FuT(aqxgT`jfY;#VdBF?6F5c+O2J4e73*fw{*pU{FrR%rbUdxXI zI5|!S0@n+v;$^cip^j&2_(PwX@NB`uO8^>LPkZ9_!JB_Eo>^Pk8;xBA@*6`MwH2wq zP?!SW-n@5nhxy#6U7vYvmNY$@z)@#Axa8H{^w{r=M$(Qt@U?bVW^Y9y7ZAYh+t)z+ zzu?ghufH$!YRFu>6(w~);P;fmFQG#}pIW@A8*PMRLMG#i1CC9wA(`FQon=bTL- zlSO^uE5FO!Z??C8=iOLY@g9FB?WZa)OAGxZjgU$ca!u8h77wDQ(qX}~PQZnq=L>Ad{c7nY7STAHrt%8URht>XY!4;IZLv=LVEi%%!KqbtMy$`=(*IU&T&zU_w1MYiB8=kQ`bakls2RF z$3*$KGD@3L6{vg@8_sY)E?5S)>T`eU&3n?|XZy+uv zz!HnWSisR3;oc065nV!@s#_u@$AZL{h1V(zIV?ST|BafyHuQL>%m_CH`DPxP-E28@qPl@ zI^Kt%vEyA<^mBGTV?&dr)&d*CE^mOwR|a~d8h@p5)EMxl*)R2+=10dL_e!WUXz`V} z(96HXrfmuOHTH8j8d7^Z4lhWf`o7GBF|-wjlke(74xK-)^lQy_qiT|@;`1=Qklbzu za=uXIM&{DAlpYb|L7YzDS)IF0FO*^e-dcbE5k zzO0yK=D?3SI5iYeAg$emcBKQN1$1zj`qEa;3tRQ5LxT}Y@Iq0l4!Z;LnaZShadZp8 zO$7H4U}};Mq(IyN78ER^ep(0-b**cZ~|g2~0z%`a9ryp6}(>kroNt!}SB-tZo-K6-S2 z^AFzVn~$WCz%nd|lY@Zea6dmo@M{FvKR}uA;R}|Sw5j+h5-Y00{9@%3@4@5EyX$Lf z_cvG`S@32gvnr(07_gx`f!fJ1?pOiA9R%k*gI6Y59*sm#l`9jD3R|CcGG$mwg66mZ-XZ*7#n+M7;xt0^3}&kdnQ$ z{oyqRO^)}J!C8KuuziMYFqvBIh&{ZZrdQ1xC>cbYsc0;on}GM9v;|+fm$0C=ig&ZE z!QD(<)V#q>ZEy$&!muS#$3oeP%(w8uRXdF^yrm99IXy#oO61%?D@=WmKWaYgbhtm2 zK=44x97O)07v!vjE1~h_+}XX?PCdX=pUj+N094&h({uiNl#%}}8e?!9RHh$slkyt6 iNV>Pu$heL74890J#i)UC2T=*{$xO+DsIve!Ec3q^?tt3> literal 0 HcmV?d00001 diff --git a/Notification/admin.py b/Notification/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Notification/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Notification/apps.py b/Notification/apps.py new file mode 100644 index 0000000..0afd4da --- /dev/null +++ b/Notification/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NotificationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Notification' diff --git a/Notification/migrations/0001_initial.py b/Notification/migrations/0001_initial.py new file mode 100644 index 0000000..37e9eda --- /dev/null +++ b/Notification/migrations/0001_initial.py @@ -0,0 +1,80 @@ +# Generated by Django 3.2.13 on 2023-09-17 15:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='NotificationType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('name', models.CharField(choices=[('user', 'USER'), ('alluser', 'AllUSER'), ('group', 'GROUP'), ('allgroup', 'AllGROUP'), ('usergroup', 'UserGroup'), ('poultry', 'Poultry'), ('province_accept', 'ProvinceAccept'), ('province_rejected', 'ProvinceRejected'), ('city_operator_accept', 'CityOperatorAccept'), ('city_operator_rejected', 'CityOperatorRejected'), ('assignment_accepted', 'AssignmentAccepted'), ('assignment_rejected', 'AssignmentRejected')], default='', max_length=50, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtype_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtype_modifiedby', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='NotificationToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('token', models.CharField(max_length=100)), + ('app_name', models.CharField(max_length=100, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtoken_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtoken_modifiedby', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_user', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('title', models.CharField(default='', max_length=200, null=True)), + ('content', models.CharField(default='', max_length=500, null=True)), + ('image', models.CharField(max_length=100, null=True)), + ('icon', models.CharField(max_length=100, null=True)), + ('app_ids', models.CharField(default='', max_length=200, null=True)), + ('device_ids', models.CharField(default='', max_length=200, null=True)), + ('hash_id', models.CharField(default='', max_length=20, null=True)), + ('status', models.CharField(choices=[('read', 'Read'), ('pending', 'Pending'), ('sent', 'Sent'), ('unread', 'Unread'), ('silent', 'Silent')], default='', max_length=10, null=True)), + ('app_name', models.CharField(max_length=100, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_modifiedby', to=settings.AUTH_USER_MODEL)), + ('notif_type', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='types', to='Notification.notificationtype')), + ('notification_group', models.ManyToManyField(null=True, related_name='group', to='auth.Group')), + ('notification_user', models.ManyToManyField(null=True, related_name='notification_token', to='Notification.NotificationToken')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/Notification/migrations/__init__.py b/Notification/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/migrations/__pycache__/0001_initial.cpython-39.pyc b/Notification/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98fa53ec4af03c7b16dd355f4422a065da07b02c GIT binary patch literal 3094 zcmcImOLH4V5ZlI54|IJT3R@CYCv#t8|K2M|A!kRo+(S;MyIA%d3su-f!Vh!%aEDR!%YlVdR(MDa$Wq;>E+`&KE zFg1;8Rh?*6gXmS0THo0;*UW#f6Qf~N9cncT#3U9iHb;n#$0j+FKeF}=;s7hr(dHO1 z$tftb2y`6O$dL}2Qc8^~Y7Eo_q{kIC0cx_Lk;xx)QYKSaGp#h70bZ6G=~Q#NS!|wZ z&X5^t6FGZiL+9)%{J8iq z!K;6{GTX)oQdLTnes`7|0(tsc9Yy9w^g2f z2JTtx{WMXf|s$3O?^6Yq;lOG{U-x}G0X z?sg&!dyGf18+a}1w!G)APlE=;CN{#D?XkKl;O%ak3WJe*!g#P&u|x@)LbuMT7gHie z+MKmK-gOUXR}`R* zP%)CIbqRE%qa<4e;WO7G*b04w?R9%hN7(_Uuw(8;`xTu}pqW4;ys4C=&(cLwqr5&I0cp!4Ux&d2&YY2mr?vT z^ic}-Rn82{_VrF@r8ByxyeMLgphbf?RSYA_%Y86WObN`$iuUzpM0;9=c6=4#m#19c z4_Tc?$#E11dBw}}e2Qu6xV5gB4>E}sBhh+!RqIx4Vb^>wI7n6%!obB>gvLlMPJNks zNopB!m}Uk81fAzz(y8(WTreH479m^eamD0w(wf{Jqdtj5K5QpntI46<4-aU7{F1L@ zBHh#Zb-B#(+HJLe#ZW+E=ZJ_EHP*en*U~s-j=u@1Sn&iE=kMagxabUa@* zVOQi?z+yB}jysVirlV&#B-tsE{W9V-sCT&`S>ww)+h4k}k-6)S*6uzOrM?8Ckr-K3 z9eQ2fy|}#diP8#dETebdlNm1xJ39~7lBw0{Fd}R@UK68hsI6_*q6x3G5{5qYf^^a9 zzQ>bk@m|O&ddOGk`#I9|f<`C|QWK7_2w>GxktJlchM!AE(z-gl<&$0e7WXf9+2H^WOoPm%-tn?+}t#Ji|sDA zp|^m4^c;g@SY}Z-{;_hFZWz=0j6SBGS^c7Z@h@Y<`qy}2O$_tK^a;t;7ydLx!T-Z5 z5A(}<84?p1F@Xc2X{mz-ZAMMzB@b#g{?eQ*)^fm`!p lFPuDv7d()IbZii{WcD>M;oMG=>T{Vpk%OTGqa58a{{y-6uh9Si literal 0 HcmV?d00001 diff --git a/Notification/migrations/__pycache__/__init__.cpython-39.pyc b/Notification/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42d15d4f50131e347829b28c591b6e2d4844ab45 GIT binary patch literal 163 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6wm*(# str: + return self.name + + def save(self, *args, **kwargs): + super(NotificationType, self).save(*args, **kwargs) + + pass + + +class NotificationToken(BaseModel): + token = models.CharField(max_length=100) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notification_user", null=True) + app_name = models.CharField(max_length=100, null=True) + + def __str__(self) -> str: + return self.token + + def save(self, *args, **kwargs): + super(NotificationToken, self).save(*args, **kwargs) + + pass + + +class Notification(BaseModel): + s = ( + ("read", "Read"), + ("pending", "Pending"), + ("sent", "Sent"), + ("unread", "Unread"), + ("silent", "Silent"), + ) + notif_type = models.ForeignKey( + NotificationType, + on_delete=models.CASCADE, + null=True, + default=None, + related_name="types", + ) + notification_user = models.ManyToManyField( + NotificationToken, + null=True, + related_name="notification_token" + ) + notification_group = models.ManyToManyField( + Group, + null=True, + related_name="group" + ) + title = models.CharField(max_length=200, default="", null=True) + content = models.CharField(max_length=500, default="", null=True) + image = models.CharField(max_length=100, null=True) + icon = models.CharField(max_length=100, null=True) + app_ids = models.CharField(max_length=200, default="", null=True) + device_ids = models.CharField(max_length=200, default="", null=True) + hash_id = models.CharField(max_length=20, default="", null=True) + status = models.CharField(choices=s, max_length=10, default="", null=True) + app_name = models.CharField(max_length=100, null=True) + + def __str__(self) -> str: + return self.title + + def save(self, *args, **kwargs): + super(Notification, self).save(*args, **kwargs) + + pass diff --git a/Notification/najva/__init__.py b/Notification/najva/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/najva/__pycache__/__init__.cpython-39.pyc b/Notification/najva/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..496fce870e34176f7229ee0b47473400eed202d5 GIT binary patch literal 158 zcmYe~<>g`k0;a1{nIQTxh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6w+*(#MdshRIIiQIZ~itUuPb zW`;8m$cgIaM{Oy4;$opddcAx=I(JCk4ZGlZg*(MU5K6D~7Rkn}^%0 z3$GOrzs>^E@ENTgo9ZL38}?y|aegh_U_s_WWyvkBx<$IYve;y9k+J5=(kkSIu9VA* ztkk&BD3$0E9nV3k;kZ#!3mst4*a^a%OWZcwF;$xMWgFt!jK()p2WiR4L62>wqggrZ zO@&+?P$CN-Q%yWTwLcw5IP{d-s54XPD6Kt@%6r7Jl7y#2XwtU#l!W8 zrR#8+v>x%ZF2nldKJ&eSdjHkXL6&g_@%@*xkVGO6pd~ER&=0-F+@$C4_Up#(V zpVxUAzSz$IjD8($?|ldD`@MS^aDE#7j810s`?LFdKa6(wkK&|X!2B&U`VnS6eRhBJ zbpP_}JElPtH zfm(qUB_b_KL|Swt(xT)r30gE)-K_rz%p3s>>DX8ddx*Rp70nMEO1}Vv5Z3`1`5yp6HmWJ-%Btv zYgb=9**ISTpRXcR5H3uhnoXz$+95$J%;S~hC#A~`oDyZ%LJOsYpGWu*0n3BqCR5== jfkiPnm%1VC`|J~xcY*Pz^ab6~9ZS=v-hY$lr@Zn1wz5a< literal 0 HcmV?d00001 diff --git a/Notification/najva/get_segments_detail.py b/Notification/najva/get_segments_detail.py new file mode 100644 index 0000000..56bcf9f --- /dev/null +++ b/Notification/najva/get_segments_detail.py @@ -0,0 +1,17 @@ +from django.http.response import JsonResponse +import requests +import json + + +def get_segments(request): + url = "https://app.najva.com/api/v1/websites/65b3a75a-d634-48c5-824f-c80c703534af/segments/" + + headers = { + 'content-type': "application/json", + 'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d", + 'cache-control': "no-cache", + } + + response = requests.request('GET', url=url, headers=headers) + resp = json.loads(response.text.encode('utf8')) + return JsonResponse(resp, safe=False) diff --git a/Notification/najva/send_notif_to_segments.py b/Notification/najva/send_notif_to_segments.py new file mode 100644 index 0000000..671b742 --- /dev/null +++ b/Notification/najva/send_notif_to_segments.py @@ -0,0 +1,75 @@ +from django.http.response import JsonResponse +from datetime import datetime, timezone, timedelta +import requests +import json + + +def send_notification_to_all_segments( + title=None, + body=None, + content=None, + icon=None, + image=None, + segments_include=None, + segments_exclude=None, +): + url = "https://app.najva.com/api/v1/notifications/" + + payload = { + "api_key": "65b3a75a-d634-48c5-824f-c80c703534af", + "title": "title", + "body": "body", + "priority": "high", + "onclick_action": "open-link", + "url": "https://imedstores.ir/", + "content": "content", + "icon": "", + "image": "", + # "json": "{"key":"value"}", + "sent_time": datetime.now() + timedelta(minutes=1), + "segments_include": [], + "segments_exclude": [], + "one_signal_enabled": False, + "one_signal_accounts": [] + } + headers = { + 'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d", + 'content-type': "application/json", + 'cache-control': "no-cache", + } + + response = requests.request("POST", url, data=json.dumps(payload, default=str), headers=headers) + resp = json.loads(response.text.encode('utf-8')) + return resp + + +def send_notification_to_specific_segment( + title="سامانه سبحان طیور", + body="خوش آمدید", + content="سامانه مدیریت درخواست های مرغداران", + icon="https://user-image-gallery.s3.ir-thr-at1.arvanstorage.com/1WGPTMFND3TREWD.jpg", + image="https://user-image-gallery.s3.ir-thr-at1.arvanstorage.com/1WGPTMFND3TREWD.jpg", + subscriber_tokens=None, +): + url = "https://app.najva.com/notification/api/v1/notifications/" + payload = { + "api_key": "65b3a75a-d634-48c5-824f-c80c703534af", + "subscriber_tokens": subscriber_tokens, + "title": title, + "body": body, + "onclick_action": "open-link", + "url": "https://imedstores.ir/", + "content": content, + "icon": icon, + "image": image, + "sent_time": datetime.now() + timedelta(minutes=3), + } + headers = { + 'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d", + 'content-type': "application/json", + 'cache-control': "no-cache", + } + + response = requests.request("POST", url, data=json.dumps(payload, default=str), headers=headers) + resp = json.loads(response.text.encode('utf-8')) + return resp diff --git a/Notification/serializers.py b/Notification/serializers.py new file mode 100644 index 0000000..4a6e3b1 --- /dev/null +++ b/Notification/serializers.py @@ -0,0 +1,18 @@ +from rest_framework import serializers +from .models import Notification, NotificationToken +from Authentication.serializers import GroupSerializer + + +class NotificationTokenSerializer(serializers.ModelSerializer): + class Meta: + Model = NotificationToken + fields = "__all__" + + +class NotificationSerializer(serializers.ModelSerializer): + notif_user = NotificationTokenSerializer() + notif_group = GroupSerializer() + + class Meta: + Model = Notification + fields = "__all__" diff --git a/Notification/tests.py b/Notification/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Notification/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Notification/urls.py b/Notification/urls.py new file mode 100644 index 0000000..499022a --- /dev/null +++ b/Notification/urls.py @@ -0,0 +1,12 @@ +from django.urls import path, include +from rest_framework import routers +from django.conf import settings +import oauth2_provider.views as oauth2_views +from .views import NajvaNotificationViewSet + +router = routers.DefaultRouter() +router.register('notification_base', NajvaNotificationViewSet, basename='notification_base') + +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/Notification/views.py b/Notification/views.py new file mode 100644 index 0000000..6c4f60c --- /dev/null +++ b/Notification/views.py @@ -0,0 +1,157 @@ +import os +import random +import string + +from django.shortcuts import render +from django_filters.rest_framework import DjangoFilterBackend +from Authentication.models import UserIdentity +from rest_framework import viewsets, status + +from Core.ArvanStorage.arvan_storage import upload_object +from .models import ( + Notification, + NotificationType, + NotificationToken +) +from .najva.send_notif_to_segments import ( + send_notification_to_all_segments, + send_notification_to_specific_segment +) +from django.contrib.auth.models import User, Group +from .serializers import ( + NotificationTokenSerializer, + NotificationSerializer +) +from rest_framework.permissions import AllowAny +from rest_framework.response import Response + +ARVAN_NOTIFICATION_GALLERY_URL = 'https://notification-gallery.s3.ir-thr-at1.arvanstorage.com/' + + +class NajvaNotificationViewSet(viewsets.ModelViewSet): + queryset = NotificationToken.objects.all() + serializer_class = NotificationSerializer + permission_classes = [AllowAny] + + def list(self, request, *args, **kwargs): + if "key" in request.GET: + add_obj = Notification.objects.get(key__exact=request.GET["key"]) + serializer = self.serializer_class(add_obj) + return Response(serializer.data, status=status.HTTP_200_OK) + if "read_notif" in request.GET: + add_obj = Notification.objects.filter( + user_id=request.user.id, + status="read", + app_name=request.GET['app_name'] + ) + query = [x for x in add_obj] + serializer = self.serializer_class(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "unread_notif" in request.GET: + add_obj = Notification.objects.filter( + user_id=request.user.id, + status="unread", + app_name=request.GET['app_name'] + ) + query = [x for x in add_obj] + serializer = self.serializer_class(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "pending_notif" in request.GET: + add_obj = Notification.objects.filter( + user_id=request.user.id, + status="pending", + app_name=request.GET['app_name'] + ) + query = [x for x in add_obj] + serializer = self.serializer_class(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + queryset = Notification.objects.all() + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request, *args, **kwargs): + segments = [] + userprofile = User.objects.get(user=request.user) + ran = ''.join(random.choices(string.ascii_uppercase + string.digits, k=15)) + notification = Notification() + if 'image' in request.data.keys(): + image = request.data['image'] + upload_object( + image_data=image, + bucket_name="notification-gallery", + object_name="{0}.jpg".format(str(ran)) + ) + notification_image = ARVAN_NOTIFICATION_GALLERY_URL + "{0}.jpg".format(str(ran)) + os.remove("{0}.jpg".format(str(ran))) + else: + notification_image = "" + if 'icon' in request.data.keys(): + icon = request.data['icon'] + upload_object( + image_data=icon, + bucket_name="notification-gallery", + object_name="{0}.jpg".format(str(ran)) + ) + notification_icon = ARVAN_NOTIFICATION_GALLERY_URL + "{0}.jpg".format(str(ran)) + os.remove("{0}.jpg".format(str(ran))) + else: + notification_icon = "" + if 'request_type' in request.data.keys(): + if request.data['request_type'] == "token": + if not NotificationToken.objects.filter(user=userprofile): + notification = NotificationToken() + notification.token = request.data['token'] + notification.user = userprofile + notification.app_name = request.data['app_name'] + notification.save() + return Response({"msg": "Done"}, status=status.HTTP_200_OK) + else: + return Response({"msg": "user already has token"}, status=status.HTTP_403_FORBIDDEN) + if 'value' in request.data.keys(): + if not request.data['value']: + send_notification = send_notification_to_all_segments( + title=request.data['title'], + body=request.data['body'], + content=request.data['content'], + icon=notification_icon, + image=notification_image, + segments_include=request.data['segments_include'], + segments_exclude=request.data['segments_exclude'], + # subscriber_tokens=['c22206d3-248a-4c81-b7c2-de2cfe5e5766'] + # subscriber_tokens=['2cc244fc-1340-4942-bf19-2ba9f66f44e6'] + ) + notification.notif_type = NotificationType.objects.get(name="alluser") + else: + for key in request.data['value']: + if User.objects.filter(key__exact=key): + notif_user = NotificationToken.objects.get(user__key__exact=key) + segments.append(notif_user.token) + if Group.objects.filter(name__exact=key): + for item in NotificationToken.objects.filter(user__role__name=key): + segments.append(item.token) + send_notification = send_notification_to_specific_segment( + title=request.data['title'], + body=request.data['body'], + content=request.data['content'], + icon=notification_icon, + image=notification_image, + subscriber_tokens=segments + ) + notification.notif_type = NotificationType.objects.get(name=request.data['request_type']) + notification.title = request.data['title'] + notification.content = request.data['content'] + notification.icon = notification_icon + notification.image = notification_image + notification.app_name = request.data['app_name'] + notification.save() + if 'value' in request.data.keys(): + for key in request.data['value']: + if UserIdentity.objects.filter(key__exact=key): + user = UserIdentity.objects.get(key__exact=key).user + notification.notification_user.add(user) + # elif Group.objects.filter(name__exact=key): + # notification.notification_group.add(Group.objects.get(name__exact=key)) + # for item in User.objects.filter(role=Group.objects.get(name__exact=key)): + # notification.notification_user.add(item) + return Response(send_notification) diff --git a/README.md b/README.md new file mode 100644 index 0000000..18f3c40 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ArtaSystemMain +Arta System Project For Main Server diff --git a/Wallet/__init__.py b/Wallet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/__pycache__/__init__.cpython-39.pyc b/Wallet/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b8e2227e605432e0e5f3a0ed429c3d33e32f1ec GIT binary patch literal 146 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 LTfbJHuCM$*sEODZ literal 0 HcmV?d00001 diff --git a/Wallet/__pycache__/processor.cpython-39.pyc b/Wallet/__pycache__/processor.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d41484d607e75a99f33ad614c52368fa9f4b9c53 GIT binary patch literal 4291 zcmZ`+TW{RP73Tf67hNsMvYjxE6?6-Gt)xktDrst2@PPJ2?GSpnUzTKvKex1 zYe7B9=$nE51p6_s{R#aELHokjKKU0EP?5Ul3@K?P$Rv1p=E}o4-}&Z@*J@b;T=Ksb zga6J6!oRUFekoA62~SplAOaC1Ar>4#L|ux+SaKw;%aI%_j>2^%Qe(~0xUNQeY&Ztj zwaAPur@?hSYQ`<6#dRZU#}m$kC{T%*2ck21S0EN?yb?%*P94b3G-*B(I<0qLfzT04 zgLpa3gP4MDZqxjJy1h*q6qJFN@4#Cf4g>Nf_RMIW2g!DZ`VhM2^kt6vWBTllH;ho% z)}nwWxuXxfy(mbik6#(TWGLK(CtCqQ1xF-;BN5S&iA3aAf};?HsPI-vo9M)NB{~{0 zi3K$}X^=68Wt(CmhIy;Oe{u%;P14m$bTM@{(Sc zth`YOp$?A$k4cQzIKD|tFx(m~Y$RFkC3ye~Z)Zc2**;&iuG8FHeR6-JciVmZX#M`k z><1BDxp8AD8(mlr81?h|$5uBtx-T*kIWtH7AKZSpc5k(}WXp?C&gL4)ZV0w1+&3bBkiM=|^cTyINygWz~o6#SJG|TNgwPSA= zvCgQS?@-(K7_o!Qe(ps9>AoYd)Q62E`+Q|kMee(wfF??LZFB3;#A*O1Wryu0a$_mJ zvGl}Vy0^0Q;LyM(oC+|Fe1kDxs=RNhVEy6z5fjwNvHM;Sfe~9j0&CHiK0v0Q-sv4K zf1uwrdk|4C1EblV?QMHOVn-0PtXrzfcpXYBHbZh5&9C7v@&Bp*-G_n6aL|#QR?fU6 z^L$(q2v6NlW3;sM3A+FzOT7bbEH>bxE-b=on+d_0WZ zYRT9yU=H>vimNyqEJem{ViEbE5*M+dRXd8hI28`aZh{ab1vo;hZv*ru*C)qnCD9PI z_m?B*r~juY7StKY&X$h^cyu*E(c$6y&_6&qj(gmNOo)5al_Bm; zSAn=U-Ba8HWes@13^i8=F2T0}Jb-T#*Z|)apRW-%A%=}?r!KYjtc5l)9Q#00KDZI)QT6{uZ6t^girjQ+aDL2ReY z9u5$2wrBgpEKgx`uwQWAMmpF@BVyMfYhw_yn1iru+4c31=0ld)TWm<}pkJ{DPK~^B zGWiQH16J{I8|V+ClhGp!xuN)2>iqc(_RL|&HnVB2(HcV?B!!N$JE-oHbEnGO2}#kU}wW);_u`OiDe zYFVlhk7f3I?D`#wKcKjYVim!QZWR(SAQ6UGg1QFBENV@_tjsZc^)AhiusMcByU)@Xb4ko8yETAV4{T&- z(Ze2KEiQ4dLQtzh5cfYO)#L8Q-~F)9r?K_K@iKQj`xDll=Qs-{uido^eKKTGK^iA1 zbPdqd&!fe&&N&(CqZ^JcB&B1Qlu&>L6laBfNMXm4A0#D(r!Wd6KMLq7Q9)M8k2040 zKw7h}9w$F4BtLSY@{#IT@&m~Z)K1jypA4sf-c)curZrlf)=cmHy>|Z$kk9F~=E9k@ zMhoY{d7jp2;X-(^!15wbZcw{aJAZ1l^Z+vx@0g6Z(=kWc4xZDbhDDcMf^}e*QCtD> zj-vJvijPs)AUg8c_Hz~`Jx(&LgPl3WTs}iFtcy)IP%NYP90l(5oVM7nQN!(>eTf1i zh<%0PHzNnDEuWbZep?!6X^ZBuKcop104A0~dE!4T1aVS>T>CcdsLEbxb@3ASJ>%#d2N9 zmWKSy(sbP)hF(;+=q?^AT$e3^CG2|?4^ccq@n;k#IU@TADy=rmC6R_7>wsN#+-we(pCheU49dH+xv-&RZV9h#OQ(=;K& Lfm^EIvLXK;C8;-B literal 0 HcmV?d00001 diff --git a/Wallet/admin.py b/Wallet/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Wallet/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Wallet/apps.py b/Wallet/apps.py new file mode 100644 index 0000000..2845f18 --- /dev/null +++ b/Wallet/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WalletConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Wallet' diff --git a/Wallet/errors.py b/Wallet/errors.py new file mode 100644 index 0000000..881c2c2 --- /dev/null +++ b/Wallet/errors.py @@ -0,0 +1,10 @@ +from django.db import IntegrityError + + +class InsufficientBalance(IntegrityError): + """Raised when a wallet has insufficient balance to + run an operation. + We're subclassing from :mod:`django.db.IntegrityError` + so that it is automatically rolled-back during django's + transaction lifecycle. + """ \ No newline at end of file diff --git a/Wallet/migrations/0001_initial.py b/Wallet/migrations/0001_initial.py new file mode 100644 index 0000000..b11cf3c --- /dev/null +++ b/Wallet/migrations/0001_initial.py @@ -0,0 +1,204 @@ +# Generated by Django 3.2.13 on 2023-09-17 15:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Address', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('title', models.CharField(default='', max_length=200, null=True)), + ('country', models.CharField(default='', max_length=100, null=True)), + ('province', models.CharField(default='', max_length=50, null=True)), + ('city', models.CharField(default='', max_length=50, null=True)), + ('street', models.CharField(default='', max_length=200, null=True)), + ('postal_code', models.CharField(default='', max_length=20, null=True)), + ('phone', models.CharField(default='', max_length=20, null=True)), + ('phone_type', models.CharField(default='', max_length=20, null=True)), + ('no', models.CharField(default='', max_length=5, null=True)), + ('floor', models.IntegerField(default=0, null=True)), + ('unit', models.IntegerField(default=0, null=True)), + ('is_default', models.BooleanField(default=False, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='address_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='address_modifiedby', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_address', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='BankCard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('card', models.CharField(default='', max_length=16, null=True)), + ('iban', models.CharField(default='', max_length=100, null=True)), + ('state', models.CharField(default='pending', max_length=20)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bankcard_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bankcard_modifiedby', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='banks', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PaymentMethod', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('method_type', models.CharField(default='', max_length=255)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='paymentmethod_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='paymentmethod_modifiedby', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_user', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Wallet', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('credit', models.CharField(default='0', max_length=20)), + ('card_expiry', models.DateTimeField(default=django.utils.timezone.now, null=True)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallet_createdby', to=settings.AUTH_USER_MODEL)), + ('credit_cards', models.ManyToManyField(to='Wallet.BankCard')), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallet_modifiedby', to=settings.AUTH_USER_MODEL)), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallets', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'wallet', + 'verbose_name_plural': 'wallets', + }, + ), + migrations.CreateModel( + name='Transaction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('date', models.DateTimeField(default=django.utils.timezone.now, help_text='When the account was created')), + ('amount', models.DecimalField(decimal_places=5, max_digits=12)), + ('status', models.CharField(choices=[('completed', 'Complete!'), ('requested', 'Requested!'), ('pending', 'Pending!'), ('confirmed', 'Confirmed!')], max_length=45)), + ('transaction_type', models.CharField(choices=[('send', 'Send'), ('request', 'Request'), ('transfer', 'Transfer')], default='', max_length=45)), + ('category', models.CharField(choices=[('Bank', 'Bank Transfer'), ('Utilities', 'Bills & Utilities'), ('Transportation', 'Auto & Transport'), ('Groceries', 'Groceries'), ('Food', 'Food'), ('Shopping', 'Shopping'), ('Health', 'Healthcare'), ('Education', 'Education'), ('Travel', 'Travel'), ('Housing', 'Housing'), ('Entertainment', 'Entertainment'), ('Others', 'Others')], max_length=45)), + ('description', models.CharField(default=False, max_length=200)), + ('create_date', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ('is_complete', models.BooleanField(default=False)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_createdby', to=settings.AUTH_USER_MODEL)), + ('creator', models.ForeignKey(default='', on_delete=django.db.models.deletion.PROTECT, related_name='creator', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_modifiedby', to=settings.AUTH_USER_MODEL)), + ('payment_method', models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payment_method', to='Wallet.paymentmethod')), + ('receiver', models.ForeignKey(default='', on_delete=django.db.models.deletion.PROTECT, related_name='receiver', to=settings.AUTH_USER_MODEL)), + ('wallet', models.ForeignKey(blank=True, help_text='Wallet holding payment information', null=True, on_delete=django.db.models.deletion.SET_NULL, to='Wallet.wallet')), + ], + options={ + 'verbose_name': 'transaction', + 'verbose_name_plural': 'transactions', + 'ordering': ('-date',), + }, + ), + migrations.CreateModel( + name='Shipping', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('client', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_client', to=settings.AUTH_USER_MODEL)), + ('client_address', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='client_address', to='Wallet.address')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_modifiedby', to=settings.AUTH_USER_MODEL)), + ('supplier', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_supplier', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Factor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='factor_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='factor_modifiedby', to=settings.AUTH_USER_MODEL)), + ('transaction', models.ForeignKey(blank=True, help_text='Transactions', null=True, on_delete=django.db.models.deletion.SET_NULL, to='Wallet.transaction')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('balance', models.FloatField(default=0.0)), + ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='Wallet.paymentmethod')), + ], + ), + migrations.CreateModel( + name='Card', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('card_type', models.CharField(choices=[('Credit', 'Credit Card'), ('Debit', 'Debit Card')], max_length=45)), + ('card_number', models.CharField(default=None, max_length=16)), + ('owner_first_name', models.CharField(max_length=45)), + ('owner_last_name', models.CharField(max_length=45)), + ('security_code', models.CharField(default=None, max_length=3)), + ('expiration_date', models.DateField(default=None)), + ('payment', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, to='Wallet.paymentmethod')), + ], + options={ + 'ordering': ['card_type', 'card_number'], + 'unique_together': {('card_type', 'owner_first_name', 'owner_last_name', 'card_number', 'security_code', 'expiration_date')}, + }, + ), + migrations.CreateModel( + name='Bank', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('owner_first_name', models.CharField(default=None, max_length=255)), + ('owner_last_name', models.CharField(default=None, max_length=255)), + ('routing_number', models.CharField(default=None, max_length=9)), + ('account_number', models.CharField(default=None, max_length=10)), + ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='Wallet.paymentmethod')), + ], + options={ + 'unique_together': {('routing_number', 'account_number')}, + }, + ), + ] diff --git a/Wallet/migrations/__init__.py b/Wallet/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc b/Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c964aba3f97890ebf445df6bd962c78aeaff33e GIT binary patch literal 5964 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 expiry_date_to_datetime(today.strftime("%m%y")) + + def expires_this_month(self): + """Return True if the card expiry date is in this current month.""" + today = datetime.today().strftime("%m%y") + return today == self.card_expiry + + def make_payment(self, amount): + """Make a payment from this wallet.""" + pp = DPSPayProcessor() + result, transaction, message = pp.make_wallet_payment(self.wallet_id, amount) + if result: + self.transaction_set.create(amount=amount, transaction_id=transaction) + return result, message + + def deposit(self, value): + """Deposits a value to the wallet. + Also creates a new transaction with the deposit + value. + """ + self.transaction_set.create( + value=value, running_balance=self.current_balance + value + ) + self.current_balance += value + self.save() + + def withdraw(self, value): + """Withdraw's a value from the wallet. + Also creates a new transaction with the withdraw + value. + Should the withdrawn amount is greater than the + balance this wallet currently has, it raises an + :mod:`InsufficientBalance` error. This exception + inherits from :mod:`django.db.IntegrityError`. So + that it automatically rolls-back during a + transaction lifecycle. + """ + if value > self.current_balance: + raise InsufficientBalance("This wallet has insufficient balance.") + + self.transaction_set.create( + value=-value, running_balance=self.current_balance - value + ) + self.current_balance -= value + self.save() + + def transfer(self, wallet, value): + """Transfers an value to another wallet. + Uses `deposit` and `withdraw` internally. + """ + self.withdraw(value) + wallet.deposit(value) + + +class Shipping(BaseModel): + client_address = models.OneToOneField(Address, on_delete=models.CASCADE, related_name="client_address", null=True) + # supplier_address = models.OneToOneField(Address, related_name="supplier_address") + client = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_client", null=True) + supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_supplier", null=True) + + def __str__(self) -> str: + return self.supplier.username + + def save(self, *args, **kwargs): + super(Shipping, self).save(*args, **kwargs) + + +class Transaction(BaseModel): + """Payment.""" + + wallet = models.ForeignKey( + Wallet, + null=True, + blank=True, + on_delete=models.SET_NULL, # do never ever delete + help_text=_("Wallet holding payment information"), + ) + date = models.DateTimeField( + default=now, help_text=_("When the account was created") + ) + amount = models.DecimalField(max_digits=12, decimal_places=5) + + status = models.CharField(max_length=45, choices=states) + transaction_type = models.CharField( + max_length=45, choices=Transaction_Type, default="" + ) + category = models.CharField(max_length=45, choices=Categories) + # amount = models.FloatField(default=0.00) + description = models.CharField(max_length=200, default=False) + create_date = models.DateTimeField(default=now, editable=False) + is_complete = models.BooleanField(default=False) + receiver = models.ForeignKey( + User, related_name="receiver", on_delete=models.PROTECT, default="" + ) + creator = models.ForeignKey( + User, related_name="creator", on_delete=models.PROTECT, default="" + ) + payment_method = models.ForeignKey( + PaymentMethod, + related_name="payment_method", + on_delete=models.PROTECT, + default="", + null=True, + ) + + def check_status(self): + return "status is: %c" % self.status + + def set_status(self, state): + self.status = state + if state == "completed": + self.is_complete = True + return "Status %c has been set!" + + def get_absolute_url(self): + return reverse("staff_tran_detail", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("staff_tran_delete", kwargs={"pk": self.pk}) + + def save(self, *args, **kwargs): + # ensure that the database only stores 2 decimal places + self.amount = round(self.amount, 2) + super(Transaction, self).save(*args, **kwargs) + + def __str__(self): + return str(self.transaction_id) + + class Meta: + ordering = ("-date",) + verbose_name = _("transaction") + verbose_name_plural = _("transactions") + + +class Factor(BaseModel): + transaction = models.ForeignKey( + Transaction, + null=True, + blank=True, + on_delete=models.SET_NULL, # do never ever delete + help_text=_("Transactions"), + ) + + pass diff --git a/Wallet/processor.py b/Wallet/processor.py new file mode 100644 index 0000000..d9ac7b3 --- /dev/null +++ b/Wallet/processor.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from datetime import datetime +from logging import getLogger +from os import path +from uuid import uuid4 + +from django.conf import settings +from django.utils.translation import ugettext as _ +from suds import WebFault +from suds.client import Client + + +logger = getLogger("payline") + + +class DPSPayProcessor(object): + """Payline Payment Backend.""" + + def __init__(self): + """Instantiate suds client.""" + here = path.abspath(path.dirname(__file__)) + self.wsdl = getattr( + settings, + "PAYLINE_WSDL", + "file://%s" % path.join(here, "DirectPaymentAPI.wsdl"), + ) + self.merchant_id = getattr(settings, "PAYLINE_MERCHANT_ID", "") + self.api_key = getattr(settings, "PAYLINE_KEY", "") + self.vad_number = getattr(settings, "PAYLINE_VADNBR", "") + self.client = Client( + url=self.wsdl, username=self.merchant_id, password=self.api_key + ) + + def validate_card(self, card_number, card_type, card_expiry, card_cvx): + """Do an Authorization request to make sure the card is valid.""" + minimum_amount = 100 # 1€ is the smallest amount authorized + payment = self.client.factory.create("ns1:payment") + payment.amount = minimum_amount + payment.currency = 978 # euros + payment.action = 100 # authorization only + payment.mode = "CPT" # CPT = comptant + payment.contractNumber = self.vad_number + order = self.client.factory.create("ns1:order") + order.ref = str(uuid4()) + order.amount = minimum_amount + order.currency = 978 + order.date = datetime.now().strftime("%d/%m/%Y %H:%M") + card = self.client.factory.create("ns1:card") + card.number = card_number + card.type = card_type + card.expirationDate = card_expiry + card.cvx = card_cvx + try: + res = self.client.service.doAuthorization( + payment=payment, order=order, card=card + ) + except WebFault: + logger.error("Payment backend failure", exc_info=True) + return (False, None, _("Payment backend failure, please try again later.")) + result = ( + res.result.code == "00000", # success ? + res.result.shortMessage + ": " + res.result.longMessage, + ) + if result[0]: # authorization was successful, now cancel it (clean up) + self.client.service.doReset( + transactionID=res.transaction.id, comment="Card validation cleanup" + ) + return result + + def create_update_wallet( + self, + wallet_id, + last_name, + first_name, + card_number, + card_type, + card_expiry, + card_cvx, + create=True, + ): + """Create or update a customer wallet to hold payment information. + Return True if the creation or update was successful. + """ + wallet = self.client.factory.create("ns1:wallet") + wallet.walletId = wallet_id + wallet.lastName = last_name + wallet.firstName = first_name + wallet.card = self.client.factory.create("ns1:card") + wallet.card.number = card_number + wallet.card.type = card_type + wallet.card.expirationDate = card_expiry + wallet.card.cvx = card_cvx + service = self.client.service.createWallet + if not create: + service = self.client.service.updateWallet + try: + res = service(contractNumber=self.vad_number, wallet=wallet) + except: + logger.error("Payment backend failure", exc_info=True) + return (False, _("Payment backend failure, please try again later.")) + return ( + res.result.code == "02500", # success ? + res.result.shortMessage + ": " + res.result.longMessage, + ) + + def get_wallet(self, wallet_id): + """Get wallet information from Payline.""" + try: + res = self.client.service.getWallet( + contractNumber=self.vad_number, walletId=wallet_id + ) + except WebFault: + logger.error("Payment backend failure", exc_info=True) + return (False, _("Payment backend failure, please try again later.")) + return ( + res.result.code == "02500", # success ? + getattr(res, "wallet", None), # None is needed because of suds + res.result.shortMessage + ": " + res.result.longMessage, + ) + + def make_wallet_payment(self, wallet_id, amount): + """Make a payment from the given wallet.""" + amount_cents = amount * 100 # use the smallest unit possible (cents) + payment = self.client.factory.create("ns1:payment") + payment.amount = amount_cents + payment.currency = 978 # euros + payment.action = 101 # authorization + validation = payment + payment.mode = "CPT" # CPT = comptant + payment.contractNumber = self.vad_number + order = self.client.factory.create("ns1:order") + order.ref = str(uuid4()) + order.amount = amount_cents + order.currency = 978 + order.date = datetime.now().strftime("%d/%m/%Y %H:%M") + try: + res = self.client.service.doImmediateWalletPayment( + payment=payment, order=order, walletId=wallet_id + ) + except WebFault: + logger.error("Payment backend failure", exc_info=True) + return (False, None, _("Payment backend failure, please try again later.")) + return ( + res.result.code == "00000", # success ? + res.transaction.id, + res.result.shortMessage + ": " + res.result.longMessage, + ) diff --git a/Wallet/serializers/__init__.py b/Wallet/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/serializers/customer.py b/Wallet/serializers/customer.py new file mode 100644 index 0000000..1f69204 --- /dev/null +++ b/Wallet/serializers/customer.py @@ -0,0 +1,29 @@ +from rest_framework import serializers +from rest_framework.response import Response + +from rest_framework_recursive.fields import RecursiveField +import os + +from Wallet.models import Transaction, Wallet + + +class WalletSerializer(serializers.ModelSerializer): + class Meta: + model = Wallet + fields = "__all__" + + + def create(self, validated_data): + w = Wallet.objects.create(**validated_data) + return w + + +class TransactionSerializer(serializers.ModelSerializer): + class Meta: + model = Transaction + fields = ["__all__"] + + + def create(self, validated_data): + t = Transaction.objects.create(**validated_data) + return t diff --git a/Wallet/tests.py b/Wallet/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Wallet/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Wallet/views.py b/Wallet/views.py new file mode 100644 index 0000000..218b4fd --- /dev/null +++ b/Wallet/views.py @@ -0,0 +1,133 @@ +from django.shortcuts import render + +from Wallet.models import Transaction, Wallet +from rest_framework import viewsets, generics, status, permissions + +# Create your views here. +from rest_framework.response import Response +from django.forms.models import model_to_dict + +from Wallet.serializers.customer import TransactionSerializer, WalletSerializer +from oauth2_provider.contrib.rest_framework import ( + TokenHasReadWriteScope, + OAuth2Authentication, +) +from oauth2_provider.models import AccessToken + + +class WalletViewSet(viewsets.ModelViewSet): + queryset = Wallet.objects.all() + serializer_class = WalletSerializer + permission_classes = [TokenHasReadWriteScope] + lookup_field = 'key' + lookup_url_kwarg = 'key' + + def list(self, request, *args, **kwargs): + if "key" in request.GET: + wallet_obj = Wallet.objects.get(key__exact=request.GET["key"]) + queryset = wallet_obj.category.all() + products = [x for x in queryset] + serializer = WalletSerializer(products, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "type" in request.GET: + wallet_obj = Wallet.objects.filter(user_id=request.user.id) + query = [x for x in wallet_obj] + serializer = WalletSerializer(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + queryset = Wallet.objects.all() + serializer = WalletSerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request, *args, **kwargs): + wallet = Wallet.objects.get(key__exact=request.data["key"]) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + w = serializer.create(validated_data=request.data) + + w.user = request.user + w.save() + + w_s = self.serializer_class(w) + return Response(w_s.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors) + + def retrieve(self, request, key=None, *args, **kwargs): + queryset = self.get_object() + serializer = self.serializer_class(queryset) + return Response(serializer.data, status=status.HTTP_200_OK) + + def update(self, request, pk=None, *args, **kwargs): + queryset = self.get_object() + + queryset.save() + serializer = self.serializer_class(queryset) + serializer.update(instance=queryset, validated_data=request.data) + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request, pk=None, *args, **kwargs): + pass + + def destroy(self, request, pk=None, *args, **kwargs): + queryset = self.get_object() + queryset.trash = True + queryset.save() + return Response(status=status.HTTP_200_OK) + + +class TransactionViewSet(viewsets.ModelViewSet): + queryset = Transaction.objects.all() + serializer_class = TransactionSerializer + permission_classes = [TokenHasReadWriteScope] + + def list(self, request, *args, **kwargs): + if "key" in request.GET: + transaction_obj = Transaction.objects.get(key__exact=request.GET["key"]) + queryset = transaction_obj.category.all() + transactions = [x for x in queryset] + serializer = self.serializer_class(transactions, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "type" in request.GET: + transactions_obj = Transaction.objects.filter(user_id=request.user.id) + query = [x for x in transactions_obj] + serializer = self.serializer_class(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + queryset = Wallet.objects.all() + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request, *args, **kwargs): + transaction_obj = Transaction.objects.get(key__exact=request.data["key"]) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + t = serializer.create(validated_data=request.data) + + t.user = request.user + t.save() + + t_s = self.serializer_class(t) + return Response(t_s.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors) + + def retrieve(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["key"]) + serializer = self.serializer_class(queryset) + return Response(serializer.data, status=status.HTTP_200_OK) + + def update(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["key"]) + + queryset.save() + serializer = self.serializer_class(queryset) + serializer.update(instance=queryset, validated_data=request.data) + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request, pk=None, *args, **kwargs): + pass + + def destroy(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["wallet_id"]) + queryset.trash = True + queryset.save() + return Response(status=status.HTTP_200_OK) diff --git a/__pycache__/manage.cpython-39.pyc b/__pycache__/manage.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea1fb0a529e1940f5fd5e70264700c59b248f6c8 GIT binary patch literal 818 zcmZuvO>0y!6ixEpd{}KMD8=rw5b6j%yAwpLL#MQrc3PZ~(xDLIOFCrwUQ&|0p)+<| zIQ|DWE_CV6-}APv{0k!J%}k43cp)br_vGf}Bz$pk0YN-`_bL5O5c&}&Hv`ttOK2_u z5U7U)79_(xJnKo1h)BdAQ7@9QsGJe|NJdkHksuw^sE(hu23+^enxm=7bFRhXOle6A zuQKIF^we0&MXt1R-g2*ol6vmWT=EkRL+v!+bp#LxUI0^rdAe<4A}SAx-aIS9$3 zJf6VuM@T0z@|DpZMiUI#6<-mRDZa!X$Pqe5qB_Cvv0j+>>u4QaB3~WUroa_dkX{?F zzAp6t#g!|qSIzeJ0qbmaySwe}4%