diff --git a/.circleci/config.yml b/.circleci/config.yml index 4eae44d3d..a3c6f7b17 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,13 +2,11 @@ # # Check https://circleci.com/docs/2.0/language-javascript/ for more details # https://circleci.com/gh/dmpe/django-wohn -version: 2 +version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 - # setup_remote_docker: - # version: 18.09.3 working_directory: ~/repo steps: - checkout @@ -31,3 +29,10 @@ workflows: jobs: - build: context: DockerHub + filters: + branches: + ignore: + - docs + - server-config + - gh-pages + - azure-functions diff --git a/.gitignore b/.gitignore index 2a082e04a..668cb76c7 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ node_modules/ # local env files .env.local .env.*.local +schemav1.json # Log files npm-debug.log* diff --git a/README.md b/README.md index ca68a4a96..5bd9c3ad7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![image](https://img.shields.io/github/license/dmpe/django-wohn)](https://github.com/dmpe/django-wohn/LICENSE) [![Build Status](https://johnmalc.visualstudio.com/DJango-Wohn/_apis/build/status/dmpe.django-wohn?branchName=master)](https://johnmalc.visualstudio.com/DJango-Wohn/_build/latest?definitionId=7&branchName=master) +[![CircleCI](https://circleci.com/gh/dmpe/django-wohn/tree/master.svg?style=svg)](https://circleci.com/gh/dmpe/django-wohn/tree/master) ![GitHub language count](https://img.shields.io/github/languages/count/dmpe/django-wohn) [![Requirements Status](https://requires.io/github/dmpe/django-wohn/requirements.svg?branch=master)](https://requires.io/github/dmpe/django-wohn/requirements/?branch=master) ![GitHub repo size](https://img.shields.io/github/repo-size/dmpe/django-wohn) diff --git a/backend/core/forms.py b/backend/core/forms.py index f2b1f04e4..31c3208d5 100644 --- a/backend/core/forms.py +++ b/backend/core/forms.py @@ -22,7 +22,7 @@ class RegisterForm(forms.Form): class LoginForm(forms.Form): """ - Users can login either via username or email. + Users can login either via username or email. Hence, input type="text" """ diff --git a/backend/core/migrations/0001_initial.py b/backend/core/migrations/0001_initial.py index bb1298ff7..42e6465fa 100644 --- a/backend/core/migrations/0001_initial.py +++ b/backend/core/migrations/0001_initial.py @@ -1,117 +1,386 @@ # Generated by Django 2.2.5 on 2019-10-13 18:31 -import core.mics -import core.models -from django.conf import settings import django.contrib.auth.validators -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone import django_countries.fields import phonenumber_field.modelfields import timezone_field.fields +from django.conf import settings +from django.db import migrations, models + +import core.mics +import core.models class Migration(migrations.Migration): initial = True - dependencies = [ - ('auth', '0011_update_proxy_permissions'), - ] + dependencies = [("auth", "0011_update_proxy_permissions")] operations = [ migrations.CreateModel( - name='myUser', + name="myUser", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('user_gender', models.CharField(choices=[('M', 'Mr.'), ('F', 'Mrs. or Miss'), ('O', 'Other or prefer not to say')], default='O', max_length=1, null=True)), - ('user_int_tel', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None)), - ('user_timezone', timezone_field.fields.TimeZoneField(default='Europe/Prague')), - ('user_country', django_countries.fields.CountryField(default='CZ', max_length=2)), - ('user_profile_image', models.ImageField(blank=True, null=True, upload_to=core.mics.upload_profile_image)), - ('user_units_system', models.CharField(choices=[('Imperial', 'Imperial'), ('Metric', 'Metric')], default='Metric', max_length=10, null=True)), - ('user_first_lastname_visibility', models.CharField(choices=[('VFN', 'First name'), ('VLN', 'Last name')], default='VFN', max_length=3, null=True)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'unique_together': {('email',)}, - }, - managers=[ - ('objects', core.models.MyUserManager()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=30, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "user_gender", + models.CharField( + choices=[ + ("M", "Mr."), + ("F", "Mrs. or Miss"), + ("O", "Other or prefer not to say"), + ], + default="O", + max_length=1, + null=True, + ), + ), + ( + "user_int_tel", + phonenumber_field.modelfields.PhoneNumberField( + blank=True, max_length=128, null=True, region=None + ), + ), + ( + "user_timezone", + timezone_field.fields.TimeZoneField(default="Europe/Prague"), + ), + ( + "user_country", + django_countries.fields.CountryField(default="CZ", max_length=2), + ), + ( + "user_profile_image", + models.ImageField( + blank=True, null=True, upload_to=core.mics.upload_profile_image + ), + ), + ( + "user_units_system", + models.CharField( + choices=[("Imperial", "Imperial"), ("Metric", "Metric")], + default="Metric", + max_length=10, + null=True, + ), + ), + ( + "user_first_lastname_visibility", + models.CharField( + choices=[("VFN", "First name"), ("VLN", "Last name")], + default="VFN", + max_length=3, + null=True, + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], + options={"unique_together": {("email",)}}, + managers=[("objects", core.models.MyUserManager())], ), migrations.CreateModel( - name='ExchangeRate', + name="ExchangeRate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('today', models.DateField(auto_now_add=True, verbose_name="Today's Date")), - ('OneEurCzk', models.DecimalField(decimal_places=3, max_digits=7, verbose_name='1 EUR - CZK')), - ('OneEurUsd', models.DecimalField(decimal_places=3, max_digits=7, verbose_name='1 EUR - USD')), - ('OneUsdCzk', models.DecimalField(decimal_places=3, max_digits=7, verbose_name='1 USD - CZK')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "today", + models.DateField(auto_now_add=True, verbose_name="Today's Date"), + ), + ( + "OneEurCzk", + models.DecimalField( + decimal_places=3, max_digits=7, verbose_name="1 EUR - CZK" + ), + ), + ( + "OneEurUsd", + models.DecimalField( + decimal_places=3, max_digits=7, verbose_name="1 EUR - USD" + ), + ), + ( + "OneUsdCzk", + models.DecimalField( + decimal_places=3, max_digits=7, verbose_name="1 USD - CZK" + ), + ), ], ), migrations.CreateModel( - name='Room', + name="Room", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('property_created', models.DateTimeField(auto_now_add=True)), - ('property_rooms', models.IntegerField()), - ('property_size_in_sq_meters', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_size_in_sq_foot', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_eur', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_czk', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_usd', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_offered_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("property_created", models.DateTimeField(auto_now_add=True)), + ("property_rooms", models.IntegerField()), + ( + "property_size_in_sq_meters", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_size_in_sq_foot", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_eur", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_czk", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_usd", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_offered_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - options={ - 'verbose_name_plural': 'Rooms', - }, + options={"verbose_name_plural": "Rooms"}, ), migrations.CreateModel( - name='House', + name="House", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('property_created', models.DateTimeField(auto_now_add=True)), - ('property_rooms', models.IntegerField()), - ('property_size_in_sq_meters', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_size_in_sq_foot', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_eur', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_czk', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_usd', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('house_garden_size_in_sq_meters', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_offered_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("property_created", models.DateTimeField(auto_now_add=True)), + ("property_rooms", models.IntegerField()), + ( + "property_size_in_sq_meters", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_size_in_sq_foot", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_eur", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_czk", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_usd", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "house_garden_size_in_sq_meters", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_offered_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - options={ - 'verbose_name_plural': 'Houses', - }, + options={"verbose_name_plural": "Houses"}, ), migrations.CreateModel( - name='Apartment', + name="Apartment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('property_created', models.DateTimeField(auto_now_add=True)), - ('property_rooms', models.IntegerField()), - ('property_size_in_sq_meters', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_size_in_sq_foot', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_eur', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_czk', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_price_in_usd', models.DecimalField(blank=True, decimal_places=2, max_digits=7, null=True)), - ('property_offered_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("property_created", models.DateTimeField(auto_now_add=True)), + ("property_rooms", models.IntegerField()), + ( + "property_size_in_sq_meters", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_size_in_sq_foot", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_eur", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_czk", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_price_in_usd", + models.DecimalField( + blank=True, decimal_places=2, max_digits=7, null=True + ), + ), + ( + "property_offered_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - options={ - 'verbose_name_plural': 'Apartments', - }, + options={"verbose_name_plural": "Apartments"}, ), ] diff --git a/backend/core/migrations/0002_auto_20191102_2035_squashed_0003_auto_20191102_2157.py b/backend/core/migrations/0002_auto_20191102_2035_squashed_0003_auto_20191102_2157.py new file mode 100644 index 000000000..0e177430c --- /dev/null +++ b/backend/core/migrations/0002_auto_20191102_2035_squashed_0003_auto_20191102_2157.py @@ -0,0 +1,131 @@ +# Generated by Django 2.2.6 on 2019-11-02 21:15 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + replaces = [('core', '0002_auto_20191102_2035'), ('core', '0003_auto_20191102_2157')] + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='apartment', + name='apartment_floor', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name='apartment', + name='property_address_city_town', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='apartment', + name='property_address_street', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='apartment', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AddField( + model_name='apartment', + name='property_furnished', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='apartment', + name='property_garage', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name='house', + name='property_address_city_town', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='house', + name='property_address_street', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='house', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AddField( + model_name='house', + name='property_furnished', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='house', + name='property_garage', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name='room', + name='apartment_floor', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name='room', + name='property_address_city_town', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='room', + name='property_address_street', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='room', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AddField( + model_name='room', + name='property_furnished', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='room', + name='property_garage', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.AddField( + model_name='apartment', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=2, null=True), + ), + migrations.AddField( + model_name='apartment', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=2), + ), + migrations.AddField( + model_name='house', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=2, null=True), + ), + migrations.AddField( + model_name='house', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=2), + ), + migrations.AddField( + model_name='room', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=2, null=True), + ), + migrations.AddField( + model_name='room', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=2), + ), + ] diff --git a/backend/core/migrations/0004_auto_20191102_2200_squashed_0005_auto_20191102_2208.py b/backend/core/migrations/0004_auto_20191102_2200_squashed_0005_auto_20191102_2208.py new file mode 100644 index 000000000..76abc134e --- /dev/null +++ b/backend/core/migrations/0004_auto_20191102_2200_squashed_0005_auto_20191102_2208.py @@ -0,0 +1,76 @@ +# Generated by Django 2.2.6 on 2019-11-02 21:17 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + replaces = [('core', '0004_auto_20191102_2200'), ('core', '0005_auto_20191102_2208')] + + dependencies = [ + ('core', '0003_auto_20191102_2157'), + ] + + operations = [ + migrations.RenameField( + model_name='room', + old_name='apartment_floor', + new_name='room_floor', + ), + migrations.AlterField( + model_name='apartment', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(9999), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AlterField( + model_name='house', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(9999), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AlterField( + model_name='room', + name='property_address_zipcode', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(9999), django.core.validators.MaxValueValidator(100000)]), + ), + migrations.AlterField( + model_name='apartment', + name='apartment_floor', + field=models.PositiveSmallIntegerField(default=1), + ), + migrations.AlterField( + model_name='apartment', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=3, null=True), + ), + migrations.AlterField( + model_name='apartment', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=3), + ), + migrations.AlterField( + model_name='house', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=3, null=True), + ), + migrations.AlterField( + model_name='house', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=3), + ), + migrations.AlterField( + model_name='room', + name='property_status', + field=models.CharField(choices=[('N', 'New'), ('G', 'Good'), ('UC', 'Under Construction')], default='G', max_length=3, null=True), + ), + migrations.AlterField( + model_name='room', + name='property_wash_machine', + field=models.CharField(choices=[('O', 'Owned'), ('S', 'Shared in house'), ('NP', 'Not present')], default='NP', max_length=3), + ), + migrations.AlterField( + model_name='room', + name='room_floor', + field=models.PositiveSmallIntegerField(default=1), + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index b0c8d96ae..9d8afbb5a 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1,14 +1,13 @@ import hashlib - # for gravatar URLs and user's profile image and its unique name import urllib import django - # for time related tasks, incl. timezone import pytz from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.safestring import * from django_countries.fields import CountryField @@ -52,6 +51,25 @@ class AbstractProperty(django.db.models.Model): max_digits=7, decimal_places=2, null=True, blank=True ) + property_garage = models.PositiveSmallIntegerField(default=0) + + PROPERTY_STATUS = (("N", "New"), ("G", "Good"), ("UC", "Under Construction")) + property_status = models.CharField( + max_length=3, choices=PROPERTY_STATUS, null=True, default="G" + ) + + property_furnished = models.BooleanField(default=False) + + WASHING_MACHINE = (("O", "Owned"), ("S", "Shared in house"), ("NP", "Not present")) + property_wash_machine = models.CharField( + max_length=3, choices=WASHING_MACHINE, null=False, default="NP" + ) + property_address_street = models.TextField(null=True) + property_address_city_town = models.TextField(null=True) + property_address_zipcode = models.IntegerField( + default=0, validators=[MinValueValidator(9999), MaxValueValidator(100000)] + ) + class Meta: verbose_name_plural = "properties" abstract = True @@ -85,6 +103,8 @@ class Apartment(AbstractProperty): """ + apartment_floor = models.PositiveSmallIntegerField(default=1) + class Meta: verbose_name_plural = "Apartments" @@ -95,6 +115,8 @@ class Room(AbstractProperty): """ + room_floor = models.PositiveSmallIntegerField(default=1) + class Meta: verbose_name_plural = "Rooms" diff --git a/backend/core/pages/footer/about.html b/backend/core/pages/footer/about.html deleted file mode 100644 index 082cc3382..000000000 --- a/backend/core/pages/footer/about.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}melive.xyz - About us{% endblock %} - -{% block descip %}Learn more about people behind melive.xyz{% endblock %} - -{% block robots %}index, follow{% endblock %} - -{% block content %} -
- -
-
- {{ raw_markdown_html | safe }} -
-
-
-{% endblock %} \ No newline at end of file diff --git a/backend/core/pages/footer/privacy.html b/backend/core/pages/footer/privacy.html deleted file mode 100644 index 38c526df4..000000000 --- a/backend/core/pages/footer/privacy.html +++ /dev/null @@ -1,157 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}melive.xyz - Privacy Policy{% endblock %} - -{% block descip %}Your privacy when using melive.xyz{% endblock %} - -{% block robots %}index, follow{% endblock %} - -{% block content %} -
- -
-

