From 3f474378229baf4e64fed5ee2252c88ed87df5d8 Mon Sep 17 00:00:00 2001 From: mi1468 Date: Sun, 13 Apr 2025 11:58:48 +0330 Subject: [PATCH] auth and simple info --- .gitignore | 89 ++ .gitlab-ci.yml | 39 + Dockerfile | 19 + REDME.md | 7 + core/__init__.py | 0 core/admin.py | 11 + core/asgi.py | 16 + core/migrations/0001_initial.py | 47 ++ ...002_alter_customer_profile_glb_and_more.py | 23 + core/migrations/__init__.py | 0 core/models/AssignedRule.py | 9 + core/models/customer.py | 23 + core/models/role.py | 6 + core/serializers/CustomerSerializer.py | 7 + core/serializers/UserSerializer.py | 8 + core/serializers/__init__.py | 0 core/settings.py | 161 ++++ .../emails/verification_email_template.html | 16 + .../emails/verification_email_template2.html | 764 ++++++++++++++++++ core/tests/__init__.py | 0 core/tests/test_signup.py | 57 ++ core/urls.py | 31 + core/views.py | 58 ++ core/views/__init__.py | 0 core/views/userView.py | 353 ++++++++ core/wsgi.py | 16 + docker-compose.yml | 30 + manage.py | 22 + requirements.txt | 7 + server/views.py | 49 ++ test.rest | 71 ++ 31 files changed, 1939 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 Dockerfile create mode 100644 REDME.md create mode 100644 core/__init__.py create mode 100644 core/admin.py create mode 100644 core/asgi.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_alter_customer_profile_glb_and_more.py create mode 100644 core/migrations/__init__.py create mode 100644 core/models/AssignedRule.py create mode 100644 core/models/customer.py create mode 100644 core/models/role.py create mode 100644 core/serializers/CustomerSerializer.py create mode 100644 core/serializers/UserSerializer.py create mode 100644 core/serializers/__init__.py create mode 100644 core/settings.py create mode 100644 core/templates/emails/verification_email_template.html create mode 100644 core/templates/emails/verification_email_template2.html create mode 100644 core/tests/__init__.py create mode 100644 core/tests/test_signup.py create mode 100644 core/urls.py create mode 100644 core/views.py create mode 100644 core/views/__init__.py create mode 100644 core/views/userView.py create mode 100644 core/wsgi.py create mode 100644 docker-compose.yml create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 server/views.py create mode 100644 test.rest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..621ddc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Created by https://www.gitignore.io + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# 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/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + + +### Django ### +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py + +.env +db.sqlite3 \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..d29904c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,39 @@ +stages: + - test + - deploy + +before_script: + - apk update + - apk add pkgconfig + - apk add mariadb-connector-c-dev build-base + + +test: + stage: test + image: python:3.9-alpine + script: + - pip install --upgrade pip + - pip install django + - pip install -r requirements.txt + - pip install python-dotenv + - python manage.py test + +deploy: + stage: deploy + image: python:3.9-alpine + before_script: + - apk add --no-cache openssh-client + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | ssh-add - + - ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER_IP echo "SSH-connection successful." + script: + - mkdir deploy + - cp -r core deploy/ + - cp -r server deploy/ + - cp manage.py requirements.txt test.rest docker-compose.yml Dockerfile deploy/ + - ls deploy/ + - scp -r deploy $SSH_USER@$SERVER_IP:$DEPLOY_PATH/ + - ssh $SSH_USER@$SERVER_IP "cd $DEPLOY_PATH/deploy && docker compose down" + - ssh $SSH_USER@$SERVER_IP "cd $DEPLOY_PATH/deploy && docker compose up -d" + only: + - main \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..476a243 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.9 + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 +ENV PYTHONPATH=/code:/code/CLINET_APPS + +# Set work directory +WORKDIR /code + +# Update pip +RUN pip install --upgrade pip + +# Install dependencies +COPY requirements.txt /code/ +RUN pip install -r requirements.txt + +# Copy project +COPY . /code/ \ No newline at end of file diff --git a/REDME.md b/REDME.md new file mode 100644 index 0000000..debad18 --- /dev/null +++ b/REDME.md @@ -0,0 +1,7 @@ +python3 manage.py runserver 0.0.0.0:8000 + +pm2 start "python3 manage.py runserver 0.0.0.0:8000 " --name "serverDjango" + +pm2 start "npm run dev -- --host 0.0.0.0 " --name "vue" + +npm run dev -- --host 0.0.0.0 diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..424f49f --- /dev/null +++ b/core/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from core.models.customer import Customer +from core.models.role import Role +# from .model import user +from core.models.AssignedRule import AssignedRule + + +admin.site.register(Role) +admin.site.register(AssignedRule) +admin.site.register(Customer) diff --git a/core/asgi.py b/core/asgi.py new file mode 100644 index 0000000..a6b478a --- /dev/null +++ b/core/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for server 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.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_asgi_application() diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..6984443 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 5.0 on 2025-04-12 14:25 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ], + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile_img', models.CharField(max_length=255)), + ('profile_glb', models.CharField(max_length=255)), + ('mobile_number', models.CharField(max_length=15)), + ('verification_sms_code', models.CharField(blank=True, max_length=6)), + ('verification_email_code', models.CharField(blank=True, max_length=6)), + ('is_sms_verified', models.BooleanField(default=False)), + ('is_email_verified', models.BooleanField(default=False)), + ('sms_time_for_valid', models.DateTimeField(blank=True, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='AssignedRule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.role')), + ], + ), + ] diff --git a/core/migrations/0002_alter_customer_profile_glb_and_more.py b/core/migrations/0002_alter_customer_profile_glb_and_more.py new file mode 100644 index 0000000..b8c7b08 --- /dev/null +++ b/core/migrations/0002_alter_customer_profile_glb_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0 on 2025-04-12 14:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customer', + name='profile_glb', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='customer', + name='profile_img', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models/AssignedRule.py b/core/models/AssignedRule.py new file mode 100644 index 0000000..7f2b8d0 --- /dev/null +++ b/core/models/AssignedRule.py @@ -0,0 +1,9 @@ +# models.py +from django.contrib.auth.models import User +from .role import Role +from django.db import models + +class AssignedRule(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + role = models.ForeignKey(Role, on_delete=models.CASCADE) + # Add any other fields you need diff --git a/core/models/customer.py b/core/models/customer.py new file mode 100644 index 0000000..db1d551 --- /dev/null +++ b/core/models/customer.py @@ -0,0 +1,23 @@ + + +from django.db import models +from django.contrib.auth.models import User + + +class Customer(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + + + mobile_number = models.CharField(max_length=15) # Adjust max length as per your needs + profile_img = models.CharField(max_length=255,blank=True) # Adjust max length as per your needs + profile_glb = models.CharField(max_length=255,blank=True) # Adjust max length as per your needs + + verification_sms_code = models.CharField(blank=True,max_length=6) # Adjust max length as per your needs + verification_email_code = models.CharField(blank=True,max_length=6) # Adjust max length as per your needs + + is_sms_verified = models.BooleanField(default=False) + is_email_verified = models.BooleanField(default=False) + sms_time_for_valid = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return self.user.username \ No newline at end of file diff --git a/core/models/role.py b/core/models/role.py new file mode 100644 index 0000000..b931b79 --- /dev/null +++ b/core/models/role.py @@ -0,0 +1,6 @@ +from django.db import models + +class Role(models.Model): + name = models.CharField(max_length=100, unique=True) + + diff --git a/core/serializers/CustomerSerializer.py b/core/serializers/CustomerSerializer.py new file mode 100644 index 0000000..e6281ff --- /dev/null +++ b/core/serializers/CustomerSerializer.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from ..models.customer import Customer + +class CustomerSerializer(serializers.ModelSerializer): + class Meta: + model = Customer + fields = '__all__' diff --git a/core/serializers/UserSerializer.py b/core/serializers/UserSerializer.py new file mode 100644 index 0000000..5abea61 --- /dev/null +++ b/core/serializers/UserSerializer.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +# from ..models.user import User +from django.contrib.auth.models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'username', 'email' , 'password' ] diff --git a/core/serializers/__init__.py b/core/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/settings.py b/core/settings.py new file mode 100644 index 0000000..78425cc --- /dev/null +++ b/core/settings.py @@ -0,0 +1,161 @@ +""" +Django settings for server project. + +Generated by 'django-admin startproject' using Django 4.2. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path +import os +from dotenv import load_dotenv +import sys + +# Load environment variables from .env file +load_dotenv() +# 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.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-q1-xt8(+yr^6iye@sa3@miqn&(#-be96ild1s!o)wlmwqrzd3-' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +# ALLOWED_HOSTS = ['0.0.0.0'] +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', + 'core', + 'corsheaders' + + +] + +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', + 'corsheaders.middleware.CorsMiddleware', + +] +CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect + +ROOT_URLCONF = 'core.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 = 'core.wsgi.application' + + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +if 'test' in sys.argv: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'core1', + 'USER': 'root', + 'PASSWORD': '', + 'HOST': 'localhost', + 'PORT': '3306', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.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/4.2/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.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.ionos.de' +EMAIL_PORT = 465 +EMAIL_USE_SSL = True +EMAIL_HOST_USER = 'mail@clinet.club' +EMAIL_HOST_PASSWORD = 'uzudzsd78786d7asd56gasdbsad' +DEFAULT_FROM_EMAIL = 'mail@clinet.club' + diff --git a/core/templates/emails/verification_email_template.html b/core/templates/emails/verification_email_template.html new file mode 100644 index 0000000..2825d19 --- /dev/null +++ b/core/templates/emails/verification_email_template.html @@ -0,0 +1,16 @@ + + + + + + +

