from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework.views import APIView from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenRefreshSerializer, TokenObtainSerializer from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.exceptions import TokenError, InvalidToken from rest_framework import status from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated from django.contrib.auth import authenticate from django.db.models import Case, When, Value, IntegerField, Q from .models import * from .serializers import * from .services import * from projects.serializers import * from portfolios.serializers import * from django.db import transaction from google.oauth2 import id_token from google.auth.transport import requests class RefreshAPIView(APIView): permission_classes = [AllowAny] # access token 재발급 @transaction.atomic def post(self, request): refresh = request.COOKIES.get("refresh") if not refresh: return Response({"message": "No refresh token"}, status=status.HTTP_400_BAD_REQUEST) if request.data.get("user_id", None) != RefreshToken(refresh).payload.get("user_id", None): return Response({"message": "Wrong userid"}, status=status.HTTP_400_BAD_REQUEST) try: serializer = TokenRefreshSerializer(data={'refresh': refresh}) if serializer.is_valid(): res = Response({"access": serializer.validated_data['access']}, status=status.HTTP_200_OK) res.set_cookie("refresh", serializer.validated_data['refresh'], httponly=True, samesite=None, secure=not settings.DEBUG) return res except TokenError as e: return Response({"message": f"Invalid token: {e}"}, status=status.HTTP_401_UNAUTHORIZED) class GoogleLoginAPIView(APIView): permission_classes = [AllowAny] @transaction.atomic def post(self, request): token_str = request.data.get("id_token") if not token_str: return Response({"message": "no id_token"}, status=status.HTTP_400_BAD_REQUEST) try: idinfo = id_token.verify_oauth2_token( token_str, requests.Request(), settings.GOOGLE_CLIENT_ID ) except ValueError: return Response({"message": "wrong id_token"}, status=status.HTTP_400_BAD_REQUEST) email = idinfo["email"] # sub = idinfo["sub"] # Google 고유 ID # 이미 소셜 회원가입을 한 경우, 로그인 기능. if user := User.objects.filter(email=email).first(): # 로그인 전 쿠키에 있는 리프레시 토큰 무효화 try: refresh = request.COOKIES.get('refresh') if refresh: refresh_token = RefreshToken(refresh) refresh_token.blacklist() except TokenError as e: pass # 어차피 만료된거면 그냥 넘어가기 refresh = RefreshToken.for_user(user) access = str(refresh.access_token) res = Response( { "message": "login success", "user_id": user.id, "nickname": user.nickname, "access": access, "is_member": True, "is_custom_url": user.is_custom_url }, status=status.HTTP_200_OK, ) res.set_cookie("refresh", str(refresh), httponly=True, samesite=None, secure=not settings.DEBUG) return res else: return Response( { "message": "Not member", "email": email, "is_member": False }, status=status.HTTP_200_OK ) class JoinAPIView(APIView): permission_classes = [AllowAny] # 회원가입 @transaction.atomic def post(self, request): serializer = JoinSerializer(data=request.data) if serializer.is_valid(): user = serializer.save() access = str(RefreshToken.for_user(user).access_token) res = Response({ "message": "회원가입이 완료되었습니다.", "access": access }, status=status.HTTP_200_OK) return res return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class LoginAPIView(APIView): permission_classes = [AllowAny] # 로그인 @transaction.atomic def post(self, request): email=request.data.get("email", None) password=request.data.get("password", None) # 로그인 전 쿠키에 있는 리프레시 토큰 무효화 try: refresh = request.COOKIES.get('refresh') if refresh: refresh_token = RefreshToken(refresh) refresh_token.blacklist() except TokenError as e: pass if user := authenticate(email=email, password=password): serializer = TokenObtainPairSerializer(data={'email': email, 'password': password}) # id, 비번 맞나 틀리나 검사 if serializer.is_valid(): res = Response( { "message": "login success", "user_id": user.id, "nickname": user.nickname, "access": serializer.validated_data['access'], "is_custom_url": user.is_custom_url }, status=status.HTTP_200_OK, ) res.set_cookie("refresh", serializer.validated_data['refresh'], httponly=True, samesite=None, secure=not settings.DEBUG) return res else: return Response(serializer.errors) else: # id, 비번 둘 중 하나가 틀렸을 때 return Response({"message": "아이디 혹은 비밀번호가 맞지 않습니다."}, status=status.HTTP_400_BAD_REQUEST) class CheckUserFieldDuplicateAPIView(APIView): permission_classes = [AllowAny] # 유저 필드 중복 확인 def get(self, request): field = request.query_params.get('field') value = request.query_params.get('value') if not field or not value: return Response({"message": "올바르지 않은 요청입니다."}, status=status.HTTP_400_BAD_REQUEST) try: if CheckUserFieldDuplicateService.check_duplicate(field, value): return Response({"message": f"존재하는 {field} 입니다."}, status=status.HTTP_400_BAD_REQUEST) else: return Response({"message": f"사용해도 되는 {field} 입니다."}, status=status.HTTP_200_OK) except ValueError as e: return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST) class TagUserAPIView(APIView): def get(self, request): nickname = request.query_params.get('nickname') users = User.objects.filter(nickname__icontains=nickname).annotate( priority=Case( When(nickname__iexact=nickname, then=Value(0)), default=Value(1), output_field=IntegerField() ) ).order_by('priority').values('profile_image', 'nickname')[:5] serializer = TagUserSerializer(users, many=True) return Response({'users': serializer.data}) class SetPortofolioRequiredInfoAPIView(APIView): def get(self, request): custom_url = request.GET.get('custom_url', None) if not custom_url: return Response({"message": "no url"}, status=status.HTTP_400_BAD_REQUEST) if User.objects.filter(custom_url=custom_url).exists(): return Response({"message": "already used url"}, status=status.HTTP_400_BAD_REQUEST) else: return Response({"message": "can use this url"}, status=status.HTTP_200_OK) @transaction.atomic def patch(self, request): user = request.user serializer = SetPortofolioRequiredInfoSerializer(user, data=request.data) if serializer.is_valid(): serializer.save() user.is_custom_url = True user.save() return Response({"message": "updated successfully"}, status=status.HTTP_202_ACCEPTED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 마이페이지 관련 class MyPageProfileAPIView(APIView): # 프로필 조회(안 읽은 알림 수) def get(self, request, nickname): target_user = get_object_or_404(User, nickname=nickname) user = request.user serializer = UserProfileSerializer(target_user) data = serializer.data data['represent_portfolio_id'] = UserToPortfolioService.get_represent_portfolio(target_user) data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user) return Response(data, status=status.HTTP_200_OK) # 프로필 수정 @transaction.atomic def patch(self, request, nickname): target_user = get_object_or_404(User, nickname=nickname) user = request.user if user == target_user: serializer = UserProfileSerializer(user, request.data, partial=True) if serializer.is_valid(): serializer.save() data = serializer.data data['represent_portfolio_id'] = UserToPortfolioService.get_represent_portfolio(target_user) data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user) return Response(data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST) class MyPageWorkListAPIView(APIView): # 포폴, 플젝, 작업중, 스크랩 조회(안 읽은 알림 수) def get(self, request, nickname): target_user = get_object_or_404(User, nickname=nickname) user = request.user retreive_type = request.query_params.get('type') data = {} if retreive_type == 'portfolio': portfolios = UserToPortfolioService.get_published_portfolio(target_user) serializer = PortfolioListViewSerializer(portfolios, many=True) data = { "portfolios": serializer.data } elif retreive_type == 'project': solo_projects = UserToProjectService.get_published_solo_project(target_user) team_projects = UserToProjectService.get_published_team_project(target_user) sp_serializer = ProjectListViewSerializer(solo_projects, many=True) tp_serializer = ProjectListViewSerializer(team_projects, many=True) data = { "solo_projects": sp_serializer.data, "team_projects": tp_serializer.data, } elif retreive_type == 'working': if user != target_user: return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST) portfolios = UserToPortfolioService.get_unpublished_portfolio(target_user) solo_projects = UserToProjectService.get_unpublished_solo_project(target_user) team_projects = UserToProjectService.get_unpublished_team_project(target_user) po_serializer = PortfolioListViewSerializer(portfolios, many=True) sp_serializer = ProjectListViewSerializer(solo_projects, many=True) tp_serializer = ProjectListViewSerializer(team_projects, many=True) data = { "portfolios": po_serializer.data, "solo_projects": sp_serializer.data, "team_projects": tp_serializer.data, } elif retreive_type == 'scrap': if user != target_user: return Response({"message": "Not account owner"}, status=status.HTTP_400_BAD_REQUEST) portfolios = UserToPortfolioService.get_scrap_portfolio(target_user) po_serializer = PortfolioListViewSerializer(portfolios, many=True) projects = UserToProjectService.get_scrap_project(target_user) pr_serializer = ProjectListViewSerializer(projects, many=True) data = { "portfolios": po_serializer.data, "projects": pr_serializer.data, } else: return Response({"message": "not allowed retreive_type"}, status=status.HTTP_400_BAD_REQUEST) data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user) return Response(data, status=status.HTTP_200_OK) class MyPageMemberInfoAPIView(APIView): # 내 정보 조회(안 읽은 알림 수) def get(self, request): user = request.user serializer = UserMemberInfoSerializer(user) data = serializer.data data['new_notification_count'] = UserToNotificationService.get_new_notification_count(user) return Response(data, status=status.HTTP_200_OK) # 내 정보 수정 @transaction.atomic def patch(self, request): user = request.user serializer = UserMemberInfoSerializer(user, request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)