diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..279594a --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,132 @@ +# 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 +env/ +static/ + +# 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 + +# 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 +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +node_modules + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..7f489f2 --- /dev/null +++ b/backend/README.md @@ -0,0 +1 @@ +# It is a Django API for Ugra-Hackaton 2020 diff --git a/backend/Ugra_hackaton/__init__.py b/backend/Ugra_hackaton/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/Ugra_hackaton/asgi.py b/backend/Ugra_hackaton/asgi.py new file mode 100644 index 0000000..a88388a --- /dev/null +++ b/backend/Ugra_hackaton/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for Ugra_hackaton 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.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Ugra_hackaton.settings') + +application = get_asgi_application() diff --git a/backend/Ugra_hackaton/settings.py b/backend/Ugra_hackaton/settings.py new file mode 100644 index 0000000..01121a8 --- /dev/null +++ b/backend/Ugra_hackaton/settings.py @@ -0,0 +1,149 @@ +""" +Django settings for Ugra_hackaton project. + +Generated by 'django-admin startproject' using Django 3.1.3. + +For more information on this file, see +https://docs.djangoproject.com/en/3.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.1/ref/settings/ +""" + +from pathlib import Path + +# 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/3.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'cn0m$w9+&hn7eus%m2z3m-=9!fz+!*#y_c7lb(4nax^51gfajz' + +# 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', + 'rest_framework.authtoken', + 'back', +] + +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', +] + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + ] +} + +ROOT_URLCONF = 'Ugra_hackaton.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 = 'Ugra_hackaton.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.1/ref/settings/#databases + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', +# } +# } + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'ugra_hackaton', + 'USER': 'postgres', + 'PASSWORD': 'zo.h0906023', + 'HOST': '127.0.0.1', + 'PORT': '5432', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.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/3.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.1/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'static' + + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = 'artemss20030906@gmail.com' +EMAIL_HOST_PASSWORD = 'hbqgvdsqhvkuijuc' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True diff --git a/backend/Ugra_hackaton/urls.py b/backend/Ugra_hackaton/urls.py new file mode 100644 index 0000000..2d0ce46 --- /dev/null +++ b/backend/Ugra_hackaton/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('back.urls')), +] diff --git a/backend/Ugra_hackaton/wsgi.py b/backend/Ugra_hackaton/wsgi.py new file mode 100644 index 0000000..393fac9 --- /dev/null +++ b/backend/Ugra_hackaton/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for Ugra_hackaton 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.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Ugra_hackaton.settings') + +application = get_wsgi_application() diff --git a/backend/back/__init__.py b/backend/back/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/back/admin.py b/backend/back/admin.py new file mode 100644 index 0000000..7dadec1 --- /dev/null +++ b/backend/back/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from . import models + +admin.site.register(models.Profile) +# Register your models here. diff --git a/backend/back/apps.py b/backend/back/apps.py new file mode 100644 index 0000000..1856dbe --- /dev/null +++ b/backend/back/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BackConfig(AppConfig): + name = 'back' diff --git a/backend/back/migrations/0001_initial.py b/backend/back/migrations/0001_initial.py new file mode 100644 index 0000000..470bb9a --- /dev/null +++ b/backend/back/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.3 on 2020-11-28 08:03 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('points', models.DecimalField(decimal_places=0, default=0, max_digits=7)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/back/migrations/__init__.py b/backend/back/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/back/models.py b/backend/back/models.py new file mode 100644 index 0000000..73ceebc --- /dev/null +++ b/backend/back/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete = models.CASCADE) + points = models.DecimalField(max_digits=7, decimal_places=0, default = 0) + +@receiver(post_save, sender = User) +def create_profile(sender, instance, created, **kwargs): + if(created): + Profile.objects.create(user = instance) \ No newline at end of file diff --git a/backend/back/serializers.py b/backend/back/serializers.py new file mode 100644 index 0000000..926f84a --- /dev/null +++ b/backend/back/serializers.py @@ -0,0 +1,13 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from . import models + +class UserRegisterSerializer(serializers.ModelSerializer): + email = serializers.EmailField(required=True) + class Meta: + model = User + fields = [ + 'username', + 'password', + 'email' + ] \ No newline at end of file diff --git a/backend/back/tests.py b/backend/back/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/back/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/back/urls.py b/backend/back/urls.py new file mode 100644 index 0000000..4278d80 --- /dev/null +++ b/backend/back/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from rest_framework.authtoken.views import obtain_auth_token +from . import views + +urlpatterns = [ + path('register', views.UserRegisterAPIView, name = 'UserRegister'), + path('login', obtain_auth_token, name = 'UserLogin'), + path('logout', views.UserLogoutAPIView, name = 'UserLogout'), + path('activate//', views.UserActivationAPIView, name = 'UserActivation'), +] diff --git a/backend/back/views.py b/backend/back/views.py new file mode 100644 index 0000000..7f4a236 --- /dev/null +++ b/backend/back/views.py @@ -0,0 +1,70 @@ +from django.shortcuts import render +from rest_framework import permissions, status, generics +from rest_framework.response import Response +from rest_framework.authtoken.models import Token +from django.contrib.auth.models import User +from rest_framework.decorators import api_view, permission_classes +from django.contrib.sites.shortcuts import get_current_site +from django.utils.encoding import force_bytes, force_text +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.template.loader import render_to_string +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.core.mail import EmailMultiAlternatives +from .serializers import UserRegisterSerializer +from django.utils.html import strip_tags +import six +import threading + +class EmailTokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)) + +email_token_gen = EmailTokenGenerator() + +@api_view(['POST']) +@permission_classes([~permissions.IsAuthenticated]) +def UserRegisterAPIView(request): + serializer = UserRegisterSerializer(data = request.data) + if(serializer.is_valid()): + data = serializer.validated_data + if(User.objects.filter(username = data['username']).exists()): + return Response({'ok':False, 'error':'User already exists'}, status = status.HTTP_400_BAD_REQUEST) + user = User.objects.create(username = data['username'], email = data['email'], is_active = False) + user.set_password(data['password']) + user.save() + domain = get_current_site(request).domain + mail_subject = 'Account activation' + message = render_to_string('email_activtion.html',{ + 'user':user, + 'domain': domain, + 'uidb64': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': email_token_gen.make_token(user), + }) + message_task = threading.Thread(target = async_email_send, args=( mail_subject, message, [data['email']] )) + message_task.start() + return Response({'ok':True}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':serializer.errors},status = status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([permissions.AllowAny]) +def UserActivationAPIView(request, uidb64, token): + uid = force_text(urlsafe_base64_decode(uidb64)) + user = User.objects.filter(id = uid) + if(user.exists()): + user = user.first() + if(email_token_gen.check_token(user, token) and not user.is_active): + user.is_active = True + user.save() + return Response({'ok':True}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'Invalid activation link'}, status = status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([permissions.IsAuthenticated]) +def UserLogoutAPIView(request): + Token.objects.get(user = request).delete() + return Response(status = status.HTTP_200_OK) + +def async_email_send(mail_subject, message, to_email): + mail_to_send = EmailMultiAlternatives(mail_subject, strip_tags(message), to=to_email) + mail_to_send.attach_alternative(message, 'text/html') + mail_to_send.send() \ No newline at end of file diff --git a/backend/manage.py b/backend/manage.py new file mode 100644 index 0000000..40aff44 --- /dev/null +++ b/backend/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', 'Ugra_hackaton.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/backend/templates/email_activtion.html b/backend/templates/email_activtion.html new file mode 100644 index 0000000..935b556 --- /dev/null +++ b/backend/templates/email_activtion.html @@ -0,0 +1,5 @@ +{% autoescape off %} +

HI {{ user.username }}

+

Please click on the link to confirm your registration:

+

Confirm Email

+{% endautoescape %} \ No newline at end of file