Privacy Policy

-

Effective date: November 5th, 2018 | Contact us

-
- -
-
-

1. What ?

-

This Site ("us", "we", "service", or "our") operates the https://melive.xyz website.

- -

Your privacy is important to us. It is our policy to respect your privacy regarding any information we may collect from you across our websites we own and operate. Therefore, this page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our services and the choices you have associated with that data.

- -

This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our Service and the choices you have associated with that data. We use your data to provide and improve our services. By using them, you agree to the collection and use of information in accordance with this policy.

- -

Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms of Use, accessible from https://melive.xyz/terms.

- -

Your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.

-
-
- -
-
-

2. Information Collection and Use

-

We collect several different types of information for various purposes to provide and improve our service to you.

-

We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent.

- -
2.1 Personal Data
- -

While using our service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:

- -
    -
  • Email address
  • -
  • First name and last name
  • -
  • Phone number
  • -
  • Cookies and Usage Data
  • -
- -
2.2 Usage Data
- -

We may also collect information how the service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (i.e. IP address), browser type, browser version, the pages of our service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

- -
2.3 Tracking & Cookies Data
-

We use cookies and similar tracking technologies to track the activity on our service and hold certain information.

-

Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our service.

-

You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our service.

-

Examples of Cookies we use:

- -
    -
  • We use Session Cookies to operate our service.
  • -
  • We use Preference Cookies to remember your preferences and various settings.
  • -
  • We use Security Cookies for security purposes.
  • -
-
-
- -
-
-

3. How we use your data ?

-

This site uses the collected data for various purposes:

-
    -
  • To provide and maintain the service.
  • -
  • To notify you about changes to our service.
  • -
  • To allow you to participate in interactive features of our service when you choose to do so.
  • -
  • To provide customer care and support.
  • -
  • To provide analysis or valuable information so that we can improve the service.
  • -
  • To monitor the usage of the service.
  • -
  • To detect, prevent and address technical issues.
  • -
-
-
- -
-
-

4. Transfer Of Data

-

Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.

-

If you are located outside EU and choose to provide information to us, please note that we transfer the data, including Personal Data, to EU and process it there.

-

Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.

-

My Site will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.

-
-
- -
-
-

5. Disclosure Of Data

-

This site may disclose your Personal Data in the good faith belief that such action is necessary to:

-
    -
  • To comply with a legal obligation
  • -
  • To protect and defend the rights or property of My Site
  • -
  • To prevent or investigate possible wrongdoing in connection with the service
  • -
  • To protect the personal safety of users of the service or the public
  • -
  • To protect against legal liability
  • -
-
-
- -
-
-

6. Security Of Data

-

The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.

-
-
- -
-
-

7. Service Providers

-

We may employ third party companies and individuals to facilitate our service ("service Providers"), to provide the service on our behalf, to perform service-related services or to assist us in analyzing how our service is used.

-

These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.

- -

7.1 Analytics

-

We use third-party service providers to monitor and analyze the use of our service.

-
    -
  • -

    Google Analytics

    -

    Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.

    -

    You can opt-out of having made your activity on the service available to Google Analytics by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google Analytics JavaScript from sharing information with Google Analytics about visits activity. Or simly adblock us.

    -

    For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: https://policies.google.com/privacy?hl=en

    -
  • -
-
-
- -
-
-

8. Links to other sites

-

Our service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.

-

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

-
-
- -
-
-

9. Children's Privacy

-

Our service must not be used under the age of 18 ("Children").

-

We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.

-
-
- -
-
-

10. Changes To This Privacy Policy

-

We update our Privacy Policy from time to time and we will notify you of any changes by posting the new Privacy Policy on this page.

-

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

-
-
-
-{% endblock %} \ No newline at end of file diff --git a/backend/core/pages/footer/terms.html b/backend/core/pages/footer/terms.html deleted file mode 100644 index abefede81..000000000 --- a/backend/core/pages/footer/terms.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}melive.xyz - Terms of Use{% endblock %} - -{% block descip %}Your terms of use on melive.xyz{% endblock %} - -{% block robots %}index, follow{% endblock %} - -{% block content %} -
- -
-

Terms of Service

-

Effective date: November 9th, 2018 | Contact us

