Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 3m10s
CI/CD Pipeline / Run Tests (push) Successful in 3m35s
CI/CD Pipeline / Telegram Notify Success (push) Has been skipped
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m26s
CI/CD Pipeline / Run Tests (pull_request) Successful in 2m46s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
329 lines
12 KiB
Python
329 lines
12 KiB
Python
from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag
|
||
from django.contrib.auth import authenticate
|
||
from django.contrib.auth.hashers import check_password
|
||
from drf_yasg import openapi
|
||
from drf_yasg.utils import swagger_auto_schema
|
||
from rest_framework import generics, status
|
||
from rest_framework.decorators import api_view, permission_classes
|
||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework.views import APIView
|
||
from rest_framework_simplejwt.views import TokenRefreshView as SimpleJWTTokenRefreshView
|
||
from rest_framework_simplejwt.views import TokenVerifyView as SimpleJWTTokenVerifyView
|
||
|
||
from .serializers import (
|
||
LoginSerializer,
|
||
PasswordChangeSerializer,
|
||
ProfileUpdateSerializer,
|
||
TokenSerializer,
|
||
UserRegistrationSerializer,
|
||
UserSerializer,
|
||
UserUpdateSerializer,
|
||
)
|
||
from .services import ProfileService, UserService
|
||
|
||
# Swagger теги для группировки
|
||
AUTH_TAG = swagger_tag("Аутентификация", "authentication")
|
||
USER_TAG = swagger_tag("Пользователь", "user")
|
||
|
||
|
||
class RegisterView(APIView):
|
||
"""
|
||
Регистрация нового пользователя.
|
||
|
||
Создаёт учётную запись и возвращает JWT токены.
|
||
"""
|
||
|
||
permission_classes = [AllowAny]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[AUTH_TAG],
|
||
operation_summary="Регистрация",
|
||
operation_description="Создание новой учётной записи пользователя.",
|
||
request_body=UserRegistrationSerializer,
|
||
responses={
|
||
201: UserSerializer,
|
||
400: CommonResponses.BAD_REQUEST,
|
||
**ErrorResponses.PUBLIC,
|
||
},
|
||
)
|
||
def post(self, request):
|
||
serializer = UserRegistrationSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
# Убираем password_confirm из данных для создания пользователя
|
||
user_data = serializer.validated_data.copy()
|
||
user_data.pop("password_confirm", None)
|
||
|
||
user = UserService.create_user(**user_data)
|
||
user_serializer = UserSerializer(user)
|
||
tokens = UserService.get_tokens_for_user(user)
|
||
|
||
return Response(
|
||
{"user": user_serializer.data, "tokens": tokens},
|
||
status=status.HTTP_201_CREATED,
|
||
)
|
||
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class LoginView(APIView):
|
||
"""
|
||
Вход пользователя.
|
||
|
||
Возвращает access и refresh токены для авторизации.
|
||
"""
|
||
|
||
permission_classes = [AllowAny]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[AUTH_TAG],
|
||
operation_summary="Вход",
|
||
operation_description=(
|
||
"Аутентификация по username и паролю. Возвращает JWT токены."
|
||
),
|
||
request_body=LoginSerializer,
|
||
responses={
|
||
200: TokenSerializer,
|
||
400: CommonResponses.BAD_REQUEST,
|
||
401: CommonResponses.UNAUTHORIZED,
|
||
**ErrorResponses.PUBLIC,
|
||
},
|
||
)
|
||
def post(self, request):
|
||
serializer = LoginSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
username = serializer.validated_data["username"]
|
||
password = serializer.validated_data["password"]
|
||
|
||
user = authenticate(username=username, password=password)
|
||
if user:
|
||
tokens = UserService.get_tokens_for_user(user)
|
||
return Response(tokens, status=status.HTTP_200_OK)
|
||
else:
|
||
return Response(
|
||
{"error": "Неверные учетные данные"},
|
||
status=status.HTTP_401_UNAUTHORIZED,
|
||
)
|
||
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class LogoutView(APIView):
|
||
"""
|
||
Выход пользователя.
|
||
|
||
Логаут на JWT означает удаление токенов на клиенте.
|
||
"""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[AUTH_TAG],
|
||
operation_summary="Выход",
|
||
operation_description="Выход из системы (удаление токенов на клиенте).",
|
||
responses={
|
||
200: "Успешный выход",
|
||
**ErrorResponses.AUTHENTICATED,
|
||
},
|
||
)
|
||
def post(self, request):
|
||
# Для JWT логаут означает удаление токенов на клиенте.
|
||
# Сервер не хранит сессию и ничего не инвалидирует.
|
||
return Response({"message": "Успешный выход"}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CurrentUserView(APIView):
|
||
"""Получение данных текущего пользователя."""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[USER_TAG],
|
||
operation_summary="Текущий пользователь",
|
||
operation_description="Возвращает данные авторизованного пользователя.",
|
||
responses={
|
||
200: UserSerializer,
|
||
**ErrorResponses.AUTHENTICATED,
|
||
},
|
||
)
|
||
def get(self, request):
|
||
serializer = UserSerializer(request.user)
|
||
return Response(serializer.data)
|
||
|
||
|
||
class UserUpdateView(APIView):
|
||
"""Обновление данных пользователя."""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[USER_TAG],
|
||
operation_summary="Обновить данные",
|
||
operation_description="Частичное обновление данных пользователя.",
|
||
request_body=UserUpdateSerializer,
|
||
responses={
|
||
200: UserSerializer,
|
||
**ErrorResponses.AUTHENTICATED_VALIDATION,
|
||
},
|
||
)
|
||
def patch(self, request):
|
||
serializer = UserUpdateSerializer(request.user, data=request.data, partial=True)
|
||
if serializer.is_valid():
|
||
user = UserService.update_user(request.user.id, **serializer.validated_data)
|
||
user_serializer = UserSerializer(user)
|
||
return Response(user_serializer.data)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class ProfileDetailView(generics.RetrieveUpdateAPIView):
|
||
"""Получение и обновление профиля пользователя."""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
serializer_class = ProfileUpdateSerializer
|
||
http_method_names = ["get", "patch", "head", "options"]
|
||
|
||
def get_object(self):
|
||
profile = ProfileService.get_profile_by_user_id_or_none(self.request.user.id)
|
||
if not profile:
|
||
# Если профиль не существует, создаем его
|
||
from .models import Profile
|
||
|
||
profile = Profile.objects.create(user=self.request.user)
|
||
return profile
|
||
|
||
@swagger_auto_schema(
|
||
tags=[USER_TAG],
|
||
operation_summary="Получить профиль",
|
||
operation_description="Возвращает профиль текущего пользователя.",
|
||
responses={
|
||
200: ProfileUpdateSerializer,
|
||
**ErrorResponses.AUTHENTICATED,
|
||
},
|
||
)
|
||
def get(self, request, *args, **kwargs):
|
||
profile = self.get_object()
|
||
serializer = self.get_serializer(profile)
|
||
return Response(serializer.data)
|
||
|
||
@swagger_auto_schema(
|
||
tags=[USER_TAG],
|
||
operation_summary="Обновить профиль",
|
||
operation_description="Частичное обновление профиля пользователя.",
|
||
request_body=ProfileUpdateSerializer,
|
||
responses={
|
||
200: ProfileUpdateSerializer,
|
||
**ErrorResponses.AUTHENTICATED_VALIDATION,
|
||
},
|
||
)
|
||
def patch(self, request, *args, **kwargs):
|
||
profile = self.get_object()
|
||
serializer = self.get_serializer(profile, data=request.data, partial=True)
|
||
if serializer.is_valid():
|
||
updated_profile = ProfileService.update_profile(
|
||
request.user.id, **serializer.validated_data
|
||
)
|
||
return Response(ProfileUpdateSerializer(updated_profile).data)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class PasswordChangeView(APIView):
|
||
"""Смена пароля пользователя."""
|
||
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[USER_TAG],
|
||
operation_summary="Сменить пароль",
|
||
operation_description="Смена пароля. Требуется текущий пароль для подтверждения.",
|
||
request_body=PasswordChangeSerializer,
|
||
responses={
|
||
200: "Пароль успешно изменен",
|
||
**ErrorResponses.AUTHENTICATED_VALIDATION,
|
||
},
|
||
)
|
||
def post(self, request):
|
||
serializer = PasswordChangeSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
user = request.user
|
||
old_password = serializer.validated_data["old_password"]
|
||
|
||
if check_password(old_password, user.password):
|
||
new_password = serializer.validated_data["new_password"]
|
||
user.set_password(new_password)
|
||
user.save()
|
||
return Response(
|
||
{"message": "Пароль успешно изменен"}, status=status.HTTP_200_OK
|
||
)
|
||
else:
|
||
return Response(
|
||
{"error": "Неверный старый пароль"},
|
||
status=status.HTTP_400_BAD_REQUEST,
|
||
)
|
||
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
@swagger_auto_schema(
|
||
method="get",
|
||
tags=[USER_TAG],
|
||
operation_summary="Полный профиль",
|
||
operation_description="Расширенная информация о пользователе и профиле.",
|
||
responses={
|
||
200: ProfileUpdateSerializer,
|
||
**ErrorResponses.AUTHENTICATED,
|
||
},
|
||
)
|
||
@api_view(["GET"])
|
||
@permission_classes([IsAuthenticated])
|
||
def user_profile_detail(request):
|
||
"""Получение полных данных профиля пользователя."""
|
||
profile_data = ProfileService.get_full_profile_data(request.user.id)
|
||
return Response(profile_data)
|
||
|
||
|
||
class TokenRefreshView(SimpleJWTTokenRefreshView):
|
||
"""Обновление access токена через refresh токен."""
|
||
|
||
permission_classes = [AllowAny]
|
||
|
||
@swagger_auto_schema(
|
||
tags=[AUTH_TAG],
|
||
operation_summary="Обновить токен",
|
||
operation_description="Получение нового access токена по refresh токену.",
|
||
request_body=openapi.Schema(
|
||
type=openapi.TYPE_OBJECT,
|
||
properties={
|
||
"refresh": openapi.Schema(
|
||
type=openapi.TYPE_STRING, description="Refresh token"
|
||
)
|
||
},
|
||
required=["refresh"],
|
||
),
|
||
responses={
|
||
200: TokenSerializer,
|
||
400: CommonResponses.BAD_REQUEST,
|
||
401: CommonResponses.UNAUTHORIZED,
|
||
**ErrorResponses.PUBLIC,
|
||
},
|
||
)
|
||
def post(self, request, *args, **kwargs):
|
||
return super().post(request, *args, **kwargs)
|
||
|
||
|
||
class TokenVerifySwaggerView(SimpleJWTTokenVerifyView):
|
||
"""Проверка валидности access токена."""
|
||
|
||
@swagger_auto_schema(
|
||
tags=[AUTH_TAG],
|
||
operation_summary="Проверить токен",
|
||
operation_description="Проверяет валидность JWT токена.",
|
||
responses={
|
||
200: "Токен валиден",
|
||
400: CommonResponses.BAD_REQUEST,
|
||
401: CommonResponses.UNAUTHORIZED,
|
||
**ErrorResponses.PUBLIC,
|
||
},
|
||
)
|
||
def post(self, request, *args, **kwargs):
|
||
return super().post(request, *args, **kwargs)
|