mirror of
https://github.com/Dadechin/Dashboard-XRoom.git
synced 2025-07-04 09:14:34 +00:00
auth and simple info
This commit is contained in:
parent
89f462bf9f
commit
3f47437822
89
.gitignore
vendored
Normal file
89
.gitignore
vendored
Normal file
|
@ -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
|
39
.gitlab-ci.yml
Normal file
39
.gitlab-ci.yml
Normal file
|
@ -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
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -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/
|
7
REDME.md
Normal file
7
REDME.md
Normal file
|
@ -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
|
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
11
core/admin.py
Normal file
11
core/admin.py
Normal file
|
@ -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)
|
16
core/asgi.py
Normal file
16
core/asgi.py
Normal file
|
@ -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()
|
47
core/migrations/0001_initial.py
Normal file
47
core/migrations/0001_initial.py
Normal file
|
@ -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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
23
core/migrations/0002_alter_customer_profile_glb_and_more.py
Normal file
23
core/migrations/0002_alter_customer_profile_glb_and_more.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
0
core/migrations/__init__.py
Normal file
0
core/migrations/__init__.py
Normal file
9
core/models/AssignedRule.py
Normal file
9
core/models/AssignedRule.py
Normal file
|
@ -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
|
23
core/models/customer.py
Normal file
23
core/models/customer.py
Normal file
|
@ -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
|
6
core/models/role.py
Normal file
6
core/models/role.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Role(models.Model):
|
||||||
|
name = models.CharField(max_length=100, unique=True)
|
||||||
|
|
||||||
|
|
7
core/serializers/CustomerSerializer.py
Normal file
7
core/serializers/CustomerSerializer.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from ..models.customer import Customer
|
||||||
|
|
||||||
|
class CustomerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Customer
|
||||||
|
fields = '__all__'
|
8
core/serializers/UserSerializer.py
Normal file
8
core/serializers/UserSerializer.py
Normal file
|
@ -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' ]
|
0
core/serializers/__init__.py
Normal file
0
core/serializers/__init__.py
Normal file
161
core/settings.py
Normal file
161
core/settings.py
Normal file
|
@ -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'
|
||||||
|
|
16
core/templates/emails/verification_email_template.html
Normal file
16
core/templates/emails/verification_email_template.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
.verification-code {
|
||||||
|
background-color: orange;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hallo, Willkommen bei WZK. Ihr E-Mail-Bestätigungscode lautet:</p>
|
||||||
|
<p class="verification-code">{{ verification_code }}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
764
core/templates/emails/verification_email_template2.html
Normal file
764
core/templates/emails/verification_email_template2.html
Normal file
|
@ -0,0 +1,764 @@
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<!--[if !mso]><!-- -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<style type="text/css">
|
||||||
|
#outlook a {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.ReadMsgBody {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ExternalClass * {
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
border-collapse: collapse;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
display: block;
|
||||||
|
margin: 13px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (max-width: 480px) {
|
||||||
|
@-ms-viewport {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
@viewport {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--<![endif]-->
|
||||||
|
<!--[if mso]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG />
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.outlook-group-fix {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
|
<style type="text/css">
|
||||||
|
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||||
|
</style>
|
||||||
|
<!--<![endif]-->
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width: 480px) {
|
||||||
|
.mj-column-per-100,
|
||||||
|
* [aria-labelledby="mj-column-per-100"] {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="background: #f9f9f9">
|
||||||
|
<div style="background-color: #f9f9f9">
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
<style type="text/css">
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
* {
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
text-size-adjust: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #ffd512;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div style="margin: 0px auto; max-width: 640px; background: transparent">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="font-size: 0px; width: 100%; background: transparent"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 40px 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
aria-labelledby="mj-column-per-100"
|
||||||
|
class="mj-column-per-100 outlook-group-fix"
|
||||||
|
style="
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="border-collapse: collapse; border-spacing: 0px"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin: 0px auto;
|
||||||
|
max-width: 640px;
|
||||||
|
background: #ffd512
|
||||||
|
url(https://wappapi.wz-kliniken.de/wzkregister/assets/login/login.jpg)
|
||||||
|
top center / cover no-repeat;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:640px;">
|
||||||
|
<v:fill origin="0.5, 0" position="0.5,0" type="tile" src="https://wappapi.wz-kliniken.de/wzkregister/assets/login/login.jpg" />
|
||||||
|
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
|
||||||
|
<![endif]-->
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="
|
||||||
|
height: 230px;
|
||||||
|
font-size: 0px;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffd512
|
||||||
|
url(https://wappapi.wz-kliniken.de/wzkregister/assets/login/login.jpg)
|
||||||
|
top center / cover no-repeat;
|
||||||
|
filter: blur(5px); /* Adjust the blur radius as needed */
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
background="https://wappapi.wz-kliniken.de/wzkregister/assets/login/login.jpg"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 57px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:undefined;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: white;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica, Arial,
|
||||||
|
Lucida Grande, sans-serif;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 36px;
|
||||||
|
text-align: center;
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</v:textbox>
|
||||||
|
</v:rect>
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
<div style="margin: 0px auto; max-width: 640px; background: #ffffff">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="font-size: 0px; width: 100%; background: #ffffff"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 40px 70px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
aria-labelledby="mj-column-per-100"
|
||||||
|
class="mj-column-per-100 outlook-group-fix"
|
||||||
|
style="
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px 0px 20px;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: #737f8d;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: left;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<!-- <img
|
||||||
|
src="https://cdn.discordapp.com/email_assets/127c95bbea39cd4bc1ad87d1500ae27d.png"
|
||||||
|
alt="Party Wumpus"
|
||||||
|
title="None"
|
||||||
|
width="500"
|
||||||
|
style="height: auto"
|
||||||
|
/> -->
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
style="
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #4f545c;
|
||||||
|
letter-spacing: 0.27px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
WZK Bestätigungscode,
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Hallo, Willkommen bei WZK. Ihr
|
||||||
|
E-Mail-Bestätigungscode lautet
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Before we get started, we'll need to verify your
|
||||||
|
email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="border-collapse: separate"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
background: #ffd512;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: white;
|
||||||
|
cursor: auto;
|
||||||
|
padding: 15px 19px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
valign="middle"
|
||||||
|
bgcolor="#7289DA"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
style="
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 100%;
|
||||||
|
background: #ffd512;
|
||||||
|
color: white;
|
||||||
|
font-family: Ubuntu, Helvetica, Arial,
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: none;
|
||||||
|
margin: 0px;
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
><p style="color: black">
|
||||||
|
{{ verification_code }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
<div style="margin: 0px auto; max-width: 640px; background: transparent">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="font-size: 0px; width: 100%; background: transparent"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
aria-labelledby="mj-column-per-100"
|
||||||
|
class="mj-column-per-100 outlook-group-fix"
|
||||||
|
style="
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="word-break: break-word; font-size: 0px">
|
||||||
|
<div style="font-size: 1px; line-height: 12px">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 640px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="font-size: 0px; width: 100%; background: #ffffff"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
aria-labelledby="mj-column-per-100"
|
||||||
|
class="mj-column-per-100 outlook-group-fix"
|
||||||
|
style="
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 30px 70px 0px 70px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: #43b581;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
FUN FACT #16
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 14px 70px 30px 70px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: #737f8d;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
In Hearthstone, using the Hunter card Animal Companion
|
||||||
|
against Kel'Thuzad will summon his cat Mr.
|
||||||
|
Bigglesworth rather than the usual beasts.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="640" align="center" style="width:640px;">
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
<div style="margin: 0px auto; max-width: 640px; background: transparent">
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
style="font-size: 0px; width: 100%; background: transparent"
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 20px 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;width:640px;">
|
||||||
|
<![endif]-->
|
||||||
|
<div
|
||||||
|
aria-labelledby="mj-column-per-100"
|
||||||
|
class="mj-column-per-100 outlook-group-fix"
|
||||||
|
style="
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
direction: ltr;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: #99aab5;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Sent by Discord •
|
||||||
|
<a
|
||||||
|
href="https://blog.discordapp.com/"
|
||||||
|
style="color: #1eb0f4; text-decoration: none"
|
||||||
|
target="_blank"
|
||||||
|
>check our blog</a
|
||||||
|
>
|
||||||
|
•
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/discordapp"
|
||||||
|
style="color: #1eb0f4; text-decoration: none"
|
||||||
|
target="_blank"
|
||||||
|
>@discordapp</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
cursor: auto;
|
||||||
|
color: #99aab5;
|
||||||
|
font-family: Whitney, Helvetica Neue, Helvetica,
|
||||||
|
Arial, Lucida Grande, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
444 De Haro Street, Suite 200, San Francisco, CA 94107
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td></tr></table>
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
</body>
|
0
core/tests/__init__.py
Normal file
0
core/tests/__init__.py
Normal file
57
core/tests/test_signup.py
Normal file
57
core/tests/test_signup.py
Normal file
|
@ -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)
|
||||||
|
|
31
core/urls.py
Normal file
31
core/urls.py
Normal file
|
@ -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),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
]
|
58
core/views.py
Normal file
58
core/views.py
Normal file
|
@ -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'})
|
0
core/views/__init__.py
Normal file
0
core/views/__init__.py
Normal file
353
core/views/userView.py
Normal file
353
core/views/userView.py
Normal file
|
@ -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'})
|
16
core/wsgi.py
Normal file
16
core/wsgi.py
Normal file
|
@ -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()
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
|
@ -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:
|
22
manage.py
Normal file
22
manage.py
Normal file
|
@ -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()
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
|
@ -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
|
49
server/views.py
Normal file
49
server/views.py
Normal file
|
@ -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!")
|
71
test.rest
Normal file
71
test.rest
Normal file
|
@ -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" }
|
||||||
|
|
||||||
|
###
|
Loading…
Reference in New Issue
Block a user