diff --git a/DJANGO-CRYPTOREADER/.gitignore b/DJANGO-CRYPTOREADER/.gitignore new file mode 100644 index 0000000..b7b620e --- /dev/null +++ b/DJANGO-CRYPTOREADER/.gitignore @@ -0,0 +1,140 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.idea +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +/static +/media +/Encryptor/SecurityAuthenticator.py +/Encryptor/sym.py +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +venv/ +ENV/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + + +#celery beat +celerybeat-schedule.dir +celerybeat-schedule.dat +celerybeat-schedule.bak + +/db +supervisor/supervisord.pid +supervisor/* \ No newline at end of file diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/__init__.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/asgi.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/asgi.py new file mode 100644 index 0000000..b366cba --- /dev/null +++ b/DJANGO-CRYPTOREADER/DjangoCryptoReader/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for DjangoCryptoReader 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/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoCryptoReader.settings') + +application = get_asgi_application() diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/celery.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/celery.py new file mode 100644 index 0000000..a96e0d4 --- /dev/null +++ b/DJANGO-CRYPTOREADER/DjangoCryptoReader/celery.py @@ -0,0 +1,17 @@ +# django_celery/celery.py + +import os +from celery import Celery +from django.conf import settings + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DjangoCryptoReader.settings") +app = Celery("DjangoCryptoReader") +app.config_from_object(settings, namespace="CELERY") + +app.conf.beat_schedule = { + 'track_order':{ + 'task':'order.tasks.refresh_orders', + 'schedule':30, + } +} +app.autodiscover_tasks() \ No newline at end of file diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/settings.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/settings.py new file mode 100644 index 0000000..675e0c1 --- /dev/null +++ b/DJANGO-CRYPTOREADER/DjangoCryptoReader/settings.py @@ -0,0 +1,138 @@ +""" +Django settings for DjangoCryptoReader project. + +Generated by 'django-admin startproject' using Django 4.1.1. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +from pathlib import Path +from decouple import config + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = config('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'account', + 'order', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'DjangoCryptoReader.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + '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 = 'DjangoCryptoReader.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.1/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/4.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +#celery config + +# BROKER_URL = 'redis://localhost:6379' +# CELERY_RESULT_BACKEND = 'redis://localhost:6379' +CELERY_BROKER_URL = 'http://localhost:6379' +CELERY_ACCEPT_CONTENT = ['application/json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'Asia/Tehran' \ No newline at end of file diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/urls.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/urls.py new file mode 100644 index 0000000..93e7677 --- /dev/null +++ b/DJANGO-CRYPTOREADER/DjangoCryptoReader/urls.py @@ -0,0 +1,23 @@ +"""DjangoCryptoReader URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/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('account/', include('account.urls')), + path('ordrer/', include('order.urls')), +] diff --git a/DJANGO-CRYPTOREADER/DjangoCryptoReader/wsgi.py b/DJANGO-CRYPTOREADER/DjangoCryptoReader/wsgi.py new file mode 100644 index 0000000..eccbe6e --- /dev/null +++ b/DJANGO-CRYPTOREADER/DjangoCryptoReader/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for DjangoCryptoReader 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/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoCryptoReader.settings') + +application = get_wsgi_application() diff --git a/DJANGO-CRYPTOREADER/README.md b/DJANGO-CRYPTOREADER/README.md new file mode 100644 index 0000000..1fb5bf9 --- /dev/null +++ b/DJANGO-CRYPTOREADER/README.md @@ -0,0 +1,30 @@ +# In name of Allah + +## Introduction +We want to develop a REST based django application to read signed in user's KuCoin open positions for them. For this to happen, you must be able to read user's positions (if requested from user) every 30 seconds. + +The application you develop must have these features: +- Users should be able to **sign up** (provide name, username & kucoin details). +- Users should be able to **sign in**. +- Users should be able to **request position tracking**. +- Users should be able to **see list** of current open positions. +- Application should be able to track user's positions every 30 seconds. +- Application should be able to handle multi users. +- Application must be able to resume its job after restart. + +You can use [this api](https://docs.kucoin.com/#list-accounts) from KuCoin to handle this. You should create KuCoin account if you do not have already & get api key & secret to test your implementation. Note we do not need your api key, that is only for your own usage. + +### Note +- We do **NOT want any kind of UI** from you +- KuCoin api key & secrets should not be stored in raw format + + +## Expectations: +We want a clean, readable and maintainable code with meaningful comments and docstrings. Also you need to provide postman API doc for web app. + +## Task +1. Fork this repository +2. Develop the challenge with Django 3 or higher +3. Push your code to your repository +4. Send us a pull request, we will review and get back to you +5. Enjoy diff --git a/DJANGO-CRYPTOREADER/account/__init__.py b/DJANGO-CRYPTOREADER/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/account/admin.py b/DJANGO-CRYPTOREADER/account/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/DJANGO-CRYPTOREADER/account/apps.py b/DJANGO-CRYPTOREADER/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'account' diff --git a/DJANGO-CRYPTOREADER/account/migrations/0001_initial.py b/DJANGO-CRYPTOREADER/account/migrations/0001_initial.py new file mode 100644 index 0000000..6a8d2e9 --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 4.1.1 on 2022-09-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('username', models.CharField(max_length=64, unique=True, verbose_name='user name')), + ('name', models.CharField(blank=True, max_length=64, null=True, verbose_name='name')), + ('last_login', models.DateTimeField(auto_now=True, verbose_name='update at')), + ('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='joined at')), + ('is_active', models.BooleanField(default=True)), + ('is_superuser', models.BooleanField(default=False)), + ('is_staff', models.BooleanField(default=False)), + ('kucoin_pass_pharese', models.CharField(max_length=256, verbose_name='pass phrase')), + ('kucoin_api_key', models.CharField(max_length=256, verbose_name='api key')), + ('kucoin_api_secret', models.CharField(max_length=256, verbose_name='api secret')), + ], + options={ + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + }, + ), + ] diff --git a/DJANGO-CRYPTOREADER/account/migrations/__init__.py b/DJANGO-CRYPTOREADER/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/account/models.py b/DJANGO-CRYPTOREADER/account/models.py new file mode 100644 index 0000000..8f1ecc7 --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/models.py @@ -0,0 +1,70 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager +from .utils.utility import encrypt_message, decrypt_message + + +class CustomManager(BaseUserManager): + def create_user(self, username, kucoin_pass_pharese, kucoin_api_key, kucoin_api_secret, password=None): + if not username: + raise ValueError("Username is Required.") + user = self.model( + username=username, + kucoin_pass_pharese=encrypt_message(kucoin_pass_pharese), + kucoin_api_key=encrypt_message(kucoin_api_key), + kucoin_api_secret=encrypt_message(kucoin_api_secret), + + ) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, username, kucoin_pass_pharese, kucoin_api_key, kucoin_api_secret, password=None): + if not username: + raise ValueError("Username is required.") + user = self.model( + username=username, + kucoin_pass_pharese=encrypt_message(kucoin_pass_pharese), + kucoin_api_key=encrypt_message(kucoin_api_key), + kucoin_api_secret=encrypt_message(kucoin_api_secret), + is_staff=True, + is_superuser=True, + + ) + user.set_password(password) + user.save(using=self._db) + return user + + +class User(AbstractBaseUser): + # base information + username = models.CharField( + max_length=64, unique=True, verbose_name='user name') + name = models.CharField(max_length=64, null=True, + blank=True, verbose_name='name') + + # extra information + last_login = models.DateTimeField(auto_now=True, verbose_name='update at') + joined_at = models.DateTimeField( + auto_now_add=True, verbose_name='joined at') + is_active = models.BooleanField(default=True) + is_superuser = models.BooleanField(default=False) + is_staff = models.BooleanField(default=False) + + # kucoin details + kucoin_pass_pharese = models.CharField( + max_length=256, verbose_name='pass phrase') + kucoin_api_key = models.CharField(max_length=256, verbose_name='api key') + kucoin_api_secret = models.CharField( + max_length=256, verbose_name='api secret') + + USERNAME_FIELD = 'username' + + objects = CustomManager() + + def __str__(self) -> str: + return f'user name = {self.username}' + + + class Meta: + verbose_name = 'User' + verbose_name_plural = 'Users' diff --git a/DJANGO-CRYPTOREADER/account/serializers.py b/DJANGO-CRYPTOREADER/account/serializers.py new file mode 100644 index 0000000..919194d --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/serializers.py @@ -0,0 +1,79 @@ +from .utils.utility import encrypt_message, decrypt_message +from rest_framework import serializers +from django.contrib.auth import authenticate +from rest_framework.exceptions import ValidationError +from .utils.utility import encrypt_message, decrypt_message +from .models import User + + +class RegisterSerializer(serializers.ModelSerializer): + + class Meta: + model = User + fields = ('username', 'password', 'kucoin_api_secret', + 'kucoin_api_key', 'kucoin_pass_pharese') + + def create(self, validated_data): + user = super().create({ + 'username': self.validated_data['username'], + 'kucoin_api_secret': encrypt_message(str(self.validated_data['kucoin_api_secret'])), + 'kucoin_api_key': encrypt_message(str(self.validated_data['kucoin_api_key'])), + 'kucoin_pass_pharese': encrypt_message(str(self.validated_data['kucoin_pass_pharese'])), + } + ) + user.set_password(validated_data['password']) + user.save() + return user + + def validate(self, attrs): + if not attrs.get('username'): + raise ValueError('username is required') + if not attrs.get('password'): + raise ValueError('password is required') + if not attrs.get('kucoin_api_secret'): + raise ValueError('kucoin_api_secret is required') + if not attrs.get('kucoin_api_key'): + raise ValueError('kucoin_api_key is required') + if not attrs.get('kucoin_pass_pharese'): + raise ValueError('kucoin_pass_pharese is required') + + return super().validate(attrs) + + + +class LoginSerializer(serializers.Serializer): + """ + This serializer defines two fields used for authentication: username and password. + It will try to authenticate the user with username/password when validated. + """ + username = serializers.CharField( + label="Username", + write_only=True + ) + password = serializers.CharField( + label="Password", + style={'input_type': 'password'}, # This will be used when the DRF browsable API is enabled + trim_whitespace=False, + write_only=True + ) + + def validate(self, attrs): + # Take username and password from request + username = attrs.get('username') + password = attrs.get('password') + + if username and password: + # Try to authenticate the user using Django auth framework. + user = authenticate(request=self.context.get('request'), + username=username, password=password) + if not user: + # If we don't have a regular user, raise a ValidationError + msg = 'Access denied: wrong username or password.' + raise serializers.ValidationError(msg, code='authorization') + else: + msg = 'Both "username" and "password" are required.' + raise serializers.ValidationError(msg, code='authorization') + # We have a valid user, put it in the serializer's validated_data. + # It will be used in the view. + attrs['user'] = user + return attrs diff --git a/DJANGO-CRYPTOREADER/account/tests.py b/DJANGO-CRYPTOREADER/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/DJANGO-CRYPTOREADER/account/urls.py b/DJANGO-CRYPTOREADER/account/urls.py new file mode 100644 index 0000000..ee9a0e0 --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from .views import RegisterView, LoginView + +urlpatterns = [ + path('sign-up/', RegisterView.as_view({'post': 'create'})), + path('sign-in/', LoginView.as_view()), +] diff --git a/DJANGO-CRYPTOREADER/account/utils/random-key.txt b/DJANGO-CRYPTOREADER/account/utils/random-key.txt new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/account/utils/utility.py b/DJANGO-CRYPTOREADER/account/utils/utility.py new file mode 100644 index 0000000..f076703 --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/utils/utility.py @@ -0,0 +1,43 @@ +import re +from cryptography.fernet import Fernet +from decouple import config + + +def generate_key(): + """ + Generates a key and save it into a file + """ + + key = Fernet.generate_key() + # with open("random-key.txt", "wb") as key_file: + # key_file.write(key) + + +def load_key(): + """ + Load the previously generated key + """ + return config('key') + + +def encrypt_message(message): + """ + Encrypts a message + """ + key = load_key() + f = Fernet(key) + encrypted_message = f.encrypt(bytes(message, encoding='utf8')) + + # print(encrypted_message.decode("utf-8")) + return encrypted_message.decode("utf-8") + + +def decrypt_message(encrypted_message): + """ + Decrypts an encrypted message + """ + key = load_key() + f = Fernet(key) + decrypted_message = f.decrypt(bytes(encrypted_message, encoding='utf8')) + # print(decrypted_message.decode("utf-8")) + return decrypted_message.decode("utf-8") diff --git a/DJANGO-CRYPTOREADER/account/views.py b/DJANGO-CRYPTOREADER/account/views.py new file mode 100644 index 0000000..7ade12e --- /dev/null +++ b/DJANGO-CRYPTOREADER/account/views.py @@ -0,0 +1,39 @@ +from rest_framework.response import Response +from rest_framework.generics import CreateAPIView +from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet +from .models import User +from django.contrib.auth import login +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_202_ACCEPTED +from .serializers import RegisterSerializer, LoginSerializer +from rest_framework.exceptions import NotFound + + +class RegisterView(ModelViewSet): + + def get_queryset(self): + try: + user = User.objects.get(username=self.request.user.username) + except User.DoesNotExist: + raise NotFound('User not found.') + + def get_serializer_class(self): + if self.action in ['create']: + return RegisterSerializer + + def perform_create(self, serializer): + serializer = serializer.save(data=self.request.data) + return serializer + + +class LoginView(APIView): + # This view should be accessible also for unauthenticated users. + # permission_classes = (permissions.AllowAny,) + + def post(self, request, format=None): + serializer = LoginSerializer(data=self.request.data, context={ + 'request': self.request}) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + login(request, user) + return Response(None, status=HTTP_202_ACCEPTED) diff --git a/DJANGO-CRYPTOREADER/manage.py b/DJANGO-CRYPTOREADER/manage.py new file mode 100644 index 0000000..7be8b6e --- /dev/null +++ b/DJANGO-CRYPTOREADER/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoCryptoReader.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/DJANGO-CRYPTOREADER/order/__init__.py b/DJANGO-CRYPTOREADER/order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/order/admin.py b/DJANGO-CRYPTOREADER/order/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/DJANGO-CRYPTOREADER/order/apps.py b/DJANGO-CRYPTOREADER/order/apps.py new file mode 100644 index 0000000..42888e4 --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OrderConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'order' diff --git a/DJANGO-CRYPTOREADER/order/migrations/0001_initial.py b/DJANGO-CRYPTOREADER/order/migrations/0001_initial.py new file mode 100644 index 0000000..df27e12 --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.1 on 2022-09-23 12:13 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('symbol', models.CharField(blank=True, max_length=64, null=True, verbose_name='symbol')), + ('autoDeposit', models.BooleanField(default=False, verbose_name='auto deposit ?')), + ('isOpen', models.BooleanField(default=False, verbose_name='is open ?')), + ], + options={ + 'verbose_name': 'Order', + 'verbose_name_plural': 'Orders', + }, + ), + ] diff --git a/DJANGO-CRYPTOREADER/order/migrations/0002_orderdetail.py b/DJANGO-CRYPTOREADER/order/migrations/0002_orderdetail.py new file mode 100644 index 0000000..fb510b5 --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/migrations/0002_orderdetail.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0.7 on 2022-09-24 07:25 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='OrderDetail', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('symbol', models.CharField(blank=True, max_length=64, null=True, verbose_name='symbol')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), + ], + options={ + 'verbose_name': 'OrderDetail', + 'verbose_name_plural': 'OrdersDetail', + }, + ), + ] diff --git a/DJANGO-CRYPTOREADER/order/migrations/__init__.py b/DJANGO-CRYPTOREADER/order/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DJANGO-CRYPTOREADER/order/models.py b/DJANGO-CRYPTOREADER/order/models.py new file mode 100644 index 0000000..7ecdf44 --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/models.py @@ -0,0 +1,43 @@ + +from django.db import models +import uuid + + +class Order(models.Model): + """ + based on this docs: just define some fileds not all of them + https://docs.kucoin.com/futures/#get-position-list + """ + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + symbol = models.CharField(max_length=64, null=True, + blank=True, verbose_name='symbol') + autoDeposit = models.BooleanField( + default=False, verbose_name='auto deposit ?') + isOpen = models.BooleanField(default=False, verbose_name='is open ?') + + def __str__(self) -> str: + return f'id = {self.id} , symbol = {self.symbol}' + + class Meta: + verbose_name = 'Order' + verbose_name_plural = 'Orders' + + +class OrderDetail(models.Model): + """ + based on this docs:just define osme fileds not all of them + https://docs.kucoin.com/#list-orders + """ + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + symbol = models.CharField(max_length=64, null=True, + blank=True, verbose_name='symbol') + created_at = models.DateTimeField( + auto_now_add=True, verbose_name='created at') + + def __str__(self) -> str: + return f'id = {self.id} , symbol = {self.symbol}' + + class Meta: + verbose_name = 'OrderDetail' + verbose_name_plural = 'OrdersDetail' diff --git a/DJANGO-CRYPTOREADER/order/serializers.py b/DJANGO-CRYPTOREADER/order/serializers.py new file mode 100644 index 0000000..8b3640e --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/serializers.py @@ -0,0 +1,10 @@ +from pyexpat import model +from .models import Order +from rest_framework.serializers import ModelSerializer + + +class OpenOrderSerializer(ModelSerializer): + + class Meta: + model = Order + fields = '__all__' diff --git a/DJANGO-CRYPTOREADER/order/tasks.py b/DJANGO-CRYPTOREADER/order/tasks.py new file mode 100644 index 0000000..2bd264a --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/tasks.py @@ -0,0 +1,26 @@ +from django.conf import settings +from django.core.cache import cache +from django.core.cache.backends.base import DEFAULT_TIMEOUT +from account.models import User +from .models import OrderDetail +from celery import shared_task +from utils.utility import CallAPI + + +@shared_task +def refresh_orders(): + """ + Just track active order + """ + users = User.objects.all() + for user in users: + obj = OrderDetail() + try: + active_orders = CallAPI( + (user, '/api/v1/positions', 'GET', {'status': 'active'}, True)) + active_order_response = active_orders.create_request() + obj.id = active_order_response['id'] + obj.symbol = active_order_response['symbol'] + obj.save() + except Exception as e: + raise Exception(e) diff --git a/DJANGO-CRYPTOREADER/order/tests.py b/DJANGO-CRYPTOREADER/order/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/DJANGO-CRYPTOREADER/order/urls.py b/DJANGO-CRYPTOREADER/order/urls.py new file mode 100644 index 0000000..b44356b --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/urls.py @@ -0,0 +1,9 @@ +from django.urls import path, include +from .views import OpenOrderView + +urlpatterns = [ + path('open-position/', + OpenOrderView.as_view({'post': 'create', 'get': 'list'})), + # path('list-order/', OrderView.as_view({'post': 'create', 'get': 'list'})), + +] diff --git a/DJANGO-CRYPTOREADER/order/utils/utility.py b/DJANGO-CRYPTOREADER/order/utils/utility.py new file mode 100644 index 0000000..c33a2b3 --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/utils/utility.py @@ -0,0 +1,64 @@ +import time +import base64 +import hashlib +import hmac +import time +import urllib.parse +import requests +from account.utils.utility import encrypt_message + + +class CallAPI(): + """ + every thing base on this documentation: + https://www.kucoin.com/support/900006465403-KuCoin-API-key-upgrade-operation-guide + """ + + def __init__(self, user, endpoint, http_method, query_params, sandbox): + self.user = user + self.endpoint = endpoint + self.http_method = http_method + self.query_params = query_params + self.sandbox = sandbox + + def create_signature(self): + """ + Use API-Secret to encrypt the prehash string + {timestamp + method + endpoint + body} with sha256 HMAC. + The request body is a JSON string and needs to be the same as the parameters passed by the API + """ + api_secret = encrypt_message(self.user.get('kucoin_api_secret')) + str_to_sign = str(time.time() * 1000) + self.method + \ + self.endpoint + '?' + urllib.parse.urlencode(self.params) + signature = base64.b64encode( + hmac.new(api_secret.encode('utf-8'), str_to_sign.encode('utf-8'), hashlib.sha256).digest()) + return signature + + def create_header(self): + """ + All private REST requests must contain the following headers: + KC-API-KEY The API key as a string. + KC-API-SIGN The base64-encoded signature (see Signing a Message). + KC-API-TIMESTAMP A timestamp for your request. + KC-API-PASSPHRASE The passphrase you specified when creating the API key. + KC-API-KEY-VERSION You can check the version of API key on the page of API Management + + """ + return { + "KC-API-SIGN": self.create_signature(), + "KC-API-TIMESTAMP": str(int(time.time() * 1000)), + "KC-API-KEY": encrypt_message(self.user.get('kucoin_api_key')), + "KC-API-PASSPHRASE": encrypt_message(self.user.get('kucoin_pass_pharese')), + "KC-API-KEY-VERSION": "2" + } + + def create_request(self): + if self.sandbox: + base_url = "https://openapi-sandbox.kucoin.com" + self.endpoint + base_url = "https://api.kucoin.com" + self.endpoint + + response = requests.request( + self.http_method, base_url, headers=self.create_header(), params=self.params) + if response.status_code != 200: + raise Exception('Error Happned') + return response.json() diff --git a/DJANGO-CRYPTOREADER/order/views.py b/DJANGO-CRYPTOREADER/order/views.py new file mode 100644 index 0000000..ca008aa --- /dev/null +++ b/DJANGO-CRYPTOREADER/order/views.py @@ -0,0 +1,30 @@ +from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK +from rest_framework.viewsets import ModelViewSet +from .serializers import OpenOrderSerializer +from .models import Order +from .utils.utility import CallAPI +from account.models import User +from rest_framework.exceptions import NotFound + + +class OpenOrderView(ModelViewSet): + + def get_queryset(self): + return Order.objects.filter(isOpen=True) + + def get_serializer_class(self): + if self.action in ['create']: + return OpenOrderSerializer + + def perform_create(self, serializer): + # data = self.request.data + user = self.request.user + data = CallAPI(user, '/api/v1/positions', 'GET', None, True) + data.create_request() + + if data: + serializer = serializer.save(data=data) + # return serializer + return Response('done', status=HTTP_200_OK) + return Response('error') diff --git a/DJANGO-CRYPTOREADER/requirements.txt b/DJANGO-CRYPTOREADER/requirements.txt new file mode 100644 index 0000000..9d422ae Binary files /dev/null and b/DJANGO-CRYPTOREADER/requirements.txt differ