From 3083bc5ffaefefcca3c58eac6c4ab3ba101fb0d7 Mon Sep 17 00:00:00 2001 From: ivinayakg Date: Mon, 11 Sep 2023 00:36:39 +0530 Subject: [PATCH] refactor 11th sep 2023 --- .env.example | 2 +- .gitignore | 4 +- .vscode/launch.json | 19 ++++ {base => apps/base}/__init__.py | 0 {base => apps/base}/admin.py | 0 {base => apps/base}/apps.py | 2 +- {base => apps/base}/base_views.py | 0 {base => apps/base}/models.py | 0 apps/base/permissions.py | 6 ++ apps/base/test_utils.py | 4 + {base => apps/base}/tests.py | 0 {base => apps/base}/utils.py | 0 apps/conftest.py | 31 +++++++ {django_jsonapi => apps/goals}/__init__.py | 0 {goals => apps/goals}/admin.py | 2 +- {goals => apps/goals}/apps.py | 2 +- apps/goals/fixtures.py | 32 +++++++ {goals => apps/goals}/fixtures/goals.json | 0 .../goals}/migrations/0001_initial.py | 0 .../goals/migrations}/__init__.py | 0 {goals => apps/goals}/models.py | 5 +- {goals => apps/goals}/serializers.py | 2 +- {goals => apps/goals/tests}/__init__.py | 0 apps/goals/tests/test_drf_views.py | 51 ++++++++++ apps/goals/urls.py | 11 +++ apps/goals/views.py | 11 +++ {goals/migrations => apps/user}/__init__.py | 0 apps/user/admin.py | 11 +++ {user => apps/user}/apps.py | 2 +- apps/user/authentications.py | 32 +++++++ apps/user/managers.py | 27 ++++++ apps/user/migrations/0001_initial.py | 37 ++++++++ {user => apps/user/migrations}/__init__.py | 0 apps/user/models.py | 21 +++++ {user => apps/user}/permission.py | 7 +- .../user/tests}/__init__.py | 0 apps/user/tests/test_drf_views.py | 59 ++++++++++++ apps/user/urls.py | 11 +++ apps/user/utils.py | 0 apps/user/v1/serializer.py | 26 ++++++ apps/user/v1/views.py | 32 +++++++ apps/user/views.py | 30 ++++++ config/__init__.py | 0 {django_jsonapi => config}/asgi.py | 4 +- {django_jsonapi => config}/sample.env | 0 config/settings/__init__.py | 0 {django_jsonapi => config}/settings/base.py | 50 ++++++++-- .../settings/development.py | 2 +- .../settings/production.py | 0 .../settings/sample-local.py | 0 .../testing.py => config/settings/test.py | 0 {django_jsonapi => config}/urls.py | 11 ++- {django_jsonapi => config}/wsgi.py | 4 +- docs/README.md | 12 +-- goals/fixtures.py | 82 ----------------- goals/tests.py | 44 --------- goals/views.py | 9 -- manage.py | 6 +- pytest.ini | 10 ++ requirements.txt | 5 + sample.env | 4 +- user/authentications.py | 32 ------- user/migrations/0001_initial.py | 31 ------- user/models.py | 40 -------- user/tests.py | 92 ------------------- user/utils.py | 10 -- user/v1/serializer.py | 7 -- user/v1/views.py | 13 --- user/views.py | 30 ------ 69 files changed, 545 insertions(+), 432 deletions(-) create mode 100644 .vscode/launch.json rename {base => apps/base}/__init__.py (100%) rename {base => apps/base}/admin.py (100%) rename {base => apps/base}/apps.py (84%) rename {base => apps/base}/base_views.py (100%) rename {base => apps/base}/models.py (100%) create mode 100644 apps/base/permissions.py create mode 100644 apps/base/test_utils.py rename {base => apps/base}/tests.py (100%) rename {base => apps/base}/utils.py (100%) create mode 100644 apps/conftest.py rename {django_jsonapi => apps/goals}/__init__.py (100%) rename {goals => apps/goals}/admin.py (95%) rename {goals => apps/goals}/apps.py (83%) create mode 100644 apps/goals/fixtures.py rename {goals => apps/goals}/fixtures/goals.json (100%) rename {goals => apps/goals}/migrations/0001_initial.py (100%) rename {django_jsonapi/settings => apps/goals/migrations}/__init__.py (100%) rename {goals => apps/goals}/models.py (88%) rename {goals => apps/goals}/serializers.py (90%) rename {goals => apps/goals/tests}/__init__.py (100%) create mode 100644 apps/goals/tests/test_drf_views.py create mode 100644 apps/goals/urls.py create mode 100644 apps/goals/views.py rename {goals/migrations => apps/user}/__init__.py (100%) create mode 100644 apps/user/admin.py rename {user => apps/user}/apps.py (84%) create mode 100644 apps/user/authentications.py create mode 100644 apps/user/managers.py create mode 100644 apps/user/migrations/0001_initial.py rename {user => apps/user/migrations}/__init__.py (100%) create mode 100644 apps/user/models.py rename {user => apps/user}/permission.py (60%) rename {user/migrations => apps/user/tests}/__init__.py (100%) create mode 100644 apps/user/tests/test_drf_views.py create mode 100644 apps/user/urls.py create mode 100644 apps/user/utils.py create mode 100644 apps/user/v1/serializer.py create mode 100644 apps/user/v1/views.py create mode 100644 apps/user/views.py create mode 100644 config/__init__.py rename {django_jsonapi => config}/asgi.py (71%) rename {django_jsonapi => config}/sample.env (100%) create mode 100644 config/settings/__init__.py rename {django_jsonapi => config}/settings/base.py (80%) rename {django_jsonapi => config}/settings/development.py (93%) rename {django_jsonapi => config}/settings/production.py (100%) rename {django_jsonapi => config}/settings/sample-local.py (100%) rename django_jsonapi/settings/testing.py => config/settings/test.py (100%) rename {django_jsonapi => config}/urls.py (51%) rename {django_jsonapi => config}/wsgi.py (77%) delete mode 100644 goals/fixtures.py delete mode 100644 goals/tests.py delete mode 100644 goals/views.py create mode 100644 pytest.ini delete mode 100644 user/authentications.py delete mode 100644 user/migrations/0001_initial.py delete mode 100644 user/models.py delete mode 100644 user/tests.py delete mode 100644 user/utils.py delete mode 100644 user/v1/serializer.py delete mode 100644 user/v1/views.py delete mode 100644 user/views.py diff --git a/.env.example b/.env.example index 089c82f..1319fe9 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ DJANGO_ENV=DEVELOPMENT PORT=5000 SECRET_KEY='SECRET_KEY' -REST_KEY=123456789 +RDS_BACKEND_SECRET_KEY=123456789 AUTH_TOKEN_INVALIDATION_TIME_IN_SECONDS=2592000 # ALLOWED_HOSTS accepts string as value with space as delimiter diff --git a/.gitignore b/.gitignore index 76c307c..96559e9 100644 --- a/.gitignore +++ b/.gitignore @@ -139,7 +139,7 @@ GitHub.sublime-settings # Ignoring .env file -django_jsonapi/.env +config/.env # Ignore local setting file -django_jsonapi/settings/local.py +config/settings/local.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..654995d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Django", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": [ + "runserver" + ], + "django": true, + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/base/__init__.py b/apps/base/__init__.py similarity index 100% rename from base/__init__.py rename to apps/base/__init__.py diff --git a/base/admin.py b/apps/base/admin.py similarity index 100% rename from base/admin.py rename to apps/base/admin.py diff --git a/base/apps.py b/apps/base/apps.py similarity index 84% rename from base/apps.py rename to apps/base/apps.py index 05011e8..6050143 100644 --- a/base/apps.py +++ b/apps/base/apps.py @@ -3,4 +3,4 @@ class BaseConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'base' + name = 'apps.base' diff --git a/base/base_views.py b/apps/base/base_views.py similarity index 100% rename from base/base_views.py rename to apps/base/base_views.py diff --git a/base/models.py b/apps/base/models.py similarity index 100% rename from base/models.py rename to apps/base/models.py diff --git a/apps/base/permissions.py b/apps/base/permissions.py new file mode 100644 index 0000000..ab2ea6f --- /dev/null +++ b/apps/base/permissions.py @@ -0,0 +1,6 @@ +from rest_framework import permissions + + +class IsSuperUserPermission(permissions.BasePermission): + def has_permission(self, request, view): + return False diff --git a/apps/base/test_utils.py b/apps/base/test_utils.py new file mode 100644 index 0000000..3ca3141 --- /dev/null +++ b/apps/base/test_utils.py @@ -0,0 +1,4 @@ +from rest_framework_simplejwt.tokens import RefreshToken + +def get_user_token(user): + return f'Bearer {str(RefreshToken.for_user(user).access_token)}' \ No newline at end of file diff --git a/base/tests.py b/apps/base/tests.py similarity index 100% rename from base/tests.py rename to apps/base/tests.py diff --git a/base/utils.py b/apps/base/utils.py similarity index 100% rename from base/utils.py rename to apps/base/utils.py diff --git a/apps/conftest.py b/apps/conftest.py new file mode 100644 index 0000000..de727b6 --- /dev/null +++ b/apps/conftest.py @@ -0,0 +1,31 @@ +import pytest + +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.test import APITestCase, APIClient +from django.contrib.auth import get_user_model + +User = get_user_model() + + +def get_user_token(user): + return f'Bearer {str(RefreshToken.for_user(user).access_token)}' + + +@pytest.fixture +def new_user_factory(db): + def create_app_user(rds_id): + user = User.objects.create_user(rds_id=rds_id) + return user + + yield create_app_user + + +@pytest.fixture +def user_t1(db, new_user_factory): + user = new_user_factory("hello_1") + return user + + +@pytest.fixture +def user_t2(db, new_user_factory): + return new_user_factory("hello_2") diff --git a/django_jsonapi/__init__.py b/apps/goals/__init__.py similarity index 100% rename from django_jsonapi/__init__.py rename to apps/goals/__init__.py diff --git a/goals/admin.py b/apps/goals/admin.py similarity index 95% rename from goals/admin.py rename to apps/goals/admin.py index f6afd5e..5a24994 100644 --- a/goals/admin.py +++ b/apps/goals/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from safedelete.admin import SafeDeleteAdmin, highlight_deleted -from goals.models import Goal +from apps.goals.models import Goal # Register your models here. diff --git a/goals/apps.py b/apps/goals/apps.py similarity index 83% rename from goals/apps.py rename to apps/goals/apps.py index 7adf7ee..48f8359 100644 --- a/goals/apps.py +++ b/apps/goals/apps.py @@ -3,4 +3,4 @@ class GoalsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'goals' + name = 'apps.goals' diff --git a/apps/goals/fixtures.py b/apps/goals/fixtures.py new file mode 100644 index 0000000..da88fa3 --- /dev/null +++ b/apps/goals/fixtures.py @@ -0,0 +1,32 @@ +goals = [ + { + "model": "goals.goal", + "fields": { + "title": "Goal 1", + "description": "This is the description for Goal 1", + "created_at": "2023-08-04T12:00:00Z", + "created_by": "User 1", + "assigned_to": "User A", + "starts_on": "2023-08-05T00:00:00Z", + "ends_on": "2023-08-10T00:00:00Z", + "percentage_completed": 20, + "assigned_by": "Admin", + "status": "In Progress" + } + }, + { + "model": "goals.goal", + "fields": { + "title": "Goal 2", + "description": "This is the description for Goal 2", + "created_at": "2023-08-04T13:00:00Z", + "created_by": "User 2", + "assigned_to": "User B", + "starts_on": "2023-08-06T00:00:00Z", + "ends_on": "2023-08-12T00:00:00Z", + "percentage_completed": 50, + "assigned_by": "Admin", + "status": "In Progress" + } + } +] diff --git a/goals/fixtures/goals.json b/apps/goals/fixtures/goals.json similarity index 100% rename from goals/fixtures/goals.json rename to apps/goals/fixtures/goals.json diff --git a/goals/migrations/0001_initial.py b/apps/goals/migrations/0001_initial.py similarity index 100% rename from goals/migrations/0001_initial.py rename to apps/goals/migrations/0001_initial.py diff --git a/django_jsonapi/settings/__init__.py b/apps/goals/migrations/__init__.py similarity index 100% rename from django_jsonapi/settings/__init__.py rename to apps/goals/migrations/__init__.py diff --git a/goals/models.py b/apps/goals/models.py similarity index 88% rename from goals/models.py rename to apps/goals/models.py index 8dd6a6e..cced517 100644 --- a/goals/models.py +++ b/apps/goals/models.py @@ -1,6 +1,6 @@ from django.db import models -from base.models import BaseModel +from apps.base.models import BaseModel class GoalStatusChoices(models.TextChoices): @@ -18,3 +18,6 @@ class Goal(BaseModel): percentage_completed = models.IntegerField(default=0) assigned_by = models.CharField(max_length=200, blank=True) status = models.CharField(max_length=70, choices=GoalStatusChoices.choices, default=GoalStatusChoices.ONGOING) + + def __str__(self) -> str: + return self.title diff --git a/goals/serializers.py b/apps/goals/serializers.py similarity index 90% rename from goals/serializers.py rename to apps/goals/serializers.py index eb70a90..f682a0a 100644 --- a/goals/serializers.py +++ b/apps/goals/serializers.py @@ -1,5 +1,5 @@ from rest_framework_json_api import serializers -from goals.models import Goal +from apps.goals.models import Goal class GoalSerializer(serializers.ModelSerializer): class Meta: diff --git a/goals/__init__.py b/apps/goals/tests/__init__.py similarity index 100% rename from goals/__init__.py rename to apps/goals/tests/__init__.py diff --git a/apps/goals/tests/test_drf_views.py b/apps/goals/tests/test_drf_views.py new file mode 100644 index 0000000..6d05d44 --- /dev/null +++ b/apps/goals/tests/test_drf_views.py @@ -0,0 +1,51 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIClient +from django.conf import LazySettings + +from apps.conftest import get_user_token +from apps.goals.fixtures import goals +from apps.goals.models import Goal + +settings = LazySettings() + + +@pytest.mark.django_db +class TestGoalsViewSet: + @pytest.fixture(autouse=True) + def setup(self): + self.client = APIClient() + for goal in goals: + Goal.objects.create(**goal["fields"]) + + def test_get_goals(self, user_t1): + self.client.credentials(HTTP_AUTHORIZATION=get_user_token(user_t1)) + _response = self.client.get('/api/v1/goal/', format="vnd.api+json") + _response_data = _response.json() + + _response_goals_data = _response_data["data"] + _response_goal_data = _response_goals_data[0]["attributes"] + + assert _response.status_code == status.HTTP_200_OK + assert len(_response_goals_data) > 0 + assert "title" in _response_goal_data + assert "description" in _response_goal_data + assert "assigned_by" in _response_goal_data + assert "status" in _response_goal_data + + def test_get_goals_filtered(self, user_t1): + goal_title = goals[1]["fields"]["title"] + self.client.credentials(HTTP_AUTHORIZATION=get_user_token(user_t1)) + _response = self.client.get(f'/api/v1/goal/?title={goal_title}', format="vnd.api+json") + _response_data = _response.json() + + _response_goals_data = _response_data["data"] + _response_goal_data = _response_goals_data[0]["attributes"] + + assert _response.status_code == status.HTTP_200_OK + assert len(_response_goals_data) > 0 + assert "title" in _response_goal_data + assert goal_title == _response_goal_data["title"] + assert "description" in _response_goal_data + assert "assigned_by" in _response_goal_data + assert "status" in _response_goal_data diff --git a/apps/goals/urls.py b/apps/goals/urls.py new file mode 100644 index 0000000..1eb0cff --- /dev/null +++ b/apps/goals/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from rest_framework import routers +from django.urls import path +from apps.goals.views import GoalViewSet + +router = routers.DefaultRouter() +router.register(r'goal', GoalViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] diff --git a/apps/goals/views.py b/apps/goals/views.py new file mode 100644 index 0000000..b3a0573 --- /dev/null +++ b/apps/goals/views.py @@ -0,0 +1,11 @@ +from apps.base.base_views import ModelBaseViewSet +from apps.goals.models import Goal +from apps.goals.serializers import GoalSerializer +from rest_framework.permissions import IsAuthenticated + + +class GoalViewSet(ModelBaseViewSet): + queryset = Goal.objects.all().order_by('-created_at') + serializer_class = GoalSerializer + filterset_fields = ['status', 'title', 'assigned_to'] + permission_classes = [IsAuthenticated] diff --git a/goals/migrations/__init__.py b/apps/user/__init__.py similarity index 100% rename from goals/migrations/__init__.py rename to apps/user/__init__.py diff --git a/apps/user/admin.py b/apps/user/admin.py new file mode 100644 index 0000000..bf5fc2b --- /dev/null +++ b/apps/user/admin.py @@ -0,0 +1,11 @@ + +from django.contrib import admin +from apps.user.models import User +from safedelete.admin import SafeDeleteAdmin, highlight_deleted + + +@admin.register(User) +class UserAdmin(SafeDeleteAdmin): + list_display = ["id", "rds_id", "created_at", "modified_at"] + search_fields = ('rds_id', 'id') + ordering = ('created_at', ) diff --git a/user/apps.py b/apps/user/apps.py similarity index 84% rename from user/apps.py rename to apps/user/apps.py index 36cce4c..172a94e 100644 --- a/user/apps.py +++ b/apps/user/apps.py @@ -3,4 +3,4 @@ class UserConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'user' + name = 'apps.user' diff --git a/apps/user/authentications.py b/apps/user/authentications.py new file mode 100644 index 0000000..9421cb8 --- /dev/null +++ b/apps/user/authentications.py @@ -0,0 +1,32 @@ +# from .models import User +# from rest_framework import authentication +# from rest_framework import exceptions + + +# class UserAuthenticationExtender(): +# def __init__(self, auth_token, userId, created): +# self.auth_token = auth_token +# self.user_id = userId +# self.token_created = created +# self.is_authenticated = True + + +# class UserAuthentication(authentication.BaseAuthentication): +# def authenticate(self, request): +# try: +# goal_token = request.COOKIES['goal-site-session'] +# except Exception: +# return None + +# try: +# token = User.objects.get(auth_token=goal_token) +# if token.is_invalid(): +# raise exceptions.AuthenticationFailed( +# "Token is expired or invalid") +# except User.DoesNotExist: +# raise exceptions.AuthenticationFailed( +# 'Token is expired or invalid') + +# user = UserAuthenticationExtender( +# token.auth_token, token.user_id, token.token_created) +# return (user, None) diff --git a/apps/user/managers.py b/apps/user/managers.py new file mode 100644 index 0000000..5789fef --- /dev/null +++ b/apps/user/managers.py @@ -0,0 +1,27 @@ +from django.contrib.auth.models import BaseUserManager +import string +from django.utils.crypto import get_random_string + + +class UserManager(BaseUserManager): + def create_user(self, rds_id, password=None, **extra_fields): + if not rds_id: + raise ValueError('The rds_id field must be set') + user = self.model(rds_id=rds_id, **extra_fields) + if password is None: + password = self._generate_random_password() + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, rds_id, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + + return self.create_user(rds_id, password, **extra_fields) + + @staticmethod + def _generate_random_password(): + length = 50 # You can adjust the password length as needed + chars = string.ascii_letters + string.digits + string.punctuation + return get_random_string(length, chars) diff --git a/apps/user/migrations/0001_initial.py b/apps/user/migrations/0001_initial.py new file mode 100644 index 0000000..1a09f1a --- /dev/null +++ b/apps/user/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 4.0.9 on 2023-09-10 10:24 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('deleted', models.DateTimeField(db_index=True, editable=False, null=True)), + ('deleted_by_cascade', models.BooleanField(default=False, editable=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('modified_at', models.DateTimeField(auto_now=True)), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('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')), + ('rds_id', models.CharField(blank=True, max_length=80, 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, + }, + ), + ] diff --git a/user/__init__.py b/apps/user/migrations/__init__.py similarity index 100% rename from user/__init__.py rename to apps/user/migrations/__init__.py diff --git a/apps/user/models.py b/apps/user/models.py new file mode 100644 index 0000000..1831bda --- /dev/null +++ b/apps/user/models.py @@ -0,0 +1,21 @@ +from django.db import models +# from django.contrib.postgres.fields import JSONField +from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin + +from apps.base.models import BaseModel +from apps.user.managers import UserManager + + +class User(AbstractBaseUser, PermissionsMixin, BaseModel): + rds_id = models.CharField(max_length=80, unique=True, blank=True) + + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + + objects = UserManager() + + USERNAME_FIELD = 'rds_id' + + def __str__(self): + return self.rds_id diff --git a/user/permission.py b/apps/user/permission.py similarity index 60% rename from user/permission.py rename to apps/user/permission.py index b99c233..2a29998 100644 --- a/user/permission.py +++ b/apps/user/permission.py @@ -1,7 +1,10 @@ from rest_framework import permissions -from .utils import env +from django.conf import LazySettings + +settings = LazySettings() + class RestKeyPermission(permissions.BasePermission): def has_permission(self, request, view): key = request.headers.get('Rest-Key') - return key == env('REST_KEY') \ No newline at end of file + return key == settings.RDS_BACKEND_SECRET_KEY diff --git a/user/migrations/__init__.py b/apps/user/tests/__init__.py similarity index 100% rename from user/migrations/__init__.py rename to apps/user/tests/__init__.py diff --git a/apps/user/tests/test_drf_views.py b/apps/user/tests/test_drf_views.py new file mode 100644 index 0000000..4b95bef --- /dev/null +++ b/apps/user/tests/test_drf_views.py @@ -0,0 +1,59 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIClient +from django.conf import LazySettings + +from apps.conftest import get_user_token + +settings = LazySettings() + + +@pytest.mark.django_db +class TestUsersViewSet: + @pytest.fixture(autouse=True) + def setup(self): + self.client = APIClient() + + @classmethod + def set_user_token(self, user): + self.client = APIClient() + self.client.credentials(HTTP_AUTHORIZATION=get_user_token(user)) + + def test_create_or_get_user(self, client): + rds_id = "test1" + user_data = { + "data": { + "type": "User", + "attributes": { + "rds_id": rds_id + } + } + } + self.client.credentials(HTTP_REST_KEY=settings.RDS_BACKEND_SECRET_KEY) + + _response = self.client.post( + "/api/v1/user/", user_data, format="vnd.api+json") + assert _response.status_code == status.HTTP_201_CREATED + _response_data = _response.json() + + _response_data = _response_data["data"] + user_data = {"id": _response_data["id"], + **_response_data["attributes"]} + + assert user_data["rds_id"] == rds_id + assert "token" in user_data + + def test_get_user(self, client, user_t1): + self.client.credentials(HTTP_AUTHORIZATION=get_user_token(user_t1)) + + _response = self.client.get( + f"/api/v1/user/{str(user_t1.id)}/", format="vnd.api+json") + assert _response.status_code == status.HTTP_200_OK + _response_data = _response.json() + + _response_data = _response_data["data"] + user_data = {"id": _response_data["id"], + **_response_data["attributes"]} + + assert user_data["rds_id"] == user_t1.rds_id + assert "token" in user_data diff --git a/apps/user/urls.py b/apps/user/urls.py new file mode 100644 index 0000000..207a026 --- /dev/null +++ b/apps/user/urls.py @@ -0,0 +1,11 @@ +from django.urls import include, path +from rest_framework import routers +from django.urls import path +from apps.user.v1.views import UserViewSet + +router = routers.DefaultRouter() +router.register(r'user', UserViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] diff --git a/apps/user/utils.py b/apps/user/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/user/v1/serializer.py b/apps/user/v1/serializer.py new file mode 100644 index 0000000..10c6319 --- /dev/null +++ b/apps/user/v1/serializer.py @@ -0,0 +1,26 @@ +from rest_framework_json_api import serializers +from rest_framework_simplejwt.tokens import RefreshToken + +from apps.user.models import User + + +class UserSerializer(serializers.ModelSerializer): + token = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ["id", "rds_id", "token", "created_at", "modified_at"] + + def get_token(self, user): + refresh = RefreshToken.for_user(user) + return { + "exp": refresh.payload["exp"], + "access": str(refresh.access_token) + } + + def create(self, validated_data): + if self.is_valid(): + return User.objects.create_user(**validated_data) + + + diff --git a/apps/user/v1/views.py b/apps/user/v1/views.py new file mode 100644 index 0000000..ea732d7 --- /dev/null +++ b/apps/user/v1/views.py @@ -0,0 +1,32 @@ +from rest_framework.permissions import IsAuthenticated +from rest_framework import status +from rest_framework.response import Response + +from apps.base.base_views import ModelBaseViewSet +from apps.user.models import User +from apps.user.v1.serializer import UserSerializer +from apps.user.permission import RestKeyPermission +from apps.base.permissions import IsSuperUserPermission + + +class UserViewSet(ModelBaseViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [IsAuthenticated] + lookup_field = "id" + + def get_permissions(self): + if self.action in ["create"]: + self.permission_classes = [RestKeyPermission] + elif self.action in ["list"]: + self.permission_classes = [IsSuperUserPermission] + return [permission() for permission in self.permission_classes] + + def create(self, request, *args, **kwargs): + rds_id = request.data.get("rds_id") + user = User.objects.filter(rds_id=rds_id).first() + if user is not None: + user_serialized = self.get_serializer(user) + headers = self.get_success_headers(user_serialized.data) + return Response(user_serialized.data, status=status.HTTP_201_CREATED, headers=headers) + return super().create(request, *args, **kwargs) diff --git a/apps/user/views.py b/apps/user/views.py new file mode 100644 index 0000000..6242f66 --- /dev/null +++ b/apps/user/views.py @@ -0,0 +1,30 @@ +# from rest_framework.decorators import api_view, parser_classes +# from rest_framework.parsers import JSONParser +# from rest_framework.response import Response +# from django.utils import timezone + +# from .models import User +# from .utils import env + + +# @api_view(["POST"]) +# @parser_classes([JSONParser]) +# def goal_auth_token(request, userId): +# try: +# RDS_BACKEND_SECRET_KEY = request.data.get('RDS_BACKEND_SECRET_KEY') +# if RDS_BACKEND_SECRET_KEY != env('RDS_BACKEND_SECRET_KEY'): +# raise Exception("Wrong RDS_BACKEND_SECRET_KEY") + +# try: +# token = User.objects.get(userId=userId) +# if token.is_invalid(createdTime=token.created.timestamp()): +# User.objects.filter(userId=userId).update( +# auth_token=User.generate_key(), created=timezone.now()) +# token = User.objects.get(userId=userId) +# except User.DoesNotExist: +# token = User(userId=userId) +# token.save() + +# return Response({"message": "Goal site token generated succesfully", "token": token.auth_token}) +# except Exception as e: +# return Response(e.args[0], status=400, exception=True) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_jsonapi/asgi.py b/config/asgi.py similarity index 71% rename from django_jsonapi/asgi.py rename to config/asgi.py index 13143e4..f2191c2 100644 --- a/django_jsonapi/asgi.py +++ b/config/asgi.py @@ -1,5 +1,5 @@ """ -ASGI config for django_jsonapi project. +ASGI config for config project. It exposes the ASGI callable as a module-level variable named ``application``. @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_jsonapi.settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') application = get_asgi_application() diff --git a/django_jsonapi/sample.env b/config/sample.env similarity index 100% rename from django_jsonapi/sample.env rename to config/sample.env diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_jsonapi/settings/base.py b/config/settings/base.py similarity index 80% rename from django_jsonapi/settings/base.py rename to config/settings/base.py index d419ec8..3da7dba 100644 --- a/django_jsonapi/settings/base.py +++ b/config/settings/base.py @@ -1,5 +1,5 @@ """ -Django settings for django_jsonapi project. +Django settings for config project. Generated by 'django-admin startproject' using Django 4.0.7. @@ -14,18 +14,18 @@ from pathlib import Path import environ import os +from django.utils.timezone import timedelta env = environ.Env( # set casting, default value DEBUG=(bool, False), PORT=(int, 8000) ) - # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Take environment variables from .env file -# environ.Env.read_env(os.path.join(BASE_DIR, '.env')) +environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ @@ -45,8 +45,8 @@ # Application definition INSTALLED_APPS = [ - 'goals.apps.GoalsConfig', - 'user.apps.UserConfig', + 'apps.goals', + 'apps.user', 'rest_framework', 'rest_framework.authtoken', 'corsheaders', @@ -56,9 +56,10 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'base.apps.BaseConfig', + 'apps.base.apps.BaseConfig', 'rest_framework_json_api', - 'django_filters' + 'django_filters', + "rest_framework_simplejwt" ] MIDDLEWARE = [ @@ -73,7 +74,7 @@ 'whitenoise.middleware.WhiteNoiseMiddleware', ] -ROOT_URLCONF = 'django_jsonapi.urls' +ROOT_URLCONF = 'config.urls' TEMPLATES = [ { @@ -91,7 +92,7 @@ }, ] -WSGI_APPLICATION = 'django_jsonapi.wsgi.application' +WSGI_APPLICATION = 'config.wsgi.application' # Database @@ -179,10 +180,39 @@ 'TEST_REQUEST_RENDERER_CLASSES': ( 'rest_framework_json_api.renderers.JSONRenderer', ), - 'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json' + 'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json', + "DEFAULT_AUTHENTICATION_CLASSES": ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + ), } CORS_ALLOWED_ORIGIN_REGEXES = [ r"^http:\/\/localhost:\d{4}$", r"^https:\/\/\w+\.realdevsquad\.com$" ] + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=5), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': os.getenv('SECRET_KEY'), + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', +} + +AUTH_USER_MODEL = "user.User" +RDS_BACKEND_SECRET_KEY = os.getenv('RDS_BACKEND_SECRET_KEY') diff --git a/django_jsonapi/settings/development.py b/config/settings/development.py similarity index 93% rename from django_jsonapi/settings/development.py rename to config/settings/development.py index 31395a5..24be7d6 100644 --- a/django_jsonapi/settings/development.py +++ b/config/settings/development.py @@ -27,7 +27,7 @@ try: from .local import * except ImportError: - print('"local.py" NOT-FOUND, Please create it in "django_jsonapi/settings" folder') + print('"local.py" NOT-FOUND, Please create it in "config/settings" folder') sentry_sdk.init( dsn="https://92bcf175d3f4477985131f011e405d6e@o4505162173382656.ingest.sentry.io/4505162175217664", diff --git a/django_jsonapi/settings/production.py b/config/settings/production.py similarity index 100% rename from django_jsonapi/settings/production.py rename to config/settings/production.py diff --git a/django_jsonapi/settings/sample-local.py b/config/settings/sample-local.py similarity index 100% rename from django_jsonapi/settings/sample-local.py rename to config/settings/sample-local.py diff --git a/django_jsonapi/settings/testing.py b/config/settings/test.py similarity index 100% rename from django_jsonapi/settings/testing.py rename to config/settings/test.py diff --git a/django_jsonapi/urls.py b/config/urls.py similarity index 51% rename from django_jsonapi/urls.py rename to config/urls.py index 77cac34..352e636 100644 --- a/django_jsonapi/urls.py +++ b/config/urls.py @@ -1,15 +1,16 @@ from django.urls import include, path from rest_framework import routers -from goals import views as goal_views -from user.v1 import views as user_views from django.contrib import admin from django.urls import path router = routers.DefaultRouter() -router.register(r'goal', goal_views.GoalViewSet) -router.register(r'user', user_views.UserViewSet) urlpatterns = [ - path("", include(router.urls)), path("admin/", admin.site.urls) ] + +# apps urls +urlpatterns += [ + path("api/v1/", include(("apps.user.urls", "user"), namespace="user")), + path("api/v1/", include(("apps.goals.urls", "goals"), namespace="goals")) +] diff --git a/django_jsonapi/wsgi.py b/config/wsgi.py similarity index 77% rename from django_jsonapi/wsgi.py rename to config/wsgi.py index bc2a467..8963afe 100644 --- a/django_jsonapi/wsgi.py +++ b/config/wsgi.py @@ -1,5 +1,5 @@ """ -WSGI config for django_jsonapi project. +WSGI config for config project. It exposes the WSGI callable as a module-level variable named ``application``. @@ -12,6 +12,6 @@ from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', - 'django_jsonapi.settings.production') + 'config.settings.production') application = get_wsgi_application() diff --git a/docs/README.md b/docs/README.md index c825119..6822af7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,12 +4,12 @@ - Signup on [Railway](https://railway.app/login) - Goto [New Project](https://railway.app/new) page - Choose the "Deploy from GitHub Repo" option -- Choose the forked `website-goals` repository from the dropdown. -> If the repository is not listed, click on "Configure GitHub App" option and grant repository access for the forked repository. -- Click on "Add Variables" option and click on the "RAW Editor" option. +- Choose the forked `website-goals` repository from the dropdown. + > If the repository is not listed, click on "Configure GitHub App" option and grant repository access for the forked repository. +- Click on "Add Variables" option and click on the "RAW Editor" option. - Copy the content of `.env.example` file and add the required variable values. -- Goto Settings. -- Add "Start Command" as `python manage.py migrate && gunicorn django_jsonapi.wsgi`. +- Goto Settings. +- Add "Start Command" as `python manage.py migrate && gunicorn config.wsgi`. - Click on "Generate Domain" and add that hostname to `ALLOWED_HOSTS` env variable under the "Variables" tab. -> For example - `HOSTS=goals-api.railway.app` + > For example - `HOSTS=goals-api.railway.app` - Goto the generated domain and access the backend diff --git a/goals/fixtures.py b/goals/fixtures.py deleted file mode 100644 index 2b1b3cc..0000000 --- a/goals/fixtures.py +++ /dev/null @@ -1,82 +0,0 @@ -goals = [ - { - "model": "goals.goal", - "pk": 1, - "fields": { - "title": "Goal 1", - "description": "This is the description for Goal 1", - "created_at": "2023-08-04T12:00:00Z", - "created_by": "User 1", - "assigned_to": "User A", - "starts_on": "2023-08-05T00:00:00Z", - "ends_on": "2023-08-10T00:00:00Z", - "percentage_completed": 20, - "assigned_by": "Admin", - "status": "In Progress" - } - }, - { - "model": "goals.goal", - "pk": 2, - "fields": { - "title": "Goal 2", - "description": "This is the description for Goal 2", - "created_at": "2023-08-04T13:00:00Z", - "created_by": "User 2", - "assigned_to": "User B", - "starts_on": "2023-08-06T00:00:00Z", - "ends_on": "2023-08-12T00:00:00Z", - "percentage_completed": 50, - "assigned_by": "Admin", - "status": "In Progress" - } - }, - { - "model": "goals.goal", - "pk": 3, - "fields": { - "title": "Goal 3", - "description": "This is the description for Goal 3", - "created_at": "2023-08-04T14:00:00Z", - "created_by": "User 3", - "assigned_to": "User C", - "starts_on": "2023-08-07T00:00:00Z", - "ends_on": "2023-08-15T00:00:00Z", - "percentage_completed": 80, - "assigned_by": "Admin", - "status": "Completed" - } - }, - { - "model": "goals.goal", - "pk": 4, - "fields": { - "title": "Goal 4", - "description": "This is the description for Goal 4", - "created_at": "2023-08-04T15:00:00Z", - "created_by": "User 4", - "assigned_to": "User D", - "starts_on": "2023-08-08T00:00:00Z", - "ends_on": "2023-08-18T00:00:00Z", - "percentage_completed": 10, - "assigned_by": "Admin", - "status": "In Progress" - } - }, - { - "model": "goals.goal", - "pk": 5, - "fields": { - "title": "Goal 5", - "description": "This is the description for Goal 5", - "created_at": "2023-08-04T16:00:00Z", - "created_by": "User 5", - "assigned_to": "User E", - "starts_on": "2023-08-09T00:00:00Z", - "ends_on": "2023-08-20T00:00:00Z", - "percentage_completed": 40, - "assigned_by": "Admin", - "status": "In Progress" - } - } -] diff --git a/goals/tests.py b/goals/tests.py deleted file mode 100644 index 8fca2c4..0000000 --- a/goals/tests.py +++ /dev/null @@ -1,44 +0,0 @@ -from rest_framework.test import APITestCase, APIClient -from .models import Goal -from .fixtures import goals as goals_data - - -class GoalsTest(APITestCase): - @classmethod - def setUpClass(cls): - cls.client = APIClient() - - for goal in goals_data: - Goal.objects.create(**goal["fields"]) - - super(GoalsTest, cls).setUpClass() - - def test_get_goals(self): - _response = self.client.get('/goal/') - _response_data = _response.json() - - _response_goals_data = _response_data["data"] - _response_goal_data = _response_goals_data[0]["attributes"] - - assert _response.status_code == 200 - assert len(_response_goals_data) > 0 - assert "title" in _response_goal_data - assert "description" in _response_goal_data - assert "assigned_by" in _response_goal_data - assert "status" in _response_goal_data - - def test_get_goals_filtered(self): - goal_title = goals_data[2]["fields"]["title"] - _response = self.client.get(f'/goal/?title={goal_title}') - _response_data = _response.json() - - _response_goals_data = _response_data["data"] - _response_goal_data = _response_goals_data[0]["attributes"] - - assert _response.status_code == 200 - assert len(_response_goals_data) > 0 - assert "title" in _response_goal_data - assert goal_title == _response_goal_data["title"] - assert "description" in _response_goal_data - assert "assigned_by" in _response_goal_data - assert "status" in _response_goal_data diff --git a/goals/views.py b/goals/views.py deleted file mode 100644 index 4f6e45f..0000000 --- a/goals/views.py +++ /dev/null @@ -1,9 +0,0 @@ -from base.base_views import ModelBaseViewSet -from goals.models import Goal -from goals.serializers import GoalSerializer - - -class GoalViewSet(ModelBaseViewSet): - queryset = Goal.objects.all().order_by('-created_at') - serializer_class = GoalSerializer - filterset_fields = ['status', 'title', 'assigned_to'] diff --git a/manage.py b/manage.py index bd1664b..2ad03e9 100755 --- a/manage.py +++ b/manage.py @@ -14,9 +14,9 @@ PRODUCTION = 'PRODUCTION' DEVELOPMENT = 'DEVELOPMENT' TESTING = 'TESTING' -SETTINGS_PRODUCTION = 'django_jsonapi.settings.production' -SETTINGS_DEVELOPMENT = 'django_jsonapi.settings.development' -SETTINGS_TESTING = 'django_jsonapi.settings.testing' +SETTINGS_PRODUCTION = 'config.settings.production' +SETTINGS_DEVELOPMENT = 'config.settings.development' +SETTINGS_TESTING = 'config.settings.test' def main(): diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..1c95c4b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,10 @@ +[pytest] +addopts = --ds=config.settings.test --reuse-db --no-migrations + +python_files = tests.py test_*.py +DJANGO_SETTINGS_MODULE = config.settings.local + +[tool:pytest] +markers = + # Define our new marker + unit: tests that are isolated from the db, external api calls and other mockable internal code. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index cfd440f..143a3b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,11 +9,13 @@ django-filter==22.1 django-safedelete==1.3.2 djangorestframework==3.13.1 djangorestframework-jsonapi==5.0.0 +djangorestframework-simplejwt==5.3.0 gunicorn==20.1.0 httmock==1.4.0 idna==3.4 inflection==0.5.1 pycodestyle==2.9.1 +PyJWT==2.8.0 python-dotenv==0.21.0 pytz==2022.2.1 requests==2.28.1 @@ -24,3 +26,6 @@ typing_extensions==4.7.1 tzdata==2023.3 urllib3==1.26.15 whitenoise==6.1.0 +pytest-django==3.9.0 +pytest==6.0.1 +pytest-sugar==0.9.4 diff --git a/sample.env b/sample.env index 7445559..1319fe9 100644 --- a/sample.env +++ b/sample.env @@ -3,8 +3,8 @@ DJANGO_ENV=DEVELOPMENT PORT=5000 SECRET_KEY='SECRET_KEY' -REST_KEY = 123456789 -AUTH_TOKEN_INVALIDATION_TIME_IN_SECONDS = 2592000 +RDS_BACKEND_SECRET_KEY=123456789 +AUTH_TOKEN_INVALIDATION_TIME_IN_SECONDS=2592000 # ALLOWED_HOSTS accepts string as value with space as delimiter ALLOWED_HOSTS='localhost 127.0.0.1 .realdevsquad.com' diff --git a/user/authentications.py b/user/authentications.py deleted file mode 100644 index 78f9e71..0000000 --- a/user/authentications.py +++ /dev/null @@ -1,32 +0,0 @@ -from .models import User -from rest_framework import authentication -from rest_framework import exceptions - - -class UserAuthenticationExtender(): - def __init__(self, auth_token, userId, created): - self.auth_token = auth_token - self.user_id = userId - self.token_created = created - self.is_authenticated = True - - -class UserAuthentication(authentication.BaseAuthentication): - def authenticate(self, request): - try: - goal_token = request.COOKIES['goal-site-session'] - except Exception: - return None - - try: - token = User.objects.get(auth_token=goal_token) - if token.is_invalid(): - raise exceptions.AuthenticationFailed( - "Token is expired or invalid") - except User.DoesNotExist: - raise exceptions.AuthenticationFailed( - 'Token is expired or invalid') - - user = UserAuthenticationExtender( - token.auth_token, token.user_id, token.token_created) - return (user, None) diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py deleted file mode 100644 index 7886576..0000000 --- a/user/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.0.9 on 2023-08-25 19:14 - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('deleted', models.DateTimeField(db_index=True, editable=False, null=True)), - ('deleted_by_cascade', models.BooleanField(default=False, editable=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('modified_at', models.DateTimeField(auto_now=True)), - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('auth_token', models.CharField(blank=True, max_length=40, verbose_name='Auth Token')), - ('user_id', models.CharField(max_length=80)), - ('token_created', models.DateTimeField(auto_now_add=True, verbose_name='Token Created')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/user/models.py b/user/models.py deleted file mode 100644 index a652cad..0000000 --- a/user/models.py +++ /dev/null @@ -1,40 +0,0 @@ -import binascii -import os -from django.db import models -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from base.utils import env -from base.models import BaseModel - -class User(BaseModel): - """ - The default authorization token model. - """ - auth_token = models.CharField(_("Auth Token"), max_length=40, blank=True) - user_id = models.CharField(max_length=80) - token_created = models.DateTimeField(_("Token Created"), auto_now_add=True) - - def save(self, *args, **kwargs): - if not self.auth_token: - self.auth_token = self.generate_key() - return super().save(*args, **kwargs) - - def is_invalid(self): - curr_time = timezone.now().timestamp() - auth_invalid_seconds = float(env('AUTH_TOKEN_INVALIDATION_TIME_IN_SECONDS')) - exp_time = self.token_created.timestamp() + auth_invalid_seconds - if exp_time > curr_time: - return False - return True - - def update_token(self): - self.auth_token = self.generate_key() - self.token_created=timezone.now() - return self.save() - - @classmethod - def generate_key(cls): - return binascii.hexlify(os.urandom(20)).decode() - - def __str__(self): - return self.auth_token diff --git a/user/tests.py b/user/tests.py deleted file mode 100644 index 3b1f608..0000000 --- a/user/tests.py +++ /dev/null @@ -1,92 +0,0 @@ -from rest_framework.test import APITestCase, APIClient, APIRequestFactory -from rest_framework.decorators import api_view, permission_classes, authentication_classes -from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated -from django.utils import timezone - -from user.authentications import TokenCustomAuthentication -from base.utils import env -from user.models import Token_Custom -import json -# DEMO API to test authentication - - -@api_view() -@authentication_classes([TokenCustomAuthentication]) -@permission_classes([IsAuthenticated]) -def Authenticated(request): - return Response("Hello World", status=200) - - -def get_previous_month_date(date): - month = date.month - month -= 1 - year = date.year - day = date.day - 1 - if month <= 0: - month = 12 - year -= 1 - return timezone.datetime(day=day, year=year, month=month, hour=date.hour, tzinfo=date.astimezone().tzinfo) - - -class AuthenticateTest(APITestCase): - @classmethod - def setUpClass(cls): - cls.factory = APIRequestFactory() - cls.token = Token_Custom.objects.create(user_id="Vinayak") - super(AuthenticateTest, cls).setUpClass() - - def test_auth_without_cookie(self): - _request = self.factory.post('auth_token/auth') - _response = Authenticated(_request) - self.assertEqual(_response.data[0]["detail"].title( - ), "Authentication Credentials Were Not Provided.") - self.assertEqual(_response.status_code, 403) - - def test_auth_with_wrong_cookie(self): - _request = self.factory.get('auth_token/auth') - _request.COOKIES['goal-site-session'] = self.token.auth_token + "1" - _response = Authenticated(_request) - self.assertEqual( - _response.data[0]["detail"].title(), "Token Is Expired Or Invalid") - self.assertEqual(_response.status_code, 403) - - def test_auth_with_expired_cookie(self): - _request = self.factory.get('auth_token/auth') - time = timezone.now() - time = get_previous_month_date(time) - Token_Custom.objects.filter(user_id=self.token.user_id).update(token_created=time) - _request.COOKIES['goal-site-session'] = self.token.auth_token - _response = Authenticated(_request) - self.assertEqual( - _response.data[0]["detail"].title(), "Token Is Expired Or Invalid") - self.assertEqual(_response.status_code, 403) - - def test_auth_with_cookie(self): - _request = self.factory.get('auth_token/auth') - _request.COOKIES['goal-site-session'] = self.token.auth_token - _response = Authenticated(_request) - self.assertEqual(_response.data, "Hello World") - self.assertEqual(_response.status_code, 200) - - -class Get_Auth_token_Test(APITestCase): - @classmethod - def setUpClass(cls): - cls._client = APIClient() - super(Get_Auth_token_Test, cls).setUpClass() - - def test_get_auth_token_without_restkey(self): - data = {"user_id" : "vinayak"} - _response = self._client.post('/auth_token/', data, format="json") - self.assertEqual(_response.data, 'Wrong REST_KEY') - - def test_get_auth_token_with_restkey(self): - data = {"rest_key": env("REST_KEY"), "user_id" : "vinayak"} - _response = self._client.post('/auth_token/', data, format="json") - - self.assertEqual(_response.data["message"], - 'Goal site token generated succesfully') - self.assertEqual(_response.status_code, 200) - self.assertIn("token", _response.data) - self.assertIs(type(_response.data["token"]["auth_token"]), str) diff --git a/user/utils.py b/user/utils.py deleted file mode 100644 index 4765f09..0000000 --- a/user/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -from pathlib import Path -import environ -import os - -env = environ.Env() - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -environ.Env.read_env(os.path.join(BASE_DIR, '.env')) \ No newline at end of file diff --git a/user/v1/serializer.py b/user/v1/serializer.py deleted file mode 100644 index 7eaf905..0000000 --- a/user/v1/serializer.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework_json_api import serializers -from user.models import User - -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = "__all__" \ No newline at end of file diff --git a/user/v1/views.py b/user/v1/views.py deleted file mode 100644 index 1805aed..0000000 --- a/user/v1/views.py +++ /dev/null @@ -1,13 +0,0 @@ -from base.base_views import ModelBaseViewSet - -from user.models import User -from user.v1.serializer import UserSerializer -from base.utils import env -from user.permission import RestKeyPermission - -class UserViewSet(ModelBaseViewSet): - queryset = User.objects.all() - serializer_class = UserSerializer - permission_classes = [RestKeyPermission] - - \ No newline at end of file diff --git a/user/views.py b/user/views.py deleted file mode 100644 index cc91c5f..0000000 --- a/user/views.py +++ /dev/null @@ -1,30 +0,0 @@ -from rest_framework.decorators import api_view, parser_classes -from rest_framework.parsers import JSONParser -from rest_framework.response import Response -from django.utils import timezone - -from .models import User -from .utils import env - - -@api_view(["POST"]) -@parser_classes([JSONParser]) -def goal_auth_token(request, userId): - try: - rest_key = request.data.get('rest_key') - if rest_key != env('REST_KEY'): - raise Exception("Wrong REST_KEY") - - try: - token = User.objects.get(userId=userId) - if token.is_invalid(createdTime=token.created.timestamp()): - User.objects.filter(userId=userId).update( - auth_token=User.generate_key(), created=timezone.now()) - token = User.objects.get(userId=userId) - except User.DoesNotExist: - token = User(userId=userId) - token.save() - - return Response({"message": "Goal site token generated succesfully", "token": token.auth_token}) - except Exception as e: - return Response(e.args[0], status=400, exception=True)