login route
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
from typing import Any, Optional
|
||||
from rest_framework import serializers
|
||||
from django.conf import settings
|
||||
from api.types import User
|
||||
|
||||
class UserResponseSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
email = serializers.EmailField(read_only=True)
|
||||
account_type = serializers.CharField(
|
||||
source='userprofile.account_type',
|
||||
read_only=True
|
||||
)
|
||||
name = serializers.CharField(source='first_name', read_only=True)
|
||||
surname = serializers.CharField(source='last_name', read_only=True)
|
||||
imageURL = serializers.SerializerMethodField()
|
||||
uuid = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
ref_name = "UserResponse" # для OpenAPI
|
||||
|
||||
def get_uuid(self, obj: User) -> Optional[str]:
|
||||
"""Получает короткий UUID (первые 6 символов) из профиля пользователя"""
|
||||
return obj.userprofile.short_uuid if hasattr(obj, 'userprofile') else None
|
||||
|
||||
def get_imageURL(self, obj: User) -> Optional[str]:
|
||||
"""Получает полный URL для изображения профиля пользователя"""
|
||||
try:
|
||||
if not hasattr(obj, 'userprofile') or not obj.userprofile.imageURL:
|
||||
return None
|
||||
|
||||
relative_url = obj.userprofile.imageURL.lstrip('/')
|
||||
base_url = settings.BASE_URL.rstrip('/')
|
||||
return f"{base_url}/{relative_url}"
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def to_representation(self, instance: User) -> dict[str, Any]:
|
||||
"""Переопределяется для добавления проверки типа для вывода"""
|
||||
data = super().to_representation(instance)
|
||||
return {
|
||||
'id': data['id'], # int
|
||||
'email': data['email'], # str
|
||||
'account_type': data['account_type'], # AccountTypeLiteral
|
||||
'name': data['name'], # str
|
||||
'surname': data['surname'], # str
|
||||
'imageURL': data['imageURL'], # Optional[str]
|
||||
'uuid': data['uuid'], # Optional[str]
|
||||
}
|
||||
|
||||
|
||||
class LoginRequestSerializer(serializers.Serializer):
|
||||
"""Сериализатор для запроса авторизации"""
|
||||
login = serializers.CharField(help_text="Логин пользователя")
|
||||
password = serializers.CharField(help_text="Пароль пользователя", write_only=True)
|
||||
|
||||
|
||||
class LoginResponseSerializer(serializers.Serializer):
|
||||
"""Сериализатор для ответа при успешной авторизации"""
|
||||
message = serializers.CharField()
|
||||
access = serializers.CharField()
|
||||
refresh = serializers.CharField()
|
||||
user = UserResponseSerializer()
|
||||
@@ -1,6 +1,28 @@
|
||||
from django.urls import path
|
||||
# from . views import ()
|
||||
from django.urls import path, include
|
||||
from .views import LoginViewSet, LogoutView
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
SpectacularSwaggerView,
|
||||
SpectacularRedocView,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'', LoginViewSet, basename='auth')
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('', include(router.urls)),
|
||||
path('logout/', LogoutView.as_view(), name='auth-logout'),
|
||||
path('schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path(
|
||||
'docs/',
|
||||
SpectacularSwaggerView.as_view(url_name='schema'),
|
||||
name='swagger-ui',
|
||||
),
|
||||
# ReDoc UI - альтернативный вариант отображения доков:
|
||||
path(
|
||||
'redoc/',
|
||||
SpectacularRedocView.as_view(url_name='schema'),
|
||||
name='redoc',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,167 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from .serializers import (
|
||||
UserResponseSerializer,
|
||||
LoginRequestSerializer,
|
||||
LoginResponseSerializer
|
||||
)
|
||||
|
||||
from api.utils.cookies import AuthBaseViewSet
|
||||
from api.types import User
|
||||
|
||||
|
||||
class LoginViewSet(AuthBaseViewSet):
|
||||
"""ViewSet для авторизации пользователей"""
|
||||
serializer_class = LoginRequestSerializer
|
||||
|
||||
@extend_schema(
|
||||
summary="Авторизация пользователя",
|
||||
description="Эндпоинт для авторизации пользователя по логину и паролю",
|
||||
request=LoginRequestSerializer,
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
response=LoginResponseSerializer,
|
||||
description="Успешная авторизация",
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Успешный ответ',
|
||||
value={
|
||||
"message": "Успешная авторизация",
|
||||
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
|
||||
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"account_type": "engieneer",
|
||||
"name": "Иван",
|
||||
"surname": "Иванов",
|
||||
"imageURL": "https://example.com/avatar.jpg",
|
||||
"uuid": "abc123"
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
400: OpenApiResponse(
|
||||
description="Неверные параметры запроса",
|
||||
response=OpenApiTypes.OBJECT,
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Отсутствуют обязательные поля',
|
||||
value={"error": "Логин и пароль обязательны"}
|
||||
)
|
||||
]
|
||||
),
|
||||
403: OpenApiResponse(
|
||||
description="Неверный пароль",
|
||||
response=OpenApiTypes.OBJECT,
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Неверный пароль',
|
||||
value={"error": "Неверный пароль"}
|
||||
)
|
||||
]
|
||||
),
|
||||
404: OpenApiResponse(
|
||||
description="Пользователь не найден",
|
||||
response=OpenApiTypes.OBJECT,
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Пользователь не найден',
|
||||
value={"error": "Пользователь не найден"}
|
||||
)
|
||||
]
|
||||
),
|
||||
500: OpenApiResponse(
|
||||
description="Внутренняя ошибка сервера",
|
||||
response=OpenApiTypes.OBJECT,
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Ошибка сервера',
|
||||
value={"error": "Ошибка авторизации"}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
)
|
||||
@action(detail=False, methods=['post'], url_path="login")
|
||||
def login_client(self, request):
|
||||
try:
|
||||
login = request.data.get("login")
|
||||
password = request.data.get("password")
|
||||
|
||||
if not login or not password:
|
||||
return Response(
|
||||
{"error": "Логин и пароль обязательны"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
user = User.objects.get(login=login)
|
||||
except User.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Пользователь не найден"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
if not user.check_password(password):
|
||||
return Response(
|
||||
{"error": "Неверный пароль"},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
refresh = RefreshToken.for_user(user)
|
||||
|
||||
user_data = UserResponseSerializer(user).data
|
||||
|
||||
response = Response({
|
||||
"message": "Успешная авторизация",
|
||||
"access": str(refresh.access_token),
|
||||
"refresh": str(refresh),
|
||||
"user": user_data
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# сеттим куки
|
||||
return self._set_auth_cookies(response, refresh)
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"error": "Ошибка авторизации"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
class LogoutView(APIView):
|
||||
"""ViewSet для выхода из системы"""
|
||||
|
||||
@extend_schema(
|
||||
summary="Выход из системы",
|
||||
description="Эндпоинт для выхода из системы, очищает все токены и куки",
|
||||
responses={
|
||||
200: OpenApiResponse(
|
||||
description="Успешный выход",
|
||||
response=OpenApiTypes.OBJECT,
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
'Успешный выход',
|
||||
value={"message": "Logged out"}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
response = Response({'message': 'Logged out'}, status=status.HTTP_200_OK)
|
||||
|
||||
# чистим куки и sessionID
|
||||
response.delete_cookie('access_token')
|
||||
response.delete_cookie('refresh_token')
|
||||
response.delete_cookie('sessionid')
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user