-
- -
-

1. Terms

-

By accessing the website at https://melive.xyz, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.

-
- -
-

2. Use License

-
    -
  1. Permission is granted to temporarily download one copy of the materials (information or software) on melive.xyz's website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not: -
      -
    1. modify or copy the materials;
    2. -
    3. use the materials for any commercial purpose, or for any public display (commercial or non-commercial);
    4. -
    5. attempt to decompile or reverse engineer any software contained on melive.xyz's website;
    6. -
    7. remove any copyright or other proprietary notations from the materials; or
    8. -
    9. transfer the materials to another person or "mirror" the materials on any other server.
    10. -
    -
  2. -
  3. This license shall automatically terminate if you violate any of these restrictions and may be terminated by melive.xyz at any time. Upon terminating your viewing of these materials or upon the termination of this license, you must destroy any downloaded materials in your possession whether in electronic or printed format.
  4. -
-
- -
-

3. Disclaimer

-
    -
  1. The materials on melive.xyz's website are provided on an 'as is' basis. melive.xyz makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.
  2. -
  3. Further, melive.xyz does not warrant or make any representations concerning the accuracy, likely results, or reliability of the use of the materials on its website or otherwise relating to such materials or on any sites linked to this site.
  4. -
-
-
-

4. Limitations

-

In no event shall melive.xyz or its suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on melive.xyz's website, even if melive.xyz or a melive.xyz authorized representative has been notified orally or in writing of the possibility of such damage. Because some jurisdictions do not allow limitations on implied warranties, or limitations of liability for consequential or incidental damages, these limitations may not apply to you.

-
-
-

5. Accuracy of materials

-

The materials appearing on melive.xyz's website could include technical, typographical, or photographic errors. melive.xyz does not warrant that any of the materials on its website are accurate, complete or current. melive.xyz may make changes to the materials contained on its website at any time without notice. However melive.xyz does not make any commitment to update the materials.

-
-
-

6. Links

-

melive.xyz has not reviewed all of the sites linked to its website and is not responsible for the contents of any such linked site. The inclusion of any link does not imply endorsement by melive.xyz of the site. Use of any such linked website is at the user's own risk.

-
-
-

7. Modifications

-

melive.xyz may revise these terms of service for its website at any time without notice. By using this website you are agreeing to be bound by the then current version of these terms of service.

-
-
-

8. Governing Law

-

These terms and conditions are governed by and construed in accordance with the laws of EU and you irrevocably submit to the exclusive jurisdiction of the courts in that State or location.

-
-
-{% endblock %} \ No newline at end of file diff --git a/backend/core/pages/index.html b/backend/core/pages/index.html index ae4197c46..ed4e77b04 100644 --- a/backend/core/pages/index.html +++ b/backend/core/pages/index.html @@ -10,22 +10,5 @@

Testing homapage of B40

- {% for a_place in living_places %} -
-
- - - -
-
-

Project One

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Laudantium veniam exercitationem expedita laborum at voluptate. Labore, voluptates totam at aut nemo deserunt rem magni pariatur quos perspiciatis atque eveniet unde.

-

{{ price_in_euro }}

-

{{ price_in_czk }}

-

{{ price_in_usd }}

