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)