Hallo, Willkommen bei WZK. Ihr E-Mail-Bestätigungscode lautet:

+

{{ verification_code }}

+ + diff --git a/core/templates/emails/verification_email_template2.html b/core/templates/emails/verification_email_template2.html new file mode 100644 index 0000000..d057067 --- /dev/null +++ b/core/templates/emails/verification_email_template2.html @@ -0,0 +1,764 @@ + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + +
+ +
+ + + + + + +
+
+
+
+ +
+
+ + +
+
+ + + + + + + +
+ +
+ +
+ +
+ + +
+ + + + + + +
+ +
+ + + + + + + + + +
+
+

+ +

+ +

+ WZK Bestätigungscode, +

+

+ Hallo, Willkommen bei WZK. Ihr + E-Mail-Bestätigungscode lautet +

+

+ Before we get started, we'll need to verify your + email. +

+
+
+ + + + + + +
+

+ {{ verification_code }} +

+
+
+
+
+ +
+
+ + +
+
+ + + + + + +
+ +
+ + + + + + +
+
+   +
+
+
+ +
+
+ + +
+ + + + + + +
+ +
+ + + + + + + + + +
+
+ FUN FACT #16 +
+
+
+ In Hearthstone, using the Hunter card Animal Companion + against Kel'Thuzad will summon his cat Mr. + Bigglesworth rather than the usual beasts. +
+
+
+ +
+
+ + +
+ + + + + + +
+ +
+ + + + + + + + + +
+
+ Sent by Discord • + check our blog + • + @discordapp +
+
+
+ 444 De Haro Street, Suite 200, San Francisco, CA 94107 +
+
+
+ +
+
+ +
+ diff --git a/core/tests/__init__.py b/core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/tests/test_signup.py b/core/tests/test_signup.py new file mode 100644 index 0000000..01cdc16 --- /dev/null +++ b/core/tests/test_signup.py @@ -0,0 +1,57 @@ +from django.urls import reverse +from rest_framework.test import APITestCase, APIClient +from rest_framework import status +from django.contrib.auth.models import User +from rest_framework.authtoken.models import Token + +class PatientSignUpTest(APITestCase): + def setUp(self): + self.client = APIClient() + self.signup_url = reverse('signup') # Ensure you have named your URL in urls.py + + # def test_signup_valid(self): + # """ + # Ensure we can create a new user and patient with valid data. + # """ + # data = { + # 'fallnumber': '123', + # 'username': 'newuser2', + # 'password': 'newpassword123', + # 'email': 'user2@example.com', + # 'birthday': '1990-10-10', + # 'mobile_number': '1234567890' + # } + # response = self.client.post(self.signup_url, data, format='json') + # self.assertEqual(response.status_code, status.HTTP_201_CREATED) + # self.assertIn('token', response.data) + # self.assertEqual(User.objects.count(), 1) + # self.assertEqual(Token.objects.count(), 1) + + def test_signup_invalid_user_data(self): + """ + Ensure user data is validated. + """ + data = { + 'username': 'newuser', # Missing password and email + 'birthday': '2000-01-01', + 'mobile_number': '1234567890' + } + response = self.client.post(self.signup_url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(User.objects.count(), 0) + + def test_signup_invalid_patient_data(self): + """ + Ensure patient data is validated and no user is created if patient data is invalid. + """ + data = { + 'username': 'newuser', + 'password': 'newpassword123', + 'email': 'user@example.com', + 'birthday': 'not-a-date', # Invalid date format + 'mobile_number': '1234567890' + } + response = self.client.post(self.signup_url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(User.objects.count(), 0) + diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..88e78ea --- /dev/null +++ b/core/urls.py @@ -0,0 +1,31 @@ +from django.urls import re_path +from django.contrib import admin +from django.urls import path + +from .views import userView +from django.urls import include, path + +urlpatterns = [ + + path('admin/', admin.site.urls), + + + # re_path(r'^signup/$', userView.signup, name="signup"), + + re_path('signup', userView.signup , name="signup"), + re_path('login', userView.login), + re_path('getInfo', userView.getInfo), + + re_path('sendSmsVerification', userView.sendSmsVerification), + re_path('sendEmailVerification', userView.sendEmailVerification), + re_path('submitEmailVerification', userView.submitEmailVerification), + re_path('submitSmsVerification', userView.submitSmsVerification), + + re_path('sendForgetPasswordCode', userView.sendForgetPasswordCode), + re_path('sendCodeAndNewPassword', userView.sendCodeAndNewPassword), + + + + + +] diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..cb89600 --- /dev/null +++ b/core/views.py @@ -0,0 +1,58 @@ +from django.contrib.auth import get_user_model +from rest_framework.authtoken.models import Token +from rest_framework.decorators import api_view, authentication_classes, permission_classes +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status + +# from .models.user import User +from django.contrib.auth.models import User + +from .serializers.UserSerializer import UserSerializer + +# from .serializers.user import UserSerializer + + + +# utils.py +from .models.AssignedRule import AssignedRule + +def user_has_role(user, role_name): + return AssignedRule.objects.filter(user=user, role__name=role_name).exists() + + + +@api_view(['POST']) +def signup(request): + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + user = serializer.save() + user.set_password(request.data['password']) + user.save() + token = Token.objects.create(user=user) + return Response({'token': token.key, 'user': serializer.data}) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +def login(request): + try: + user = get_user_model().objects.get(email=request.data['email']) + except get_user_model().DoesNotExist: + return Response("User not found", status=status.HTTP_404_NOT_FOUND) + + if not user.check_password(request.data['password']): + return Response("Invalid password", status=status.HTTP_401_UNAUTHORIZED) + + token, created = Token.objects.get_or_create(user=user) + serializer = UserSerializer(user) + return Response({'token': token.key, 'user': serializer.data}) + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def test_token(request): + if not user_has_role(request.user, 'admin'): + return Response({'message': 'No access'}, status=status.HTTP_403_FORBIDDEN) + + return Response({'message': 'User has admin role'}) diff --git a/core/views/__init__.py b/core/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/views/userView.py b/core/views/userView.py new file mode 100644 index 0000000..a7086b1 --- /dev/null +++ b/core/views/userView.py @@ -0,0 +1,353 @@ +from pickle import TRUE +import random +from datetime import datetime, timedelta + + +from django.contrib.auth import get_user_model +from rest_framework.authtoken.models import Token +from rest_framework.decorators import api_view, authentication_classes, permission_classes +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status + +from django.contrib.auth.models import User + +from core.models.customer import Customer +from core.serializers.UserSerializer import UserSerializer +from core.serializers.CustomerSerializer import CustomerSerializer + +# utils.py +from core.models.AssignedRule import AssignedRule + + +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from django.utils.html import strip_tags +from django.conf import settings +from django.utils.dateparse import parse_date + + + +import requests +import json + +def send_sms(to_number, code): + username = "09399112092" + password = "Dadechin123!@##!" + from_number = "+983000505" + pattern_code = "lgfrblbdppyn202" + + url = "https://ippanel.com/patterns/pattern" + to = [to_number] + input_data = {"code": code} + + params = { + "username": username, + "password": password, + "from": from_number, + "to": json.dumps(to), + "input_data": json.dumps(input_data), + "pattern_code": pattern_code + } + + try: + response = requests.post(url, params=params, data=input_data) + return response.text + except Exception as e: + return f"Error: {e}" + + +def user_has_role(user, role_name): + return AssignedRule.objects.filter(user=user, role__name=role_name).exists() + + +@api_view(['POST']) +def signup(request): + # Check if username already exists + if User.objects.filter(username=request.data['username']).exists(): + return Response({'username': ['A user with that username already exists.']}, status=status.HTTP_400_BAD_REQUEST) + + # Ensure mobile number is provided + if 'mobile_number' not in request.data: + return Response({'mobile_number': ['This field is required.']}, status=status.HTTP_400_BAD_REQUEST) + + + # Proceed with user creation + user_serializer = UserSerializer(data=request.data) + if user_serializer.is_valid(): + user = user_serializer.save() + user.set_password(request.data['password']) + user.save() + + customer_data = { + 'user': user.id, + 'mobile_number': request.data['mobile_number'], # Ensure mobile number is provided + } + customer_serializer = CustomerSerializer(data=customer_data) + if customer_serializer.is_valid(): + customer_serializer.save() + token = Token.objects.create(user=user) + return Response({'token': token.key, 'user': customer_serializer.data}, status=status.HTTP_201_CREATED) + else: + # If customer data is invalid, delete the created user + user.delete() + return Response(customer_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +def sendForgetPasswordCode(request): + try: + + + # Retrieve the associated customer object + customer = Customer.objects.get(mobile_number=request.data['mobile_number']) + + + # Generate a random verification code + verification_code = str(random.randint(100000, 999999)) + + # Update user's verification_code and sms_time_for_valid + customer.verification_email_code = verification_code + customer.verification_sms_code = verification_code + customer.save() + + sendEmail(customer.user.email,verification_code) + + return Response({"message": ("Email verification code sent successfully. " + customer.user.email)}) + + except Customer.DoesNotExist: + # If no customer object exists for the user, return an error response + return Response({'error': 'No customer data found for this user'}, status=404) + + + + + +from django.contrib.auth.hashers import make_password + +@api_view(['POST']) +def sendCodeAndNewPassword(request): + try: + # Retrieve the associated customer object + customer = Customer.objects.get(mobile_number=request.data['mobile_number'], verification_sms_code=request.data['verification_sms_code']) + + if customer: + # Update the user's password + customer.user.password = make_password(request.data['password']) + customer.user.save() + return Response({"message": "Password has been changed successfully."}) + else: + return Response({"message": "Wrong mobile number or verification code."}) + + except Customer.DoesNotExist: + # If no customer object exists for the user, return an error response + return Response({'message': 'No customer data found for this user'}, status=404) + + + + +@api_view(['POST']) +def login(request): + try: + customer = Customer.objects.get(mobile_number=request.data['mobile_number']) + except get_user_model().DoesNotExist: + return Response("User not found", status=status.HTTP_404_NOT_FOUND) + + if not customer.user.check_password(request.data['password']): + return Response("Invalid password", status=status.HTTP_401_UNAUTHORIZED) + + token, created = Token.objects.get_or_create(user=customer.user) + serializer = UserSerializer(customer.user) + return Response({'token': token.key, 'user': serializer.data}) + + + + + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def sendSmsVerification(request): + # Retrieve the current user + user = request.user + + # Assuming a OneToOneField relation between User and Customer + try: + # Retrieve the associated customer object + customer = Customer.objects.get(user=user) + + + # Generate a random verification code + verification_code = str(random.randint(100000, 999999)) + + # Update user's verification_code and sms_time_for_valid + customer.verification_sms_code = verification_code + customer.sms_time_for_valid = datetime.now() + timedelta(minutes=10) # Set validity time to now + 10 minutes + customer.save() + + sms_response = send_sms(customer.mobile_number, verification_code) + + # Send SMS with verification code (implement this part according to your SMS service provider's API) + # send_sms(user.mobile_number, verification_code) # You need to implement this function + + return Response({ + "message": "SMS verification code sent successfully.", + "sms_response": sms_response + }) + + except Customer.DoesNotExist: + # If no customer object exists for the user, return an error response + return Response({'error': 'No customer data found for this user'}, status=404) + + + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def getInfo(request): + # Retrieve the current user + user = request.user + + # Assuming a OneToOneField relation between User and Customer + try: + # Retrieve the associated customer object + customer = Customer.objects.get(user=user) + + # Serialize the customer data + serializer = CustomerSerializer(customer) + + # Return the serialized customer data + return Response(serializer.data) + except Customer.DoesNotExist: + return Response({'error': 'No customer data found for this user'}, status=404) + + # If no customer object exists for the user, return an error response + + + + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def sendEmailVerification(request): + # Retrieve the current user + user = request.user + + # Assuming a OneToOneField relation between User and Customer + try: + # Retrieve the associated customer object + customer = Customer.objects.get(user=user) + + + # Generate a random verification code + verification_code = str(random.randint(100000, 999999)) + + # Update user's verification_code and sms_time_for_valid + customer.verification_email_code = verification_code + customer.save() + + sendEmail(user.email,verification_code) + + return Response({"message": ("Email verification code sent successfully. " + user.email)}) + + except Customer.DoesNotExist: + # If no customer object exists for the user, return an error response + return Response({'error': 'No customer data found for this user'}, status=404) + + + + +def sendEmail(email, verification_code): + subject = 'WZK Bestätigungscode' + context = {'verification_code': verification_code} + + # Render HTML content from a template + # html_content = render_to_string('core/templates/emails/email_template.html', context) + html_content = render_to_string('emails/verification_email_template2.html', context) + + # Create a plain text version of the email (optional) + text_content = strip_tags(html_content) + + email_from = settings.DEFAULT_FROM_EMAIL + recipient_list = [email] + + try: + # Create an EmailMultiAlternatives object + msg = EmailMultiAlternatives(subject, text_content, email_from, recipient_list) + + # Attach the HTML content to the email + msg.attach_alternative(html_content, "text/html") + + # Send the email + msg.send() + + print("Email has been sent! ") + return True + except Exception as e: + print(f"Error sending email: {e}") + return False + + + + +@api_view(['POST']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def submitEmailVerification(request): + try: + user = request.user + + customer = Customer.objects.get(user=user) + + if customer.verification_email_code == request.data['verification_email_code'] : + customer.is_email_verified = True + customer.save() + return Response({"message": "Email Verified!"}) + # else : + # return Response({"message": customer.verification_email_code}) + + + + except get_user_model().DoesNotExist: + return Response("Not verified 1", status=status.HTTP_404_NOT_FOUND) + return Response("Not verified ", status=status.HTTP_404_NOT_FOUND) + + + + +@api_view(['POST']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def submitSmsVerification(request): + try: + user = request.user + + customer = Customer.objects.get(user=user) + + if customer.verification_sms_code == request.data['verification_sms_code'] : + customer.is_sms_verified = True + customer.save() + return Response({"message": "SMS Verified!"}) + # else : + # return Response({"message": customer.verification_email_code}) + + + + except get_user_model().DoesNotExist: + return Response("Not verified 1", status=status.HTTP_404_NOT_FOUND) + return Response("Not verified ", status=status.HTTP_404_NOT_FOUND) + + + + + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def test_token(request): + if not user_has_role(request.user, 'admin'): + return Response({'message': 'No access'}, status=status.HTTP_403_FORBIDDEN) + + return Response({'message': 'User has admin role'}) diff --git a/core/wsgi.py b/core/wsgi.py new file mode 100644 index 0000000..e6e1fe9 --- /dev/null +++ b/core/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for server 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.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_wsgi_application() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a032143 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3.6" + +services: + db: + image: mysql:latest + environment: + MYSQL_ROOT_PASSWORD: CLINET999zztt + MYSQL_DATABASE: mysql_clinet + MYSQL_USER: clinet_dbadmin_pg + MYSQL_PASSWORD: CLINET999zztt + volumes: + - mysql_data:/var/lib/mysql_test_newplatform/data + + web: + build: + context: . + dockerfile: Dockerfile + command: sh -c "sleep 20 && python manage.py runserver 0.0.0.0:55" + volumes: + - .:/code + ports: + - "55:55" + depends_on: + - db + environment: + DJANGO_SETTINGS_MODULE: core.settings + MYSQL_UNIX_PORT: /var/run/mysqld/mysqld.sock + +volumes: + mysql_data: diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..f2a662c --- /dev/null +++ b/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', 'core.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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fca4b01 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +djangorestframework==3.15.1 +django-cors-headers==4.3.1 +pymysql==1.1.0 +python-dotenv==1.0.1 +mysqlclient==2.1.1 + +py -m pip install Django==5.2 diff --git a/server/views.py b/server/views.py new file mode 100644 index 0000000..6ec0936 --- /dev/null +++ b/server/views.py @@ -0,0 +1,49 @@ +from rest_framework.decorators import api_view, authentication_classes, permission_classes +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status + +from django.shortcuts import get_object_or_404 +from django.contrib.auth.models import User +from rest_framework.authtoken.models import Token +# from models.user import User +# from .models.serializers import UserSerializer + +from .models.user import UserSerializer + + +# utils.py +from .models.AssignedRule import AssignedRule + +def user_has_role(user, role_name): + return AssignedRule.objects.filter(user=user, role__name=role_name).exists() + + + +@api_view(['POST']) +def signup(request): + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + user = User.objects.get(username=request.data['username']) + user.set_password(request.data['password']) + user.save() + token = Token.objects.create(user=user) + return Response({'token': token.key, 'user': serializer.data}) + return Response(serializer.errors, status=status.HTTP_200_OK) + +@api_view(['POST']) +def login(request): + user = get_object_or_404(User, username=request.data['username']) + if not user.check_password(request.data['password']): + return Response("missing user", status=status.HTTP_404_NOT_FOUND) + token, created = Token.objects.get_or_create(user=user) + serializer = UserSerializer(user) + return Response({'token': token.key, 'user': serializer.data}) + +@api_view(['GET']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def test_token(request): + return Response("passed!") \ No newline at end of file diff --git a/test.rest b/test.rest new file mode 100644 index 0000000..21e69bd --- /dev/null +++ b/test.rest @@ -0,0 +1,71 @@ +# https://marketplace.visualstudio.com/items?itemName=humao.rest-client + +POST http://127.0.0.1:8000/signup +Content-Type: application/json + +{ "username": "adam3", "password": "Pass1234!", "email": "adam2@mail.com" , "mobile_number":"09140086509" } + +### + +POST http://127.0.0.1:8000/login +Content-Type: application/json + +{ "mobile_number":"09140086509", "password": "Pass1234!" } + +### + +GET http://127.0.0.1:8000/sendSmsVerification +Content-Type: application/json +Authorization: token cb8c2ef7913df31085e749398f22da5b43f419b2 + +### + +POST http://127.0.0.1:8000/submitSmsVerification +Content-Type: application/json +Authorization: token cb8c2ef7913df31085e749398f22da5b43f419b2 + +{ "verification_sms_code": "807806" } + +### + +GET http://127.0.0.1:8000/test_token +Content-Type: application/json +Authorization: token c362581117e209735d412226e54596867e370892 +# Authorization: token 53e2b003a92e22aca85c95088a438ece8d9a5dfb + + +### + +GET http://127.0.0.1:8000/getInfo +Content-Type: application/json +Authorization: token 3d5ab31449b6a075e3967559526d5e31977431a1 +# Authorization: token 53e2b003a92e22aca85c95088a438ece8d9a5dfb + + +### +GET http://127.0.0.1:8000/templatequestions +Content-Type: application/json +Authorization: token c362581117e209735d412226e54596867e370892 + + +### +GET http://127.0.0.1:8000/templatequestionscgm +Content-Type: application/json +Authorization: token c362581117e209735d412226e54596867e370892 + + +### +GET http://127.0.0.1:8000/templatepages +Content-Type: application/json +Authorization: token c362581117e209735d412226e54596867e370892 + + +### + + POST http://127.0.0.1:8000/savetemplateform +Content-Type: application/json +Authorization: token c362581117e209735d412226e54596867e370892 + +{ "question_id": "1", "answer_text": "answer1" } + +### \ No newline at end of file