- View Project -
-
- {% endfor %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/backend/core/urls.py b/backend/core/urls.py index f867e0a60..d9ec3ef72 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -8,10 +8,7 @@ urlpatterns = [ path("", views.homepage, name="homepage"), - path("about", views.AboutView.as_view(), name="about"), path("contact", views.ContactView.as_view(), name="contact"), - path("terms", views.terms, name="terms"), - path("privacy", views.privacy, name="privacy"), path("register", views.RegistrationView.as_view(), name="register"), path("login", views.LoginView.as_view(), name="login"), path("logout", views.LogoutView.as_view(), name="logout"), diff --git a/backend/core/views.py b/backend/core/views.py index bea66d75e..bf32ef35b 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -25,7 +25,6 @@ from django.utils.safestring import * from django.views import View from django.views.generic import * - # a generic view for creating and saving an object (e.g. user) from django.views.generic.edit import CreateView @@ -76,7 +75,6 @@ def post(self, request): email = form.cleaned_data["inputEmail"] subject = form.cleaned_data["inputSubject"] text_msg = form.cleaned_data["inputText"] - recap_token = request.POST.get("g-recaptcha-response", False) if is_human(recap_token) is True: prepare_visitor_mssg_email(request, username, email, subject, text_msg) @@ -124,22 +122,8 @@ def get(self, request): class AboutView(View): """docstring for AboutView """ - - template_name = "about.html" - - def return_markdown(self): - input_file = codecs.open("README.md", mode="r", encoding="utf-8") - text = input_file.read() - html = markdown.markdown( - text, output_format="html5", extensions=["pymdownx.extra"] - ) - return html - def get(self, request): - raw_markdown_html = self.return_markdown() - return render( - request, self.template_name, {"raw_markdown_html": raw_markdown_html} - ) + pass ################################### diff --git a/backend/melive/settings/components/common.py b/backend/melive/settings/components/common.py index b99cc0903..75104555b 100644 --- a/backend/melive/settings/components/common.py +++ b/backend/melive/settings/components/common.py @@ -9,9 +9,10 @@ import socket import debug_toolbar - +from azure.core.exceptions import AzureError # for Azure Key Vault -from azure.keyvault import KeyVaultClient +from azure.identity import ChainedTokenCredential, ClientSecretCredential, ManagedIdentityCredential +from azure.keyvault.secrets import SecretClient from django.contrib.messages import constants as message_constants from sendgrid import SendGridAPIClient @@ -20,7 +21,9 @@ azCon = AzureConnection() azCon.main() -client = KeyVaultClient(azCon.credentials) +client = SecretClient( + vault_url="https://b40.vault.azure.net/", credential=azCon.credentials +) SOCIAL_AUTH_USER_MODEL = "core.myUser" AUTH_USER_MODEL = "core.myUser" @@ -229,37 +232,15 @@ def custom_show_toolbar(request): SOCIAL_AUTH_POSTGRES_JSONFIELD = True SOCIAL_AUTH_REDIRECT_IS_HTTPS = True -SOCIAL_AUTH_TWITTER_KEY = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-TWITTER-KEY", - "7cf698527d95469cb91474875b29a3e0", -).value -SOCIAL_AUTH_TWITTER_SECRET = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-TWITTER-SECRET", - "5f99c09acc8e41d58c87e18cdf8dcd11", -).value +SOCIAL_AUTH_TWITTER_KEY = client.get_secret("SOCIAL-AUTH-TWITTER-KEY").value +SOCIAL_AUTH_TWITTER_SECRET = client.get_secret("SOCIAL-AUTH-TWITTER-SECRET").value SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID = True -SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-GOOGLE-OAUTH2-KEY", - "e37953c45b474a46b38c1ae02e5c541b", -).value +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = client.get_secret("SOCIAL-AUTH-GOOGLE-OAUTH2-KEY").value SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-GOOGLE-OAUTH2-SECRET", - "229ada441766486781ab00c5a63e3ebf", -).value -SOCIAL_AUTH_FACEBOOK_KEY = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-FACEBOOK-KEY", - "e02a18956b4b46f6868ca9b9a3c5608d", -).value -SOCIAL_AUTH_FACEBOOK_SECRET = client.get_secret( - "https://b40.vault.azure.net/", - "SOCIAL-AUTH-FACEBOOK-SECRET", - "f439106e5d77442b8607165cf61cf260", + "SOCIAL-AUTH-GOOGLE-OAUTH2-SECRET" ).value +SOCIAL_AUTH_FACEBOOK_KEY = client.get_secret("SOCIAL-AUTH-FACEBOOK-KEY").value +SOCIAL_AUTH_FACEBOOK_SECRET = client.get_secret("SOCIAL-AUTH-FACEBOOK-SECRET").value SOCIAL_AUTH_FACEBOOK_API_VERSION = "4.0" # not same as LOGIN_URL ! @@ -283,24 +264,12 @@ def custom_show_toolbar(request): # EMAIL_PORT = 465 # EMAIL_USE_SSL = True EMAIL_HOST_USER = "azure_a880e6655cecd4d33d0a10c5f893868f@azure.com" -EMAIL_HOST_PASSWORD = client.get_secret( - "https://b40.vault.azure.net/", - "EMAIL-HOST-PASSWORD", - "08655250f5ab42a88971cc19eaedd241", -).value +EMAIL_HOST_PASSWORD = client.get_secret("EMAIL-HOST-PASSWORD").value DEFAULT_FROM_EMAIL = EMAIL_HOST_USER -SENDGRID_API_KEY = SendGridAPIClient( - client.get_secret( - "https://b40.vault.azure.net/", - "SENDGRID-API-KEY", - "5e84e665a6624d98982c836808220c7a", - ).value -) +SENDGRID_API_KEY = SendGridAPIClient(client.get_secret("SENDGRID-API-KEY").value) -MY_EMAIL = client.get_secret( - "https://b40.vault.azure.net/", "MY-EMAIL", "27ba21440e1f41798df0217622c54dda" -).value +MY_EMAIL = client.get_secret("MY-EMAIL").value # used when pushing via git logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s") diff --git a/backend/myAzure/az_connect.py b/backend/myAzure/az_connect.py index 06ec9f00d..859418178 100644 --- a/backend/myAzure/az_connect.py +++ b/backend/myAzure/az_connect.py @@ -1,9 +1,9 @@ import os -from azure.common.credentials import ServicePrincipalCredentials -from azure.keyvault import KeyVaultClient +from azure.core.exceptions import AzureError +from azure.identity import ChainedTokenCredential, ClientSecretCredential, ManagedIdentityCredential +from azure.keyvault.secrets import SecretClient from dotenv import find_dotenv, load_dotenv -from msrestazure.azure_active_directory import MSIAuthentication class AzureConnection(object): @@ -15,6 +15,7 @@ class AzureConnection(object): Result below, on local PC >> python3 az_connect.py + MSIAuthentication: Check your development: local vs. Azure development """ @@ -33,34 +34,37 @@ def __str__(self): return "the environment is: %s " % self.env def connection(self): - try: - self.credentials = MSIAuthentication(resource="https://vault.azure.net") - self.localDevelopment = False - except Exception: - print("MSIAuthentication: Check your development: local vs. Azure") + self.localDevelopment = False + cred = None + cred = ManagedIdentityCredential() + + secrets_path = find_dotenv("secrets.env") + if secrets_path != "": self.localDevelopment = True - try: - load_dotenv(find_dotenv("secrets.env")) - self.credentials = ServicePrincipalCredentials( - client_id=os.getenv("client_id"), - secret=os.getenv("secret"), - tenant=os.getenv("tenant"), - ) - except FileNotFoundError: - print( - "ensure that secrets file exists and that keys are from (ALL) application in Azure" - ) + load_dotenv(secrets_path) + service_principal = ClientSecretCredential( + client_id=os.getenv("client_id"), + client_secret=os.getenv("secret"), + tenant_id=os.getenv("tenant"), + ) + cred = ChainedTokenCredential( + ManagedIdentityCredential(), service_principal + ) + + try: + self.credentials = cred + except AzureError: + print("Check Azure settings/connection/availability") + return [self.credentials, self.localDevelopment] def dev_or_prod(self): - client = KeyVaultClient(self.credentials) + client = SecretClient( + vault_url="https://b40.vault.azure.net/", credential=self.credentials + ) try: if self.localDevelopment is False: - self.env = client.get_secret( - "https://b40.vault.azure.net/", - "DJANGO-ENV", - "baf42a60cc1e4b588831fba2c9f2ce50", - ).value + self.env = client.get_secret("DJANGO-ENV").value else: self.env = "development" except Exception as e: diff --git a/backend/myAzure/az_storage.py b/backend/myAzure/az_storage.py index f226256a5..4f85d857c 100644 --- a/backend/myAzure/az_storage.py +++ b/backend/myAzure/az_storage.py @@ -1,4 +1,6 @@ -from azure.keyvault import KeyVaultClient +from azure.core.exceptions import AzureError +from azure.identity import ChainedTokenCredential, ClientSecretCredential, ManagedIdentityCredential +from azure.keyvault.secrets import SecretClient from storages.backends.azure_storage import AzureStorage from .az_connect import AzureConnection @@ -7,7 +9,9 @@ # used for Azure Key Vault azCon = AzureConnection() azCon.main() -client = KeyVaultClient(azCon.credentials) +client = SecretClient( + vault_url="https://b40.vault.azure.net/", credential=azCon.credentials +) class AzureMediaStorage(AzureStorage): @@ -21,11 +25,7 @@ class AzureMediaStorage(AzureStorage): """ account_name = "melivexyz5555" - account_key = client.get_secret( - "https://b40.vault.azure.net/", - "AZURE-ACCOUNT-KEY", - "36456572c12640afa4c2ba448169ee66", - ).value + account_key = client.get_secret("AZURE-ACCOUNT-KEY").value azure_container = "images-profile-pictures" expiration_secs = None @@ -38,10 +38,6 @@ class AzureStaticStorage(AzureStorage): """ account_name = "melivexyz5555" - account_key = client.get_secret( - "https://b40.vault.azure.net/", - "AZURE-ACCOUNT-KEY", - "36456572c12640afa4c2ba448169ee66", - ).value + account_key = client.get_secret("AZURE-ACCOUNT-KEY").value azure_container = "static" expiration_secs = None diff --git a/backend/myAzure/tests/az_connect_test.py b/backend/myAzure/tests/az_connect_test.py index 4eb56b920..937fecd87 100644 --- a/backend/myAzure/tests/az_connect_test.py +++ b/backend/myAzure/tests/az_connect_test.py @@ -1,6 +1,8 @@ -from azure.keyvault import KeyVaultClient - import pytest +from azure.core.exceptions import AzureError +from azure.identity import ChainedTokenCredential, ClientSecretCredential, ManagedIdentityCredential +from azure.keyvault.secrets import SecretClient + from myAzure.az_connect import AzureConnection diff --git a/backend/requirenments.txt b/backend/requirenments.txt index 57f961aab..fb60feeaa 100644 --- a/backend/requirenments.txt +++ b/backend/requirenments.txt @@ -1,45 +1,48 @@ -argon2-cffi -azure-storage-common -azure-storage-blob -azure-keyvault -azure-mgmt-keyvault -brotlipy -Django -django-filter -django-phonenumber-field -django-storages -django-split-settings -django-cors-headers -django-widget-tweaks -django-crispy-forms +argon2-cffi==19.2.0 +azure-identity==1.0.1 +azure-storage-common==2.1.0 +azure-storage-blob==2.1.0 +azure-keyvault-secrets==4.0.0 +azure-mgmt-keyvault==2.0.0 +brotlipy==0.7.0 +Django==2.2.7 +django-filter==2.2.0 +django-phonenumber-field==3.0.1 +django-storages==1.7.2 +django-split-settings==1.0.0 +django-cors-headers==3.1.1 +django-widget-tweaks==1.4.5 +django-crispy-forms==1.8.0 django-timezone-field -django-countries -django_extensions -django-ipware -django-debug-toolbar -django-graphiql-debug-toolbar -django-graphql-social-auth -django-graphql-jwt -dj-database-url -google-api-python-client -googleapis-common-protos -gunicorn -graphene-django -social-auth-core -social-auth-app-django +django-countries==5.5 +django-extensions==2.2.5 +django-ipware==2.1.0 +django-debug-toolbar==2.0 +django-graphiql-debug-toolbar== 0.1.4 +django-graphql-social-auth==0.1.4 +django-graphql-jwt==0.2.2 +dj-database-url==0.5.0 +google-api-python-client==1.7.11 +googleapis-common-protos==1.6.0 +gunicorn==20.0.0 +graphene-django==2.6.0 +social-auth-core==3.2.0 +social-auth-app-django==3.1.0 #git+git://github.com/python-social-auth/social-core.git#egg=elasticutils #git+git://github.com/python-social-auth/social-app-django.git#egg=social-app-django -markdown -pandas -phonenumbers -pint -Pillow -pinax-messages -pinax-templates -psycopg2-binary -pygraphviz -pymdown-extensions -python-dotenv -requests -sendgrid -Werkzeug +markdown==3.1.1 +pandas==0.25.3 +phonenumbers==8.10.22 +pytest==5.2.2 +pytest-django==3.7.0 +pint==0.9 +Pillow==6.2.1 +pinax-messages==2.0.2 +pinax-templates==2.0.2 +psycopg2-binary==2.8.4 +pygraphviz==1.5 +pymdown-extensions==6.1 +python-dotenv==0.10.3 +requests==2.22.0 +sendgrid==6.1.0 +Werkzeug==0.16.0 diff --git a/backend/setup.cfg b/backend/setup.cfg index d95c6eaca..0eb08e130 100644 --- a/backend/setup.cfg +++ b/backend/setup.cfg @@ -33,8 +33,10 @@ exclude = [tool:pytest] +DJANGO_SETTINGS_MODULE = melive.settings # Directories that are not visited by pytest collector: norecursedirs = hooks *.egg .eggs dist build docs .tox .git __pycache__ +python_files = tests.py test_*.py *_tests.py *_test.py # Extra options: addopts = diff --git a/backend/static/global_styles.css b/backend/static/global_styles.css index e88d7ec3d..3a02bd833 100644 --- a/backend/static/global_styles.css +++ b/backend/static/global_styles.css @@ -130,10 +130,10 @@ Sign Up Pages {logic, reset pswd, etc} /* User Profile */ -.iti__flag {background-image: url("https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/16.0.2/img/flags.png");} +.iti__flag {background-image: url("https://cdn.jsdelivr.net/npm/intl-tel-input@16.0.7/build/img/flags.png");} @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { - .iti__flag {background-image: url("https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/16.0.2/img/flags@2x.png");} + .iti__flag {background-image: url("https://cdn.jsdelivr.net/npm/intl-tel-input@16.0.7/build/img/flags@2x.png");} } /* Login diff --git a/backend/templates/base.html b/backend/templates/base.html index 568686291..2c435d70c 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -13,18 +13,6 @@ https://favicon.io/favicon-generator/?t=b40&ff=Jacques+Francois&fs=65&fc=%23000&b=rounded&bc=%23DDD https://www.1and1.com/favicon-generator --> - - - - - - - - - - {% include 'library_templates/js/bootstrap_fontAwe.html' %} @@ -34,11 +22,6 @@ {% block additional_head %}{% endblock %} - - {% include "library_templates/webtools_verification.html" %} - - {% include "library_templates/js/tracking_js.html" %} - {% include 'header.html' %} @@ -46,7 +29,6 @@ {% block content %} {% endblock %} - {% include 'footer.html' %} {% include "library_templates/js/bootstrap_fontAwe.html" %} diff --git a/backend/templates/footer.html b/backend/templates/footer.html deleted file mode 100644 index 2a5e70129..000000000 --- a/backend/templates/footer.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/backend/templates/library_templates/js/country_phone_libraries.html b/backend/templates/library_templates/js/country_phone_libraries.html deleted file mode 100644 index 4f6a81d12..000000000 --- a/backend/templates/library_templates/js/country_phone_libraries.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/backend/templates/library_templates/js/tracking_js.html b/backend/templates/library_templates/js/tracking_js.html deleted file mode 100644 index 29a4fa080..000000000 --- a/backend/templates/library_templates/js/tracking_js.html +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/backend/templates/library_templates/webtools_verification.html b/backend/templates/library_templates/webtools_verification.html deleted file mode 100644 index 17af2e715..000000000 --- a/backend/templates/library_templates/webtools_verification.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/backend/templates/robots.txt b/backend/templates/robots.txt index 87c59cb2e..79f736920 100644 --- a/backend/templates/robots.txt +++ b/backend/templates/robots.txt @@ -1,6 +1,4 @@ User-agent: * -Disallow: - Disallow: /admin/ Disallow: /administrace/ diff --git a/backend/userMng/pages/administrace/nav_sidebar.html b/backend/userMng/pages/administrace/nav_sidebar.html deleted file mode 100644 index 46a71194f..000000000 --- a/backend/userMng/pages/administrace/nav_sidebar.html +++ /dev/null @@ -1,89 +0,0 @@ -{% with url_name=request.resolver_match.url_name %} - - - -
- -
-{% endwith %} \ No newline at end of file diff --git a/backend/userMng/pages/administrace/user_adm_index.html b/backend/userMng/pages/administrace/user_adm_index.html deleted file mode 100644 index 2cbd5b2b3..000000000 --- a/backend/userMng/pages/administrace/user_adm_index.html +++ /dev/null @@ -1,104 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Home{% endblock %} - -{% block description %}This page has a description.{% endblock %} - -{% block robots %}noindex, nofollow{% endblock %} - -{% load crispy_forms_tags %} -{% load widget_tweaks %} -{% load pinax_messages_tags %} - -{% block content %} -
- -
- {% include 'nav_sidebar.html' %} - -
-

Welcome back {{ user.username }}

-

- -
- - -
-
-
-
Google Analytics Homepage Views
-

{{ number_of_views }}

-
-
-
- - -
- -
-
-
-
- On our Blog -
- -
- {% for blog_item in news_collection %} -
-

testing...

-
- {% endfor %} -
-
-
- -
-
-
- Do you want more features? -
- -
- Submit here or open an issue at GitHub. - -
- {% csrf_token %} - {{ form.non_field_errors | crispy }} - {{ form.source.errors | crispy }} - {{ form.source | crispy }} - -
- {{ form.inputFeedback.errors }} - {{ form.inputFeedback |add_class:"form-control"|attr:"rows:5"|attr:"cols:35"}} -
- - - -
-
-
-
-
-
-
-
- -{% endblock %} diff --git a/backend/userMng/pages/administrace/user_profile.html b/backend/userMng/pages/administrace/user_profile.html deleted file mode 100644 index cdcd2b273..000000000 --- a/backend/userMng/pages/administrace/user_profile.html +++ /dev/null @@ -1,191 +0,0 @@ -{% extends 'base.html' %} - -{% load static %} -{% load staticfiles %} - -{% block title %}Dashboard - User profile{% endblock %} - -{% block description %}This page has a description.{% endblock %} - -{% block robots %}index, follow{% endblock %} - -{% block additional_head %} - - {% include "base_splitting/country_phone_libraries.html" %} -{% endblock %} - -{% load crispy_forms_tags %} -{% load widget_tweaks %} -{% load countries %} -{% load tz %} - -{% block content %} -
- - -
- {% include 'nav_sidebar.html' %} - -
-
-
- My Profile picture - - My Profile picture -
-
-
-
- {% csrf_token %} -
-
- - -
-
- -
-
-
-
-
-
- -
-
- {% csrf_token %} - {{ form.non_field_errors | crispy }} - {{ form.source.errors | crispy }} - {{ form.source | crispy }} - -
-
- {{ form.user_gender.label_tag }} - {{ form.user_gender.errors }} - {{ form.user_gender | add_class:"form-control"}} -
-
- {{ form.first_name.label_tag }} - {{ form.first_name.errors }} - {{ form.first_name | add_class:"form-control"}} -
-
- {{ form.last_name.label_tag }} - {{ form.last_name.errors }} - {{ form.last_name | add_class:"form-control"}} -
-
- {{ form.user_first_lastname_visibility.label_tag }} - {{ form.user_first_lastname_visibility.errors }} - {{ form.user_first_lastname_visibility | add_class:"form-control"}} -
-
- {{ form.user_int_tel.label_tag }} - {{ form.user_int_tel.errors }} - -
-
- {{ form.user_country.label_tag }} - {{ form.user_country.errors }} - {{ form.user_country | add_class:"form-control"}} -
-
- {{ form.user_timezone.label_tag }} - {{ form.user_timezone.errors }} - {{ form.user_timezone | add_class:"form-control"}} -
-
- {{ form.user_units_system.label_tag }} - {{ form.user_units_system.errors }} - {{ form.user_units_system | add_class:"form-control"}} -
- -
- -
- -
- {% csrf_token %} - {{ form.non_field_errors | crispy }} - {{ form.source.errors | crispy }} - {{ form.source | crispy }} -
-
- - -
- -
- {{ form.inputUsername.label_tag }} - {{ form.inputUsername.errors }} - {{ form.inputUsername | add_class:"form-control"}} -
-
- -
- -
- {% csrf_token %} - {{ form.non_field_errors | crispy }} - {{ form.source.errors | crispy }} - {{ form.source | crispy }} -
-
- - -
- -
- {{ form.inputEmail.label_tag }} - {{ form.inputEmail.errors }} - {{ form.inputEmail | add_class:"form-control"}} -
-
- -
- -
- {% csrf_token %} - {{ form.non_field_errors | crispy }} - {{ form.source.errors | crispy }} - {{ form.source | crispy }} -
-
- {{ form.inputNewPassword.label_tag }} - {{ form.inputNewPassword.errors }} - {{ form.inputNewPassword | add_class:"form-control"}} -
-
- {{ form.inputConfirmNewPassword.label_tag }} - {{ form.inputConfirmNewPassword.errors }} - {{ form.inputConfirmNewPassword | add_class:"form-control"}} -
-
- -
-
- -
-
- - - -
-{% endblock %} \ No newline at end of file diff --git a/backend/userMng/pages/administrace/user_property.html b/backend/userMng/pages/administrace/user_property.html deleted file mode 100644 index 3a39511e1..000000000 --- a/backend/userMng/pages/administrace/user_property.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Dashboard - User profile{% endblock %} - -{% block description %}This page has a description.{% endblock %} - -{% block robots %}index, follow{% endblock %} - -{% block content %} -
- -
- {% include 'nav_sidebar.html' %} - -
-
- {% csrf_token %} - {{ form }} -
-
- -
- - - - - - -
-{% endblock %} \ No newline at end of file diff --git a/backend/userMng/third_party_services/google_analytics.py b/backend/userMng/third_party_services/google_analytics.py index fe8188f4b..b4b254cde 100644 --- a/backend/userMng/third_party_services/google_analytics.py +++ b/backend/userMng/third_party_services/google_analytics.py @@ -1,7 +1,11 @@ import logging import os +import sys -from azure.keyvault import KeyVaultClient +import requests +from azure.core.exceptions import AzureError +from azure.identity import ChainedTokenCredential, ClientSecretCredential, ManagedIdentityCredential +from azure.keyvault.secrets import SecretClient from google.auth.transport.requests import * from google.oauth2 import service_account from googleapiclient.discovery import * @@ -18,18 +22,26 @@ class Google_Analytics: https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/service-py """ - def returnAzureSecret(self): + def getAzureSecret(self): azCon = AzureConnection() azCon.main() - client = KeyVaultClient(azCon.credentials) - GOOGLE_ANALYTICS = client.get_secret( - "https://b40.vault.azure.net/", - "GOOGLE-ANAL", - "ab6ef2cc7d3846f199dcd149782a5d50", - ).value + client = SecretClient( + vault_url="https://b40.vault.azure.net/", credential=azCon.credentials + ) + GOOGLE_ANALYTICS = client.get_secret("GOOGLE-ANALYTICS-DROPBOX-LINK").value return GOOGLE_ANALYTICS - def initialize_analyticsreporting(self, ggl_client_key): + def download_file(self, dropbox_link): + """ + Download google analytics file from dropbox. Link to it is stored in Azure KV + """ + fl = os.path.abspath( + os.path.join(os.path.dirname(__file__), "client_secrets.json") + ) + r = requests.get(dropbox_link, allow_redirects=True) + open(fl, "wb").write(r.content) + + def initialize_analyticsreporting(self): """ Initializes an Analytics Reporting API V4 service object. @@ -39,16 +51,14 @@ def initialize_analyticsreporting(self, ggl_client_key): SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"] KEY_FILE_LOCATION = "client_secrets.json" - # try: - # fl = os.path.abspath( - # os.path.join(os.path.dirname(__file__), KEY_FILE_LOCATION) - # ) - # except Exception: - # logger.exception("clients_secrets.json not found on the server") + try: + fl = os.path.abspath( + os.path.join(os.path.dirname(__file__), KEY_FILE_LOCATION) + ) + except Exception: + logger.exception("clients_secrets.json not found on the server") - ga_credentials = service_account.Credentials.from_service_account_file( - ggl_client_key - ) + ga_credentials = service_account.Credentials.from_service_account_file(fl) scoped_credentials = ga_credentials.with_scopes(SCOPES) authed_session = AuthorizedSession(scoped_credentials) @@ -133,8 +143,9 @@ def print_response(self, response): return google_analytics_dimensions_metrics_dict - def main(self): - keyAzure = returnAzureSecret() - analytics = initialize_analyticsreporting(keyAzure) - response = get_report(analytics) - print_response(response) + # def main(self): + # keyAzure = GetAzureSecret() + # download_file(keyAzure) + # analytics = initialize_analyticsreporting() + # response = get_report(analytics) + # print_response(response) diff --git a/backend/userMng/third_party_services/tests/__init__.py b/backend/userMng/third_party_services/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/userMng/third_party_services/tests/dropbox_secret_test.py b/backend/userMng/third_party_services/tests/dropbox_secret_test.py new file mode 100644 index 000000000..9e65be1a2 --- /dev/null +++ b/backend/userMng/third_party_services/tests/dropbox_secret_test.py @@ -0,0 +1,31 @@ +import os +from os import path + +import pytest +import requests +from dotenv import find_dotenv, load_dotenv + +from myAzure.az_connect import AzureConnection + + +def test_LocalPC_download(): + load_dotenv(find_dotenv("secrets.env")) + link_db = os.getenv("dropbox_link") + + fl = os.path.abspath(os.path.join(os.path.dirname(__file__), "client_secrets.json")) + r = requests.get(link_db, allow_redirects=True) + open(fl, "wb").write(r.content) + + mes = None + if path.exists(fl): + mes = "Secret File for GA exists" + else: + mes = "GA secret json file does not exist" + + print(mes) + + assert mes == "Secret File for GA exists" + + +if __name__ == "__main__": + test_LocalPC_download() diff --git a/backend/userMng/views.py b/backend/userMng/views.py index 3203ded76..2b34a5cf6 100644 --- a/backend/userMng/views.py +++ b/backend/userMng/views.py @@ -19,7 +19,6 @@ from django.utils.http import * from django.utils.safestring import * from django.views import View - # a generic view for creating and saving an object (e.g. user) from django.views.generic.edit import CreateView @@ -97,8 +96,9 @@ def get(self, request): form = FeedbackForm() gh = Google_Analytics() - az_secret = gh.returnAzureSecret() - analytics = gh.initialize_analyticsreporting(az_secret) + keyDrop = gh.getAzureSecret() + gh.download_file(keyDrop) + analytics = gh.initialize_analyticsreporting() response = gh.get_report(analytics) number_of_views = gh.print_response(response) diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index b2409fb0f..f9434ca13 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,14 +1,210 @@ module.exports = { - root: true, env: { - node: true - }, - extends: ["plugin:vue/recommended", "@vue/prettier", "@vue/typescript"], - rules: { - "no-console": process.env.NODE_ENV === "production" ? "error" : "off", - "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" + browser: true, + es6: true }, + parser: "vue-eslint-parser", + extends: [ + "plugin:vue/recommended", + "@vue/typescript", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + ], parserOptions: { - parser: "@typescript-eslint/parser" + // Parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + ecmaVersion: 6, + sourceType: "module", + ecmaFeatures: { + jsx: true + } + }, + settings: { + "import/resolver": { + node: { + paths: ["src"] + } + } + }, + plugins: ["@typescript-eslint", "@typescript-eslint/tslint"], + rules: { + "@typescript-eslint/adjacent-overload-signatures": "warn", + "@typescript-eslint/array-type": "warn", + "@typescript-eslint/ban-types": "warn", + "@typescript-eslint/class-name-casing": "warn", + "@typescript-eslint/consistent-type-assertions": "warn", + "@typescript-eslint/consistent-type-definitions": "warn", + "@typescript-eslint/explicit-member-accessibility": [ + "warn", + { + accessibility: "explicit" + } + ], + "@typescript-eslint/indent": [ + "warn", + 2, + { + ObjectExpression: "first", + FunctionDeclaration: { + parameters: "first" + }, + FunctionExpression: { + parameters: "first" + } + } + ], + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/member-delimiter-style": [ + "error", + { + multiline: { + delimiter: "semi", + requireLast: true + }, + singleline: { + delimiter: "semi", + requireLast: false + } + } + ], + "@typescript-eslint/member-ordering": "warn", + "@typescript-eslint/no-empty-function": "warn", + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-misused-new": "warn", + "@typescript-eslint/no-namespace": "warn", + "@typescript-eslint/no-parameter-properties": "off", + "@typescript-eslint/no-this-alias": "warn", + "@typescript-eslint/no-use-before-declare": "off", + "@typescript-eslint/no-var-requires": "warn", + "@typescript-eslint/prefer-for-of": "warn", + "@typescript-eslint/prefer-function-type": "warn", + "@typescript-eslint/prefer-namespace-keyword": "warn", + "@typescript-eslint/quotes": ["warn", "double"], + "@typescript-eslint/semi": ["warn", "always"], + "@typescript-eslint/triple-slash-reference": "warn", + "@typescript-eslint/type-annotation-spacing": "warn", + "@typescript-eslint/unified-signatures": "warn", + "arrow-body-style": "warn", + "arrow-parens": ["warn", "as-needed"], + "camelcase": "warn", + "capitalized-comments": "warn", + "complexity": "off", + "constructor-super": "warn", + "curly": "warn", + "dot-notation": "warn", + "eol-last": "warn", + "eqeqeq": ["warn", "smart"], + "guard-for-in": "warn", + "id-blacklist": [ + "warn", + "any", + "Number", + "number", + "String", + "string", + "Boolean", + "boolean", + "Undefined", + "undefined" + ], + "id-match": "warn", + "max-classes-per-file": ["warn", 1], + "max-len": [ + "warn", + { + code: 120 + } + ], + "new-parens": "warn", + "no-bitwise": "warn", + "no-caller": "warn", + "no-cond-assign": "warn", + "no-console": "warn", + "no-debugger": "warn", + "no-duplicate-case": "warn", + "no-duplicate-imports": "warn", + "no-empty": "warn", + "no-eval": "warn", + "no-extra-bind": "warn", + "no-fallthrough": "off", + "no-invalid-this": "off", + "no-multiple-empty-lines": "off", + "no-new-func": "warn", + "no-new-wrappers": "warn", + "no-redeclare": "warn", + "no-return-await": "warn", + "no-sequences": "warn", + "no-shadow": [ + "warn", + { + hoist: "all" + } + ], + "no-sparse-arrays": "warn", + "no-template-curly-in-string": "warn", + "no-throw-literal": "warn", + "no-trailing-spaces": "warn", + "no-undef-init": "warn", + "no-underscore-dangle": "warn", + "no-unsafe-finally": "warn", + "no-unused-expressions": "warn", + "no-unused-labels": "warn", + "no-var": "warn", + "object-shorthand": "warn", + "one-var": ["warn", "never"], + "prefer-const": "warn", + "prefer-object-spread": "warn", + "quote-props": ["warn", "consistent-as-needed"], + "radix": "warn", + "space-before-function-paren": [ + "warn", + { + anonymous: "never", + asyncArrow: "always", + named: "never" + } + ], + "spaced-comment": "warn", + "use-isnan": "warn", + "valid-typeof": "off", + "@typescript-eslint/tslint/config": [ + "warn", + { + rules: { + "import-spacing": true, + "jsdoc-format": [true, "check-multiline-start"], + "no-reference-import": true, + "one-line": [ + true, + "check-catch", + "check-else", + "check-finally", + "check-open-brace", + "check-whitespace" + ], + "prefer-conditional-expression": true, + "trailing-comma": [ + true, + { + singleline: "never", + multiline: "always", + esSpecCompliant: true + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast", + "check-type-operator", + "check-rest-spread" + ] + } + } + ] } }; diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 13423fce4..9feb5cd13 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,14 +1,26 @@ -FROM node:current as node_cache -WORKDIR /cache/ -COPY package.json ./ -COPY yarn.lock ./ -RUN yarn add install +FROM node:latest as node_cache +RUN (set -e; \ + apt update; \ + apt install -y tree;\ + ) +WORKDIR /app/ +COPY *.json ./ +RUN ls -al +RUN yarn install +COPY . ./ +RUN tree src/ && yarn run build +# here we switch from nodejs container to +# having proper, scalable web server: Nginx +FROM nginx:latest +RUN rm /etc/nginx/conf.d/default.conf +#RUN (set -e; \ +# apt update; \ +# apt install -y software-properties-common brotli gpg; \ +# apt-add-repository --yes --update ppa:hda-me/nginx-stable; \ +# apt install nginx-module-brotli;\ +#) -FROM node:current -WORKDIR /app -COPY --from=node_cache /cache/ . -COPY . /app -RUN yarn global add local-web-server && yarn run build +COPY --from=node_cache /app/dist /usr/share/nginx/html EXPOSE 2746 -CMD ["ws", "--port=2746", "--directory=dist/"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/apollo.config.js b/frontend/apollo.config.js index 8f15e9c5b..4198dbb16 100644 --- a/frontend/apollo.config.js +++ b/frontend/apollo.config.js @@ -7,7 +7,7 @@ const env = loadEnv([ path.resolve(__dirname, ".env.local") ]); -// server and client properties for apollo CLI +// Server and client properties for apollo CLI // https://www.apollographql.com/docs/references/apollo-config/ module.exports = { client: { diff --git a/frontend/babel.config.js b/frontend/babel.config.js index 3ecebf1a5..397abca88 100644 --- a/frontend/babel.config.js +++ b/frontend/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - presets: ["@vue/app"] + presets: ["@vue/cli-plugin-babel/preset"] }; diff --git a/frontend/package.json b/frontend/package.json index d22398377..e623a65dd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,44 +1,66 @@ { "name": "django-wohn-frontend", - "version": "0.3.0", + "version": "0.3.4", "private": true, "scripts": { - "serve": "vue-cli-service serve --port 2746", + "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit", - "lint": "vue-cli-service lint" + "lint": "vue-cli-service lint --fix" }, "dependencies": { - "core-js": "^2.6.10", - "eslint-plugin-graphql": "^3.1.0", + "@azure/storage-blob": "^12.0.0", + "@babel/core": "^7.7.4", + "@fortawesome/fontawesome-free": "^5.11.2", + "@typescript-eslint/eslint-plugin-tslint": "^2.8.0", + "@typescript-eslint/parser": "^2.8.0", + "@vue/cli-shared-utils": "^4.0.5", + "apollo-client": "^2.6.4", + "apollo-link": "^1.2.13", + "axios": "^0.19.0", + "bootstrap": "^4.3.1", + "bootstrap-vue": "^2.1.0", + "core-js": "^3.4.2", + "eslint-plugin-vue": "^6.0.1", + "graphql": "^14.5.8", + "graphql-tag": "^2.10.1", + "jest": "23.6.0", + "jquery": "^3.4.1", + "popper.js": "^1.16.0", + "showdown": "^1.9.1", + "stylus": "^0.54.7", + "stylus-loader": "^3.0.2", + "ts-jest": "^23.10.5", + "tslint": "^5.20.1", + "typescript": "^3.7.2", "vue": "^2.6.10", "vue-apollo": "^3.0.0", + "vue-cli-plugin-apollo": "^0.21.3", "vue-router": "^3.1.3", - "vuex": "^3.1.1" + "vuex": "^3.1.2" }, "devDependencies": { "@types/jest": "^23.3.14", - "@vue/cli-plugin-babel": "^3.12.1", + "@types/showdown": "^1.9.3", + "@typescript-eslint/eslint-plugin": "^2.8.0", + "@vue/cli-plugin-babel": "^4.0.5", "@vue/cli-plugin-eslint": "^4.0.5", - "@vue/cli-plugin-typescript": "^3.12.1", - "@vue/cli-plugin-unit-jest": "^3.12.1", - "@vue/cli-service": "^3.12.1", - "@vue/eslint-config-prettier": "^5.0.0", + "@vue/cli-plugin-typescript": "^4.0.5", + "@vue/cli-plugin-unit-jest": "^4.0.5", + "@vue/cli-service": "^4.0.5", + "@vue/eslint-config-prettier": "^5.1.0", "@vue/eslint-config-typescript": "^4.0.0", + "@vue/eslint-plugin": "^4.2.0", "@vue/test-utils": "1.0.0-beta.29", "babel-core": "7.0.0-bridge.0", - "bootstrap-vue": "^2.0.4", - "eslint": "^5.16.0", + "compression-webpack-plugin": "^3.0.0", + "eslint": "^6.7.0", + "eslint-plugin-graphql": "^3.1.0", "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-vue": "^5.0.0", - "graphql-tag": "^2.10.1", - "prettier": "^1.18.2", - "stylus": "^0.54.7", - "stylus-loader": "^3.0.2", - "ts-jest": "^23.10.5", - "typescript": "^3.6.4", - "vue-cli-plugin-apollo": "^0.21.3", - "vue-template-compiler": "^2.6.10" + "intl-tel-input": "^16.0.7", + "prettier": "^1.19.1", + "vue-template-compiler": "^2.6.10", + "webpack": "^4.41.2" }, "postcss": { "plugins": { @@ -46,8 +68,7 @@ } }, "browserslist": [ - "> 1%", - "last 2 versions" + "defaults" ], "jest": { "moduleFileExtensions": [ diff --git a/frontend/public/index.html b/frontend/public/index.html index 351e3b905..28aac28d0 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -3,9 +3,13 @@ - - - djwohn-front + + + + + + +
+ + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 54d6e41e1..ea9ddf367 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,25 +1,40 @@ + diff --git a/frontend/src/assets/app.js b/frontend/src/assets/app.js new file mode 100644 index 000000000..a38c8776e --- /dev/null +++ b/frontend/src/assets/app.js @@ -0,0 +1,59 @@ +const open = "fa-eye"; +const close = "fa-eye-slash"; + +// For login +const passwordInput = document.getElementById("inputPassword"); +// For registration +const passwordNewInput = document.getElementById("inputNewPassword"); +const passwordConfirmNewInput = document.getElementById("inputConfirmNewPassword"); + +const icon = document.getElementById("buttonEYE"); +const icon2 = document.getElementById("buttonEYE2"); +const icon3 = document.getElementById("buttonEYE3"); + +if (typeof (icon) != "undefined" && icon != null) { + // If not null, then on click of the icon replace types & add/remove icon CSS classes + icon.addEventListener("click", evt => { + const svgIcon = evt.currentTarget.querySelector("svg"); + if (svgIcon.classList.contains(open)) { + passwordInput.type = "text"; + svgIcon.classList.remove(open); + svgIcon.classList.add(close); + } else { + passwordInput.type = "password"; + svgIcon.classList.remove(close); + svgIcon.classList.add(close); + } + }); +} + +if (typeof (icon2) != "undefined" && icon2 != null) { + icon2.addEventListener("click", evt => { + const svgIcon2 = evt.currentTarget.querySelector("svg"); + if (svgIcon2.classList.contains(open)) { + passwordNewInput.type = "text"; + svgIcon2.classList.remove(open); + svgIcon2.classList.add(close); + } else { + passwordNewInput.type = "password"; + svgIcon2.classList.remove(close); + svgIcon2.classList.add(open); + } + }); +} + +if (typeof (icon3) != "undefined" && icon3 != null) { + icon3.addEventListener("click", evt => { + const svgIcon3 = evt.currentTarget.querySelector("svg"); + if (svgIcon3.classList.contains(open)) { + passwordConfirmNewInput.type = "text"; + svgIcon3.classList.remove(open); + svgIcon3.classList.add(close); + } else { + passwordConfirmNewInput.type = "password"; + svgIcon3.classList.remove(close); + svgIcon3.classList.add(open); + } + }); +} + diff --git a/frontend/src/assets/app.styl b/frontend/src/assets/app.styl new file mode 100644 index 000000000..d1271a382 --- /dev/null +++ b/frontend/src/assets/app.styl @@ -0,0 +1,170 @@ +html + position relative + min-height 100% + +body + margin-bottom 4rem + +textarea + width 100% + +.headerFooterBackground + background-color #ECEEEF + +.resetIconStylesEYE + border none + background none + padding-top 0rem + padding-right 0.75rem + padding-bottom 0rem + padding-left 0.75rem + +.footer + position absolute + bottom 0 + left 0px + width 100% + line-height 1rem + padding-top 15px + +.footer_heartIcon + color red + +.grecaptcha-badge + bottom 4rem !important + +.card_homepage_styling, +.card_homepage_styling:hover, +.card_homepage_styling:visited, +.card_homepage_styling:link, +.card_homepage_styling:active + text-decoration none !important + color inherit + +.horizontal_line + width auto + height 30px + font-size .75rem + text-transform uppercase + +.no-messages + font-size 1.2rem + padding .5rem + background #f8f9fa + border 1px solid #e9ecef + color #868e96 + border-radius 5px + text-align center + margin-top 20px + +.with-actions + display flex + justify-content space-between + +.pinax-content-body + width 100% + padding-right 15px + padding-left 15px + margin-right auto + margin-left auto + +.responsive-map + width 100% + +.passwordReset + width 100% + max-width 420px + padding 15px + margin-bottom 100px + margin-top 80px + margin-right auto + margin-left auto + box-shadow -5px 1px 65px 16px rgba(0,0,0,0.31) + & > .form-group + &.spacing + margin-bottom 10px + +.privacyPolicyContentBox + border 1px solid black + +.iti__flag + background-image url("https://cdn.jsdelivr.net/npm/intl-tel-input@16.0.7/build/img/flags.png") + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) + .iti__flag + background-image url("https://cdn.jsdelivr.net/npm/intl-tel-input@16.0.7/build/img/flags@2x.png") + +.login_register_box + padding 50px 15px 50px 15px + +.login_register_box + & > .box_within + border 1px solid black + +.messageErrorLoginSignup + margin-top 6rem + +.google_signin + height 46px + width 240px + border-radius 4px + +.google_icon + float left + height 48px + width 48px + margin-left -13px + margin-top -8px + +.twitter_singin + height 46px + width 240px + border-radius 4px + +.twitter_icon + float left + height 40px + width 40px + margin-left -9px + margin-top -4px + background-color white + border-radius 3px + +.facebook_singin + height 46px + width 240px + border-radius 4px + background-color #4267B2 + +.facebook_icon + float left + height 40px + width 40px + margin-left -9px + margin-top -4px + +.asteriskField + display none + +.custom-display-text-size + font-size 2.5rem + font-weight 300 + line-height 1.2 + +@media(min-width: 544px) + .custom-display-text-size + font-size 3.5rem + font-weight 300 + line-height 1.2 + +@media (min-width: 768px) + .custom-display-text-size + font-size 5rem + font-weight 300 + line-height 1.2 + +@media (min-width: 992px) + .custom-display-text-size + font-size 6rem + font-weight 300 + line-height 1.2 diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue index 9b8c82294..abf8681f3 100644 --- a/frontend/src/components/HelloWorld.vue +++ b/frontend/src/components/HelloWorld.vue @@ -2,11 +2,13 @@

