From 88992b1ec76657f1c6fa50828865736f76ad85a1 Mon Sep 17 00:00:00 2001 From: Riad Elimemmedov Date: Mon, 25 Mar 2024 22:46:03 +0400 Subject: [PATCH] Completed authentication view and test --- backend/apps/transaction/admin.py | 4 +- backend/apps/transaction/serializers.py | 6 +- backend/apps/transaction/validate_field.py | 4 +- backend/apps/user_profile/models.py | 17 +- backend/apps/users/admin.py | 7 +- backend/apps/users/helpers.py | 11 + .../0008_alter_customuser_wallet_address.py | 27 + backend/apps/users/models.py | 2 +- backend/apps/users/serializers.py | 61 +++ backend/apps/users/urls.py | 17 +- backend/apps/users/views.py | 91 +++- backend/config/base.py | 5 +- backend/config/helpers.py | 4 +- .../config/tests/users/test_endpoint_users.py | 233 ++++++++ backend/config/urls.py | 2 +- backend/schema.yml | 506 +++++++++--------- 16 files changed, 726 insertions(+), 271 deletions(-) create mode 100644 backend/apps/users/helpers.py create mode 100644 backend/apps/users/migrations/0008_alter_customuser_wallet_address.py create mode 100644 backend/apps/users/serializers.py create mode 100644 backend/config/tests/users/test_endpoint_users.py diff --git a/backend/apps/transaction/admin.py b/backend/apps/transaction/admin.py index 44f6801..843c527 100644 --- a/backend/apps/transaction/admin.py +++ b/backend/apps/transaction/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from .models import Transaction -from .validate_field import validate_confirmations, validate_from_user +from .validate_field import validate_confirmations, validate_wallet_address # Register your models here. @@ -21,7 +21,7 @@ def clean(self): return if not validate_confirmations(confirmations): self.add_error("confirmations", "Confirmations value 1 or 0") - if not validate_from_user(from_user): + if not validate_wallet_address(from_user): self.add_error("from_user", "Please input valid wallet address") diff --git a/backend/apps/transaction/serializers.py b/backend/apps/transaction/serializers.py index 691f29b..35000db 100644 --- a/backend/apps/transaction/serializers.py +++ b/backend/apps/transaction/serializers.py @@ -3,19 +3,19 @@ from rest_framework import serializers from .models import Transaction -from .validate_field import validate_confirmations, validate_from_user +from .validate_field import validate_confirmations, validate_wallet_address # !TransactionSerializer class TransactionSerializer(serializers.ModelSerializer): - from_user = serializers.CharField(validators=[validate_from_user]) + from_user = serializers.CharField(validators=[validate_wallet_address]) confirmations = serializers.CharField(validators=[validate_confirmations]) def validate(self, attrs): from_user = attrs.get("from_user") confirmations = attrs.get("confirmations") - if not validate_from_user(from_user): + if not validate_wallet_address(from_user): raise serializers.ValidationError("Please input valid wallet address") if not validate_confirmations(confirmations): raise serializers.ValidationError("Confirmations value 1 or 0") diff --git a/backend/apps/transaction/validate_field.py b/backend/apps/transaction/validate_field.py index b9dc54d..0ddb3ba 100644 --- a/backend/apps/transaction/validate_field.py +++ b/backend/apps/transaction/validate_field.py @@ -11,7 +11,7 @@ def validate_confirmations(value): return value -# ?validate_from_user -def validate_from_user(value): +# ?validate_wallet_address +def validate_wallet_address(value): pattern = re.compile("^0x[a-fA-F0-9]{40}$") return bool(pattern.match(f"{value}")) diff --git a/backend/apps/user_profile/models.py b/backend/apps/user_profile/models.py index 535686b..004ef18 100644 --- a/backend/apps/user_profile/models.py +++ b/backend/apps/user_profile/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from django.core.validators import FileExtensionValidator from django.db import models from django.db.models.signals import post_save @@ -12,6 +13,8 @@ # Create your models here. +User = get_user_model() + # *ActiveQueryset class ActiveQueryset(models.QuerySet): @@ -98,15 +101,15 @@ class Meta: def save(self, *args, **kwargs): if not self.full_name: - full_name = setFullName( - self.first_name if self.first_name is not None else "", - self.last_name if self.last_name is not None else "", - ) - is_exists = __class__.objects.filter(full_name=full_name).exists() + # full_name = setFullName( + # self.first_name if self.first_name is not None else "", + # self.last_name if self.last_name is not None else "", + # ) + is_exists = User.objects.filter(username=self.user.username).exists() if is_exists: - self.full_name = f"{full_name}_{self.profile_key}" + self.full_name = f"{self.user.username}_{self.profile_key}" else: - self.full_name = f"{full_name}" + self.full_name = f"{self.user.username}" self.slug = slugify(self.full_name) super(Profile, self).save(*args, **kwargs) diff --git a/backend/apps/users/admin.py b/backend/apps/users/admin.py index fb9af2b..0505512 100644 --- a/backend/apps/users/admin.py +++ b/backend/apps/users/admin.py @@ -21,13 +21,14 @@ class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm form = CustomUserChangeForm model = CustomUser - list_display_links = ["id", "email", "wallet_address"] + list_display_links = ["id", "email", "wallet_address", "username"] list_display = ( "id", "email", "wallet_address", "is_staff", "is_active", + "username", ) list_filter = ( "email", @@ -36,7 +37,7 @@ class CustomUserAdmin(UserAdmin): "is_active", ) fieldsets = ( - (None, {"fields": ("email", "wallet_address", "password")}), + (None, {"fields": ("email", "wallet_address", "password", "username")}), ( "Permissions", {"fields": ("is_staff", "is_active", "groups", "user_permissions")}, @@ -55,6 +56,8 @@ class CustomUserAdmin(UserAdmin): "is_active", "groups", "user_permissions", + "wallet_address", + "username", ), }, ), diff --git a/backend/apps/users/helpers.py b/backend/apps/users/helpers.py new file mode 100644 index 0000000..a7efbd0 --- /dev/null +++ b/backend/apps/users/helpers.py @@ -0,0 +1,11 @@ +from django.contrib.auth import get_user_model + +# User +User = get_user_model() + + +# ?is_exists_wallet_address +def is_exists_wallet_address(wallet_address: str): + if User.objects.filter(wallet_address=wallet_address).exists(): + return True + return False diff --git a/backend/apps/users/migrations/0008_alter_customuser_wallet_address.py b/backend/apps/users/migrations/0008_alter_customuser_wallet_address.py new file mode 100644 index 0000000..07d1b9b --- /dev/null +++ b/backend/apps/users/migrations/0008_alter_customuser_wallet_address.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.1 on 2024-03-25 18:34 + +from django.db import migrations, models + +import config.helpers + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0007_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="customuser", + name="wallet_address", + field=models.CharField( + db_index=True, + max_length=100, + null=True, + unique=True, + validators=[config.helpers.is_valid_wallet_address], + verbose_name="Wallet address", + ), + ), + ] diff --git a/backend/apps/users/models.py b/backend/apps/users/models.py index e3f0d35..5f345b7 100644 --- a/backend/apps/users/models.py +++ b/backend/apps/users/models.py @@ -20,7 +20,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): unique=True, db_index=True, max_length=100, - blank=True, + blank=False, null=True, validators=[is_valid_wallet_address], ) diff --git a/backend/apps/users/serializers.py b/backend/apps/users/serializers.py new file mode 100644 index 0000000..cf5ba16 --- /dev/null +++ b/backend/apps/users/serializers.py @@ -0,0 +1,61 @@ +from django.contrib.auth import authenticate +from rest_framework import serializers + +from apps.transaction.validate_field import ( + validate_confirmations, + validate_wallet_address, +) + +from .helpers import is_exists_wallet_address +from .models import CustomUser + + +# !CustomUserSerializer +class CustomUserSerializer(serializers.ModelSerializer): + """ + Serializer class to serialize CustomUser model. + """ + + class Meta: + model = CustomUser + fields = ("id", "username", "email", "wallet_address") + + +# !UserRegisterationSerializer +class UserRegisterationSerializer(serializers.ModelSerializer): + """ + Serializer class to serialize registration requests and create a new user. + """ + + wallet_address = serializers.CharField(validators=[validate_wallet_address]) + + def validate_wallet_address(self, wallet_address): + if not validate_wallet_address(wallet_address): + raise serializers.ValidationError("Please input valid wallet address") + elif is_exists_wallet_address(wallet_address): + raise serializers.ValidationError("This wallet address already set other") + return wallet_address + + class Meta: + model = CustomUser + fields = ("id", "username", "email", "password", "wallet_address") + extra_kwargs = {"password": {"write_only": True}} + + def create(self, validated_data): + return CustomUser.objects.create_user(**validated_data) + + +# !UserLoginSerializer +class UserLoginSerializer(serializers.Serializer): + """ + Serializer class to authenticate users with email and password. + """ + + email = serializers.CharField() + password = serializers.CharField(write_only=True) + + def validate(self, data): + user = authenticate(**data) + if user and user.is_active: + return user + raise serializers.ValidationError("Incorrect Credentials") diff --git a/backend/apps/users/urls.py b/backend/apps/users/urls.py index 851691d..912d96f 100644 --- a/backend/apps/users/urls.py +++ b/backend/apps/users/urls.py @@ -1,6 +1,21 @@ from django.urls import path +from rest_framework_simplejwt.views import TokenRefreshView + +from .views import ( + UserAPIView, + UserLoginAPIView, + UserLogoutAPIView, + UserRegisterationAPIView, +) # from .views import hello_world + app_name = "users" -# urlpatterns = [path("sayhello/", hello_world, name="hello_world")] +urlpatterns = [ + path("register/", UserRegisterationAPIView.as_view(), name="create-user"), + path("login/", UserLoginAPIView.as_view(), name="login-user"), + path("token/refresh/", TokenRefreshView.as_view(), name="token-refresh"), + path("logout/", UserLogoutAPIView.as_view(), name="logout-user"), + path("", UserAPIView.as_view(), name="user-info"), +] diff --git a/backend/apps/users/views.py b/backend/apps/users/views.py index 161cea9..169e154 100644 --- a/backend/apps/users/views.py +++ b/backend/apps/users/views.py @@ -1,12 +1,87 @@ -from django.shortcuts import render -from rest_framework.decorators import api_view +from django.contrib.auth import get_user_model +from rest_framework import status +from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView +from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response +from rest_framework_simplejwt.tokens import RefreshToken -# Create your views here. +from apps.user_profile.models import Profile +from .serializers import ( + CustomUserSerializer, + UserLoginSerializer, + UserRegisterationSerializer, +) -# @api_view(["GET", "POST"]) -# def hello_world(request): -# if request.method == "POST": -# return Response({"message": "Got some data!", "data": request.data}) -# return {"message": "Hello, world!"} +# User +User = get_user_model() + + +# !UserRegisterationAPIView +class UserRegisterationAPIView(GenericAPIView): + """ + An endpoint for the client to create a new User. + """ + + permission_classes = (AllowAny,) + serializer_class = UserRegisterationSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.save() + token = RefreshToken.for_user(user) + data = serializer.data + data["tokens"] = {"refresh": str(token), "access": str(token.access_token)} + return Response(data, status=status.HTTP_201_CREATED) + + +# !UserLoginAPIView +class UserLoginAPIView(GenericAPIView): + """ + An endpoint to authenticate existing users using their email and password. + """ + + permission_classes = (AllowAny,) + serializer_class = UserLoginSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data + serializer = CustomUserSerializer(user) + token = RefreshToken.for_user(user) + data = serializer.data + data["tokens"] = {"refresh": str(token), "access": str(token.access_token)} + return Response(data, status=status.HTTP_200_OK) + + +# !UserLogoutAPIView +class UserLogoutAPIView(GenericAPIView): + """ + An endpoint to logout users. + """ + + permission_classes = (IsAuthenticated,) + + def post(self, request, *args, **kwargs): + try: + refresh_token = request.data["refresh"] + token = RefreshToken(refresh_token) + token.blacklist() + return Response(status=status.HTTP_205_RESET_CONTENT) + except Exception: + return Response(status=status.HTTP_400_BAD_REQUEST) + + +# !UserAPIView +class UserAPIView(RetrieveUpdateAPIView): + """ + Get, Update user information + """ + + permission_classes = (IsAuthenticated,) + serializer_class = CustomUserSerializer + + def get_object(self): + return self.request.user diff --git a/backend/config/base.py b/backend/config/base.py index 17b6302..7735442 100644 --- a/backend/config/base.py +++ b/backend/config/base.py @@ -295,6 +295,7 @@ # ], "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", + "rest_framework.authentication.SessionAuthentication", ), } @@ -359,8 +360,8 @@ # !SIMPLE_JWT SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15), - "REFRESH_TOKEN_LIFETIME": timedelta(days=14), + "ACCESS_TOKEN_LIFETIME": timedelta(days=1), + "REFRESH_TOKEN_LIFETIME": timedelta(days=180), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "UPDATE_LAST_LOGIN": False, diff --git a/backend/config/helpers.py b/backend/config/helpers.py index efc239d..b49aaf1 100644 --- a/backend/config/helpers.py +++ b/backend/config/helpers.py @@ -23,9 +23,9 @@ def setPetName(name: str) -> str: def is_valid_wallet_address(wallet_address: str): pattern = r"^0x[a-fA-F0-9]{40}$" if wallet_address == "" or wallet_address is None: - return "" + return ValidationError("Please enter a wallet address") elif not re.match(pattern, wallet_address): - raise ValidationError("Invalid MetaMask address.") + raise ValidationError("Invalid MetaMask wallet address address.") return True diff --git a/backend/config/tests/users/test_endpoint_users.py b/backend/config/tests/users/test_endpoint_users.py new file mode 100644 index 0000000..4145514 --- /dev/null +++ b/backend/config/tests/users/test_endpoint_users.py @@ -0,0 +1,233 @@ +import json +import os + +import pytest +from django.core.exceptions import ValidationError +from django.core.files.storage import FileSystemStorage +from django.core.files.uploadedfile import SimpleUploadedFile +from rest_framework import status + +from abstract.constants import ImageExtension +from apps.pet.models import Pet +from apps.pet.serializers import PetSerializer +from infrastructure.test_data_clear import TruncateTestData + +# * If you don't declare pytestmark our test class model don't accsess to database table +pytestmark = pytest.mark.django_db + + +# !TestUserAuthenticationEndpoints +class TestUserAuthenticationEndpoints: + endpoint = "/users" + + def get_register_payload(self): + """ + Get the payload for user registration. + + This method returns a dictionary containing the payload data for user registration. The payload includes the following fields: + - 'username': The username of the user. + - 'email': The email address of the user. + - 'password': The password for the user account. + - 'wallet_address': The wallet address associated with the user. + + Returns: + dict: A dictionary containing the payload data for user registration. + + Example: + { + 'username': 'User', + 'email': 'user@mail.ru', + 'password': 'complexpassword123', + 'wallet_address': '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + } + """ + return { + "username": "User", + "email": "user@mail.ru", + "password": "complexpassword123", + "wallet_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + } + + def test_register(self, api_client): + """ + Test the registration endpoint. + + This method tests the registration functionality by sending a POST request to the registration endpoint + with the provided payload. It asserts that the response status code is HTTP 201 Created and checks + that the returned data contains the expected values. + + Args: + api_client: An instance of the API client. + + Returns: + None + + Raises: + AssertionError: If any of the assertions fail. + """ + payload = self.get_register_payload() + + response = api_client().post( + f"{self.endpoint}/register/", payload, format="json" + ) + assert response.status_code == status.HTTP_201_CREATED + assert response.data["id"] is not None + assert response.data["username"] == payload["username"] + assert response.data["email"] == payload["email"] + assert response.data["wallet_address"] == payload["wallet_address"] + assert ( + response.data["tokens"]["refresh"] and response.data["tokens"]["access"] + ) is not None + + def test_login(self, api_client): + """ + Test the login endpoint. + + This method tests the login functionality by performing the following steps: + 1. Registers a user by sending a POST request to the registration endpoint with the provided payload. + 2. Sends a POST request to the login endpoint with the email and password from the registration payload. + 3. Asserts that the response status code is HTTP 200 OK and checks that the returned data contains the expected values. + + Args: + api_client: An instance of the API client. + + Returns: + None + + Raises: + AssertionError: If any of the assertions fail. + """ + payload = self.get_register_payload() + response_register = api_client().post( + f"{self.endpoint}/register/", payload, format="json" + ) + + login_payload = { + "email": payload["email"], + "password": payload["password"], + } + + response_login = api_client().post( + f"{self.endpoint}/login/", login_payload, format="json" + ) + assert response_login.status_code == status.HTTP_200_OK + assert response_login.data["id"] is not None + assert response_login.data["username"] == response_register.data["username"] + assert response_login.data["email"] == response_register.data["email"] + assert ( + response_login.data["wallet_address"] + == response_register.data["wallet_address"] + ) + assert ( + response_login.data["tokens"]["refresh"] + and response_login.data["tokens"]["access"] + ) is not None + + def test_logout(self, api_client): + """ + Test the logout endpoint. + + This method tests the logout functionality by performing the following steps: + 1. Registers a user by sending a POST request to the registration endpoint with the provided payload. + 2. Extracts the refresh token and access token from the registration response. + 3. Sets the authorization header using the access token. + 4. Sends a POST request to the logout endpoint with the refresh token in the payload and the authorization header. + 5. Asserts that the response status code is HTTP 205 Reset Content. + + Args: + api_client: An instance of the API client. + + Returns: + None + + Raises: + AssertionError: If the assertion fails. + """ + payload = self.get_register_payload() + response_register = api_client().post( + f"{self.endpoint}/register/", payload, format="json" + ) + refresh_token = response_register.data["tokens"]["refresh"] + access_token = response_register.data["tokens"]["access"] + headers = {"Authorization": f"Bearer {access_token}"} + + logout_payload = {"refresh": refresh_token} + response_logout = api_client().post( + f"{self.endpoint}/logout/", logout_payload, headers=headers, format="json" + ) + assert response_logout.status_code == status.HTTP_205_RESET_CONTENT + + def test_token_refresh(self, api_client): + """ + Test the token refresh endpoint. + + This method tests the token refresh functionality by performing the following steps: + 1. Registers a user by sending a POST request to the registration endpoint with the provided payload. + 2. Performs two token refresh requests using the refresh token from the registration response. + - In the first request, it asserts that the response status code is HTTP 200 OK and checks that the returned data + contains the new access and refresh tokens. + - In the second request, it asserts that the response status code is HTTP 401 UNAUTHORIZED and checks that the + returned data contains the expected error detail and code. + + Args: + api_client: An instance of the API client. + + Returns: + None + + Raises: + AssertionError: If any of the assertions fail. + """ + payload = self.get_register_payload() + response_register = api_client().post( + f"{self.endpoint}/register/", payload, format="json" + ) + for req_index in range(1, 3): + refresh_payload = {"refresh": response_register.data["tokens"]["refresh"]} + response_refresh = api_client().post( + f"{self.endpoint}/token/refresh/", refresh_payload, format="json" + ) + if req_index == 1: + assert response_refresh.status_code == status.HTTP_200_OK + assert ( + response_refresh.data["access"] and response_refresh.data["refresh"] + ) is not None + elif req_index == 2: + assert response_refresh.status_code == status.HTTP_401_UNAUTHORIZED + assert (response_refresh.data["detail"]) == "Token is blacklisted" + assert (response_refresh.data["code"]) == "token_not_valid" + else: + assert True + + def test_get_user(self, api_client): + """ + Test the get user endpoint. + + This method tests the get user functionality by performing the following steps: + 1. Registers a user by sending a POST request to the registration endpoint with the provided payload. + 2. Extracts the access token from the registration response. + 3. Sets the authorization header using the access token. + 4. Sends a GET request to the get user endpoint with the authorization header. + 5. Asserts that the response status code is HTTP 200 OK and checks that the returned data contains the expected values. + + Args: + api_client: An instance of the API client. + + Returns: + None + + Raises: + AssertionError: If any of the assertions fail. + """ + payload = self.get_register_payload() + response_register = api_client().post( + f"{self.endpoint}/register/", payload, format="json" + ) + access_token = response_register.data["tokens"]["access"] + headers = {"Authorization": f"Bearer {access_token}"} + response_user = api_client().get(f"{self.endpoint}/", headers=headers) + assert response_user.status_code == status.HTTP_200_OK + assert response_user.data["id"] is not None + assert response_user.data["username"] == payload["username"] + assert response_user.data["email"] == payload["email"] + assert response_user.data["wallet_address"] == payload["wallet_address"] diff --git a/backend/config/urls.py b/backend/config/urls.py index 84b745b..2f53971 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -51,7 +51,7 @@ ), ] urlpatterns += i18n_patterns(path("admin/", admin.site.urls)) - # urlpatterns += [path("users/", include("apps.users.urls", namespace="users"))] + urlpatterns += [path("users/", include("apps.users.urls", namespace="users"))] urlpatterns += [path("upload/", include("apps.upload.urls", namespace="upload"))] urlpatterns += [path("pets/", include("apps.pet.urls", namespace="pet"))] urlpatterns += [path("orders/", include("apps.order.urls", namespace="order"))] diff --git a/backend/schema.yml b/backend/schema.yml index 97c509b..907b2e1 100644 --- a/backend/schema.yml +++ b/backend/schema.yml @@ -4,177 +4,6 @@ info: version: 1.0.0 description: This project purpose creating ecommerce api for business company paths: - /api/auth/login/: - post: - operationId: api_auth_login_create - description: |- - Check the credentials and return the REST Token - if the credentials are valid and authenticated. - Calls Django Auth login method to register User ID - in Django session framework - - Accept the following POST parameters: username, password - Return the REST Framework Token Object's key. - tags: - - api - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Login' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Login' - multipart/form-data: - schema: - $ref: '#/components/schemas/Login' - required: true - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/Token' - description: '' - /api/auth/logout/: - post: - operationId: api_auth_logout_create - description: |- - Calls Django logout method and delete the Token object - assigned to the current User object. - - Accepts/Returns nothing. - tags: - - api - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/RestAuthDetail' - description: '' - /api/auth/register/: - post: - operationId: api_auth_register_create - tags: - - api - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Register' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Register' - multipart/form-data: - schema: - $ref: '#/components/schemas/Register' - required: true - security: - - tokenAuth: [] - - {} - responses: - '201': - content: - application/json: - schema: - $ref: '#/components/schemas/Token' - description: '' - /api/auth/user/: - get: - operationId: api_auth_user_retrieve - description: |- - Reads and updates UserModel fields - Accepts GET, PUT, PATCH methods. - - Default accepted fields: username, first_name, last_name - Default display fields: pk, username, email, first_name, last_name - Read-only fields: pk, email - - Returns UserModel fields. - tags: - - api - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetails' - description: '' - put: - operationId: api_auth_user_update - description: |- - Reads and updates UserModel fields - Accepts GET, PUT, PATCH methods. - - Default accepted fields: username, first_name, last_name - Default display fields: pk, username, email, first_name, last_name - Read-only fields: pk, email - - Returns UserModel fields. - tags: - - api - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetails' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/UserDetails' - multipart/form-data: - schema: - $ref: '#/components/schemas/UserDetails' - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetails' - description: '' - patch: - operationId: api_auth_user_partial_update - description: |- - Reads and updates UserModel fields - Accepts GET, PUT, PATCH methods. - - Default accepted fields: username, first_name, last_name - Default display fields: pk, username, email, first_name, last_name - Read-only fields: pk, email - - Returns UserModel fields. - tags: - - api - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PatchedUserDetails' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/PatchedUserDetails' - multipart/form-data: - schema: - $ref: '#/components/schemas/PatchedUserDetails' - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/UserDetails' - description: '' /orders/create-checkout-session/: post: operationId: orders_create_checkout_session_create @@ -182,7 +11,8 @@ paths: tags: - orders security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -201,7 +31,8 @@ paths: tags: - pets security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -236,7 +67,8 @@ paths: $ref: '#/components/schemas/Pet' required: true security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -258,7 +90,8 @@ paths: tags: - pets security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '204': @@ -284,7 +117,8 @@ paths: tags: - pets security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -325,7 +159,8 @@ paths: $ref: '#/components/schemas/Pet' required: true security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -354,7 +189,8 @@ paths: tags: - pets security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '204': @@ -365,7 +201,8 @@ paths: tags: - transactions security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -391,7 +228,8 @@ paths: $ref: '#/components/schemas/Transaction' required: true security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -418,7 +256,8 @@ paths: $ref: '#/components/schemas/Upload' required: true security: - - tokenAuth: [] + - jwtAuth: [] + - cookieAuth: [] - {} responses: '200': @@ -427,8 +266,197 @@ paths: schema: $ref: '#/components/schemas/Upload' description: '' + /users/: + get: + operationId: users_retrieve + description: Get, Update user information + tags: + - users + security: + - jwtAuth: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + put: + operationId: users_update + description: Get, Update user information + tags: + - users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomUser' + security: + - jwtAuth: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + patch: + operationId: users_partial_update + description: Get, Update user information + tags: + - users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCustomUser' + security: + - jwtAuth: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + description: '' + /users/login/: + post: + operationId: users_login_create + description: An endpoint to authenticate existing users using their email and + password. + tags: + - users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserLogin' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserLogin' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserLogin' + required: true + security: + - jwtAuth: [] + - cookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserLogin' + description: '' + /users/logout/: + post: + operationId: users_logout_create + description: An endpoint to logout users. + tags: + - users + security: + - jwtAuth: [] + - cookieAuth: [] + responses: + '200': + description: No response body + /users/register/: + post: + operationId: users_register_create + description: An endpoint for the client to create a new User. + tags: + - users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserRegisteration' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserRegisteration' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserRegisteration' + required: true + security: + - jwtAuth: [] + - cookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserRegisteration' + description: '' + /users/token/refresh/: + post: + operationId: users_token_refresh_create + description: |- + Takes a refresh type JSON web token and returns an access type JSON web + token if the refresh token is valid. + tags: + - users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefresh' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenRefresh' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenRefresh' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefresh' + description: '' components: schemas: + CustomUser: + type: object + description: Serializer class to serialize CustomUser model. + properties: + id: + type: integer + readOnly: true + username: + type: string + nullable: true + maxLength: 100 + email: + type: string + format: email + nullable: true + title: Email address + maxLength: 254 + wallet_address: + type: string + nullable: true + maxLength: 100 + required: + - id GenderEnum: enum: - MALE @@ -439,31 +467,27 @@ components: type: string description: "* `MALE` - male\n* `FEMALE` - female\n* `Neutered ` - neutered\ \ \n* `Spayed` - spayed\n* `Other` - other" - Login: - type: object - properties: - username: - type: string - email: - type: string - format: email - password: - type: string - required: - - password - PatchedUserDetails: + PatchedCustomUser: type: object - description: User model w/o password + description: Serializer class to serialize CustomUser model. properties: - pk: + id: type: integer readOnly: true - title: ID + username: + type: string + nullable: true + maxLength: 100 email: type: string format: email - readOnly: true + nullable: true title: Email address + maxLength: 254 + wallet_address: + type: string + nullable: true + maxLength: 100 PaymentOptionsEnum: enum: - STRIPE @@ -544,43 +568,17 @@ components: - price_currency - slug - weight - Register: - type: object - properties: - username: - type: string - maxLength: 100 - minLength: 1 - email: - type: string - format: email - password1: - type: string - writeOnly: true - password2: - type: string - writeOnly: true - required: - - password1 - - password2 - - username - RestAuthDetail: + TokenRefresh: type: object properties: - detail: + access: type: string readOnly: true - required: - - detail - Token: - type: object - description: Serializer for Token model. - properties: - key: + refresh: type: string - maxLength: 40 required: - - key + - access + - refresh Transaction: type: object properties: @@ -639,25 +637,53 @@ components: - file - id - upload_at - UserDetails: + UserLogin: + type: object + description: Serializer class to authenticate users with email and password. + properties: + email: + type: string + password: + type: string + writeOnly: true + required: + - email + - password + UserRegisteration: type: object - description: User model w/o password + description: Serializer class to serialize registration requests and create + a new user. properties: - pk: + id: type: integer readOnly: true - title: ID + username: + type: string + nullable: true + maxLength: 100 email: type: string format: email - readOnly: true + nullable: true title: Email address + maxLength: 254 + password: + type: string + writeOnly: true + maxLength: 128 + wallet_address: + type: string + nullable: true + maxLength: 100 required: - - email - - pk + - id + - password securitySchemes: - tokenAuth: + cookieAuth: type: apiKey - in: header - name: Authorization - description: Token-based authentication with required prefix "Token" + in: cookie + name: sessionid + jwtAuth: + type: http + scheme: bearer + bearerFormat: JWT