Files
mostovik-backend/src/apps/user/views.py
Aleksandr Meshchriakov a91ed1f1ae
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
feat(registry): add new endpoints for registers, exchange, and backups; update routing and configurations
2026-03-04 15:36:57 +01:00

329 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)