{{ msg }}

- For a guide and recipes on how to configure / customize this project,
+ For a guide and recipes on how to configure / customize this project,
check out the - vue-cli documentation. + vue-cli documentation.

Installed CLI Plugins

Essential Links

Ecosystem

diff --git a/frontend/src/components/Properties.vue b/frontend/src/components/Properties.vue new file mode 100644 index 000000000..fc0752822 --- /dev/null +++ b/frontend/src/components/Properties.vue @@ -0,0 +1,40 @@ + + + diff --git a/frontend/src/components/TheFooter.vue b/frontend/src/components/TheFooter.vue new file mode 100644 index 000000000..36e9e908a --- /dev/null +++ b/frontend/src/components/TheFooter.vue @@ -0,0 +1,69 @@ + + + diff --git a/frontend/src/components/TheHeader.vue b/frontend/src/components/TheHeader.vue new file mode 100644 index 000000000..7ed6328b3 --- /dev/null +++ b/frontend/src/components/TheHeader.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/components/TheNavSidebar.vue b/frontend/src/components/TheNavSidebar.vue new file mode 100644 index 000000000..f9e224af4 --- /dev/null +++ b/frontend/src/components/TheNavSidebar.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/frontend/src/components/error_handlers/NotFound.vue b/frontend/src/components/error_handlers/NotFound.vue new file mode 100644 index 000000000..948d1bc7c --- /dev/null +++ b/frontend/src/components/error_handlers/NotFound.vue @@ -0,0 +1,37 @@ + + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 453121128..57327b2c9 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,14 +1,20 @@ +import { BlobServiceClient } from "@azure/storage-blob"; +import BootstrapVue from "bootstrap-vue"; +import "bootstrap-vue/dist/bootstrap-vue.css"; +import "bootstrap/dist/css/bootstrap.css"; +import "@/assets/app.styl"; import Vue from "vue"; -import App from "./App.vue"; +import App from "./App"; import router from "./router"; import store from "./store"; import { createProvider } from "./vue-apollo"; Vue.config.productionTip = false; +Vue.use(BootstrapVue); new Vue({ router, store, apolloProvider: createProvider(), - render: h => h(App) + render: h => h(App), }).$mount("#app"); diff --git a/frontend/src/router.ts b/frontend/src/router.ts index cd8e18af7..af0024a73 100644 --- a/frontend/src/router.ts +++ b/frontend/src/router.ts @@ -1,42 +1,197 @@ +import Administrace from "./views/user_management/profile/Homepage.vue"; +import UserProfile from "./views/user_management/profile/UserProfile.vue"; +import UserProperties from "./views/user_management/profile/UserProperties.vue"; +import ResetPassword from "./views/user_management/ResetPassword.vue"; +import Login from "./views/user_management/Login.vue"; +import Register from "./views/user_management/Register.vue"; +import Contact from "./views/Contact.vue"; import Vue from "vue"; import Router from "vue-router"; -import Contact from "./views/Contact.vue"; import Home from "./views/Home.vue"; import Privacy from "./views/Privacy.vue"; import Terms from "./views/Terms.vue"; +import NotFound from "./views/404NotFound.vue"; +import PropertyID from "./views/PropertyID.vue"; + Vue.use(Router); -export default new Router({ +const router = new Router({ + mode: "history", routes: [ { path: "/", name: "home", - component: Home + component: Home, + meta: { + title: "Home Page - Student Housing in Czechia - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Home page", + }, + ], + }, }, { path: "/about", name: "about", - // route level code-splitting - // this generates a separate chunk (about.[hash].js) for this route - // which is lazy-loaded when the route is visited. + // Route level code-splitting + // This generates a separate chunk (about.[hash].js) for this route + // Which is lazy-loaded when the route is visited. component: () => - import(/* webpackChunkName: "about" */ "./views/About.vue") + import(/* WebpackChunkName: "about" */ "./views/About.vue"), + meta: { + title: "About Melive.xyz", + metaTags: [ + { + name: "description", + content: "About page - Learn more about people behind melive.xyz", + }, + ], + }, }, { path: "/contact", name: "contact", - component: Contact + component: Contact, + meta: { + title: "Contact owners & developers - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Contact page", + }, + ], + }, }, { path: "/terms", name: "terms", - component: Terms + component: Terms, + meta: { + title: "Terms of use - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Terms page - Your terms of use on melive.xyz", + }, + ], + }, }, { path: "/privacy", name: "privacy", - component: Privacy + component: Privacy, + meta: { + title: "Privacy policy - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Privacy page - Your privacy when using melive.xyz", + }, + ], + }, + }, + { + path: "/login", + name: "login", + component: Login, + meta: { + title: "Login - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Login page - Manage your real-estate listings", + }, + ], + }, + }, + { + path: "/register", + name: "register", + component: Register, + meta: { + title: "Register - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Sign up to Melive.xyz", + }, + ], + }, + }, + { + path: "/reset-password", + name: "resetpassword", + component: ResetPassword, + meta: { + title: "Reset your password - Melive.xyz", + metaTags: [ + { + name: "description", + content: "Reset your profile password on Melive.xyz", + }, + ], + }, + }, + { + path: "/administrace", + name: "administrace", + component: Administrace, + meta: { + title: "User Settings - Melive.xyz", + metaTags: [ + { + name: "description", + content: "User Setttings (administrace) page", + }, + ], + }, + children: [ + { + path: "profile", + name: "user-profile", + component: UserProfile, + meta: { + title: "User Profile", + }, + }, + { + path: "properties", + name: "user-properties", + component: UserProperties, + meta: { + title: "My properties", + }, + } + ] + }, + { + path: "/property/:id", + name: "property-id", + component: PropertyID + }, + { + // Any not listed above + path: "*", + name: "NotFound", + component: NotFound, + meta: { + title: "Try again - 404 error - Melive.xyz", + metaTags: [ + { + name: "description", + content: "We have not found what you have looked for. Try again.", + }, + ], + }, } - ] + ], +}); + +router.afterEach((to, from) => { + document.title = to.meta.title; }); + +export default router; diff --git a/frontend/src/shims-tsx.d.ts b/frontend/src/shims-tsx.d.ts index 2bcdf9fbc..4a6a6e6e5 100644 --- a/frontend/src/shims-tsx.d.ts +++ b/frontend/src/shims-tsx.d.ts @@ -2,9 +2,9 @@ import Vue, { VNode } from "vue"; declare global { namespace JSX { - // tslint:disable no-empty-interface + // Tslint:disable no-empty-interface interface Element extends VNode {} - // tslint:disable no-empty-interface + // Tslint:disable no-empty-interface interface ElementClass extends Vue {} interface IntrinsicElements { [elem: string]: any; diff --git a/frontend/src/store.ts b/frontend/src/store.ts index bb02ce2d2..c4a422071 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -6,5 +6,5 @@ Vue.use(Vuex); export default new Vuex.Store({ state: {}, mutations: {}, - actions: {} + actions: {}, }); diff --git a/frontend/src/support.ts b/frontend/src/support.ts new file mode 100644 index 000000000..d3885c1ad --- /dev/null +++ b/frontend/src/support.ts @@ -0,0 +1,5 @@ +const axios = require("axios").default; + +export function createHTMLfromMarkdown(URLlink: string) { + return axios.get(URLlink); +} diff --git a/frontend/src/views/404NotFound.vue b/frontend/src/views/404NotFound.vue new file mode 100644 index 000000000..4f9f12187 --- /dev/null +++ b/frontend/src/views/404NotFound.vue @@ -0,0 +1,25 @@ + + + + diff --git a/frontend/src/views/About.vue b/frontend/src/views/About.vue index 3fa28070d..4c8e46a70 100644 --- a/frontend/src/views/About.vue +++ b/frontend/src/views/About.vue @@ -1,5 +1,52 @@ + + + diff --git a/frontend/src/views/Contact.vue b/frontend/src/views/Contact.vue index e69de29bb..d202d69f9 100644 --- a/frontend/src/views/Contact.vue +++ b/frontend/src/views/Contact.vue @@ -0,0 +1,111 @@ +