diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6f0838 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9-slim + +ENV PYTHONUNBUFFERED=1 +WORKDIR /app +COPY requirements.txt /app/ +RUN pip install -r requirements.txt +COPY . /app/ +CMD ["python", "server/manage.py", "runserver", "0.0.0.0:8000"] + +EXPOSE 8000 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3ed32ec --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ +version: '3.9' + +services: + db: + image: postgres:17.0 + container_name: postgres-database + restart: always + ports: + - "5432:5432" + environment: + POSTGRES_DB: ${DATABASE_NAME} + POSTGRES_USER: ${DATABASE_USER} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + backend: + aliases: + - "db" + + back: + build: + dockerfile: Dockerfile + container_name: persistence + ports: + - "8000:8000" + volumes: + - .:/app + + networks: + backend: + aliases: + - "back" + +networks: + backend: + driver: bridge + +volumes: + postgres_data: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..85a22fd Binary files /dev/null and b/requirements.txt differ diff --git a/server/exercise/__init__.py b/server/exercise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/exercise/admin.py b/server/exercise/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/exercise/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/exercise/apps.py b/server/exercise/apps.py new file mode 100644 index 0000000..0e4b802 --- /dev/null +++ b/server/exercise/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ExerciseConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'exercise' diff --git a/server/exercise/migrations/__init__.py b/server/exercise/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/exercise/models.py b/server/exercise/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/server/exercise/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/server/exercise/tests.py b/server/exercise/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/exercise/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/exercise/views.py b/server/exercise/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/exercise/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server/gym_program/__init__.py b/server/gym_program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/gym_program/admin.py b/server/gym_program/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/gym_program/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/gym_program/apps.py b/server/gym_program/apps.py new file mode 100644 index 0000000..5dd1465 --- /dev/null +++ b/server/gym_program/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GymProgramConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'gym_program' diff --git a/server/gym_program/migrations/__init__.py b/server/gym_program/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/gym_program/models.py b/server/gym_program/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/server/gym_program/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/server/gym_program/tests.py b/server/gym_program/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/gym_program/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/gym_program/views.py b/server/gym_program/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/gym_program/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server/metric/__init__.py b/server/metric/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/metric/admin.py b/server/metric/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/metric/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/metric/apps.py b/server/metric/apps.py new file mode 100644 index 0000000..66260cf --- /dev/null +++ b/server/metric/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MetricConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'metric' diff --git a/server/metric/migrations/0001_initial.py b/server/metric/migrations/0001_initial.py new file mode 100644 index 0000000..75f1067 --- /dev/null +++ b/server/metric/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.16 on 2024-10-05 14:16 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Metric', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], default='M', max_length=1)), + ('weight', models.FloatField(default=0.0, validators=[django.core.validators.MinValueValidator(0.0)])), + ('height', models.FloatField(default=0.0, validators=[django.core.validators.MinValueValidator(0.0)])), + ('birth_date', models.DateField(default='2000-01-01')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/server/metric/migrations/__init__.py b/server/metric/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/metric/models.py b/server/metric/models.py new file mode 100644 index 0000000..9715e3a --- /dev/null +++ b/server/metric/models.py @@ -0,0 +1,23 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.core.validators import MinValueValidator + + +class Metric(models.Model): + class Gender(models.TextChoices): + MALE = 'M', _('Male') + FEMALE = 'F', _('Female') + + user = models.OneToOneField('user.User', on_delete=models.CASCADE) + gender = models.CharField( + max_length=1, + choices=Gender.choices, + default=Gender.MALE, + blank=False, + ) + weight = models.FloatField(blank=False, validators=[MinValueValidator(0.0)], default=0.0) + height = models.FloatField(blank=False, validators=[MinValueValidator(0.0)], default=0.0) + birth_date = models.DateField(blank=False, null=False, default='2000-01-01') + + def __str__(self): + return f"{self.gender}, Weight: {self.weight}, Height: {self.height}, Birth date: {self.birth_date}" diff --git a/server/metric/serializers.py b/server/metric/serializers.py new file mode 100644 index 0000000..150bb1a --- /dev/null +++ b/server/metric/serializers.py @@ -0,0 +1,13 @@ +from rest_framework import serializers +from .models import Metric + + +class MetricSerializer(serializers.ModelSerializer): + class Meta: + model = Metric + fields = ('id', + 'gender', + 'weight', + 'height', + 'birth_date' + ) diff --git a/server/metric/tests.py b/server/metric/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/metric/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/metric/urls.py b/server/metric/urls.py new file mode 100644 index 0000000..9d24ccf --- /dev/null +++ b/server/metric/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('api/metrics/', views.metric_list), + path('api/metric/', views.metric_detail), +] diff --git a/server/metric/views.py b/server/metric/views.py new file mode 100644 index 0000000..59d564d --- /dev/null +++ b/server/metric/views.py @@ -0,0 +1,46 @@ +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework import status +from django.shortcuts import get_object_or_404 +from .models import Metric +from .serializers import MetricSerializer + + +@api_view(['GET', 'POST']) +def metric_list(request): + if request.method == 'GET': + metrics = Metric.objects.all() + serializer = MetricSerializer(metrics, many=True) + return Response(serializer.data) + + +@api_view(['GET', 'PUT', 'POST', 'DELETE']) +def metric_detail(request): + user = request.user + + if request.method == 'GET': + metric = get_object_or_404(Metric, user=user) + serializer = MetricSerializer(metric) + return Response(serializer.data) + + elif request.method == 'PUT': + metric = get_object_or_404(Metric, user=user) + serializer = MetricSerializer(metric, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + elif request.method == 'POST': + if Metric.objects.filter(user=user).exists(): + return Response({'detail': 'Metric already exists for this user.'}, status=status.HTTP_400_BAD_REQUEST) + serializer = MetricSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(user=user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + elif request.method == 'DELETE': + metric = get_object_or_404(Metric, user=user) + metric.delete() + return Response({'detail': 'Metric deleted successfully.'}, status=status.HTTP_204_NO_CONTENT) diff --git a/server/server/settings.py b/server/server/settings.py index 09cdd28..d5558bc 100644 --- a/server/server/settings.py +++ b/server/server/settings.py @@ -9,23 +9,26 @@ 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 datetime import timedelta +import environ from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +env = environ.Env() +environ.Env.read_env(BASE_DIR / '../.env') # 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-2edm6&uow19ulf2@+x%q@e+9!6$6u852^s%!o6k(1z^605h%v4' +SECRET_KEY = env('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = env.bool('DEBUG', default=False) -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[]) # Application definition @@ -37,6 +40,17 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', + 'rest_framework', + 'rest_framework.authtoken', + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'dj_rest_auth', + 'dj_rest_auth.registration', + 'corsheaders', + 'user.apps.UserConfig', + 'metric.apps.MetricConfig', ] MIDDLEWARE = [ @@ -47,8 +61,15 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'allauth.account.middleware.AccountMiddleware', ] +CORS_ORIGIN_ALLOW_ALL = False +CORS_ORIGIN_WHITELIST = ( + 'http://localhost:8080', +) + ROOT_URLCONF = 'server.urls' TEMPLATES = [ @@ -75,8 +96,12 @@ WSGI_APPLICATION = 'server.wsgi.application' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': env('DATABASE_NAME'), + 'USER': env('DATABASE_USER'), + 'PASSWORD': env('DATABASE_PASSWORD'), + 'HOST': env('DATABASE_HOST'), + 'PORT': env('DATABASE_PORT'), } } @@ -99,6 +124,27 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +SITE_ID = 1 +DJANGO_REST_AUTH_TOKEN_MODEL = None + +ACCOUNT_USER_MODEL_USERNAME_FIELD = None +ACCOUNT_AUTHENTICATION_METHOD = 'email' +ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_UNIQUE_EMAIL = True +ACCOUNT_USERNAME_REQUIRED = False +ACCOUNT_USER_EMAIL_FIELD = 'email' +ACCOUNT_LOGOUT_ON_GET = False +ACCOUNT_EMAIL_VERIFICATION = 'none' + +AUTH_USER_MODEL = 'user.User' + +REST_AUTH_SERIALIZERS = { + "USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer", +} +REST_AUTH_REGISTER_SERIALIZERS = { + "REGISTER_SERIALIZER": "user.serializers.CustomRegisterSerializer", +} + # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ diff --git a/server/server/urls.py b/server/server/urls.py index c47b8bd..fe98614 100644 --- a/server/server/urls.py +++ b/server/server/urls.py @@ -15,8 +15,11 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('auth/', include('dj_rest_auth.urls')), + path('auth/registration/', include('dj_rest_auth.registration.urls')), + path('', include('metric.urls')), ] diff --git a/server/training_day/__init__.py b/server/training_day/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/training_day/admin.py b/server/training_day/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/training_day/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/training_day/apps.py b/server/training_day/apps.py new file mode 100644 index 0000000..da3aed6 --- /dev/null +++ b/server/training_day/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TrainingDayConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'training_day' diff --git a/server/training_day/migrations/__init__.py b/server/training_day/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/training_day/models.py b/server/training_day/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/server/training_day/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/server/training_day/tests.py b/server/training_day/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/training_day/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/training_day/views.py b/server/training_day/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/training_day/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server/user/__init__.py b/server/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/user/admin.py b/server/user/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/user/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/user/apps.py b/server/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/server/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/server/user/migrations/0001_initial.py b/server/user/migrations/0001_initial.py new file mode 100644 index 0000000..4566677 --- /dev/null +++ b/server/user/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.16 on 2024-10-05 11:19 + +from django.db import migrations, models +import user.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('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')), + ('email', models.EmailField(max_length=254, unique=True)), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('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={ + 'abstract': False, + }, + managers=[ + ('objects', user.models.UserManager()), + ], + ), + ] diff --git a/server/user/migrations/__init__.py b/server/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/user/models.py b/server/user/models.py new file mode 100644 index 0000000..f16a088 --- /dev/null +++ b/server/user/models.py @@ -0,0 +1,35 @@ +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.db import models + + +class UserManager(BaseUserManager): + use_in_migrations = True + + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError("Email address must be provided") + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + + return self.create_user(email, password, **extra_fields) + + +class User(AbstractBaseUser, PermissionsMixin): + email = models.EmailField(unique=True) + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + + objects = UserManager() + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = [] + + def __str__(self): + return self.email diff --git a/server/user/serializers.py b/server/user/serializers.py new file mode 100644 index 0000000..ef8ded3 --- /dev/null +++ b/server/user/serializers.py @@ -0,0 +1,26 @@ +from dj_rest_auth.registration.serializers import RegisterSerializer +from rest_framework import serializers +from .models import User + + +class CustomRegisterSerializer(RegisterSerializer): + email = serializers.EmailField(required=True) + password1 = serializers.CharField(write_only=True) + password2 = serializers.CharField(write_only=True) + + def get_cleaned_data(self): + super(CustomRegisterSerializer, self).get_cleaned_data() + + return { + 'email': self.validated_data.get('email', ''), + 'password1': self.validated_data.get('password1', ''), + 'password2': self.validated_data.get('password2', ''), + } + + +class CustomUserDetailsSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('email',) + read_only_fields = ('email',) + diff --git a/server/user/tests.py b/server/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/user/views.py b/server/user/views.py new file mode 100644 index 0000000..ed4d785 --- /dev/null +++ b/server/user/views.py @@ -0,0 +1,6 @@ +from dj_rest_auth.registration.views import RegisterView +from .models import User + + +class CustomRegisterView(RegisterView): + queryset = User.objects.all()