From e102d6766a9f750cec1e762f901bdca9fa7b56f7 Mon Sep 17 00:00:00 2001 From: icurfer Date: Wed, 14 Jan 2026 21:43:02 +0900 Subject: [PATCH] =?UTF-8?q?v0.0.22=20|=20NHN=20Cloud=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=88=98=EC=A0=95=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PUT /api/auth/nhn-cloud/projects/{id}/ 수정 API - GET /api/auth/nhn-cloud/projects/{id}/ 상세 조회 API - 프로젝트 생성 로직 수정 (encrypted_password 설정) Co-Authored-By: Claude Opus 4.5 --- users/views.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++-- version | 2 +- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/users/views.py b/users/views.py index 704960d..7bd4709 100644 --- a/users/views.py +++ b/users/views.py @@ -503,7 +503,9 @@ class NHNCloudProjectListView(APIView): storage_account=storage_account, is_active=is_first, ) - project.save_credentials(password) + # 암호화된 비밀번호 설정 후 저장 + project.encrypted_password = project.encrypt_password(password) + project.save() logger.info( f"[NHN PROJECT CREATE] user={email} | project={name} | is_active={is_first} | IP={ip} | UA={ua}" @@ -528,7 +530,7 @@ class NHNCloudProjectListView(APIView): class NHNCloudProjectDetailView(APIView): - """NHN Cloud 프로젝트 상세 (삭제)""" + """NHN Cloud 프로젝트 상세 (조회/수정/삭제)""" permission_classes = [IsAuthenticated] def get_project(self, request, project_id): @@ -538,6 +540,96 @@ class NHNCloudProjectDetailView(APIView): except NHNCloudProject.DoesNotExist: return None + def get(self, request, project_id): + """프로젝트 상세 조회""" + with tracer.start_as_current_span("NHNCloudProjectDetailView GET") as span: + email, ip, ua = get_request_info(request) + + project = self.get_project(request, project_id) + if not project: + return Response({"error": "프로젝트를 찾을 수 없습니다."}, status=404) + + span.add_event("NHN project retrieved", attributes={"email": email, "project": project.name}) + + return Response({ + "id": project.id, + "name": project.name, + "tenant_id": project.tenant_id, + "username": project.username, + "storage_account": project.storage_account or "", + "is_active": project.is_active, + "created_at": project.created_at.isoformat(), + }) + + def put(self, request, project_id): + """프로젝트 수정""" + with tracer.start_as_current_span("NHNCloudProjectDetailView PUT") as span: + email, ip, ua = get_request_info(request) + + project = self.get_project(request, project_id) + if not project: + logger.warning( + f"[NHN PROJECT UPDATE] user={email} | status=fail | reason=not_found | IP={ip} | UA={ua}" + ) + return Response({"error": "프로젝트를 찾을 수 없습니다."}, status=404) + + # 수정 가능한 필드 + name = request.data.get("name") + tenant_id = request.data.get("tenant_id") + username = request.data.get("username") + password = request.data.get("password") # 비어있으면 변경 안함 + storage_account = request.data.get("storage_account") + + # 필수 필드 검증 + if not name or not tenant_id or not username: + return Response( + {"error": "name, tenant_id, username은 필수입니다."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # tenant_id 중복 체크 (자기 자신 제외) + if NHNCloudProject.objects.filter( + user=request.user, tenant_id=tenant_id + ).exclude(id=project_id).exists(): + return Response( + {"error": "이미 등록된 Tenant ID입니다."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + project.name = name + project.tenant_id = tenant_id + project.username = username + if storage_account is not None: + project.storage_account = storage_account + + # 비밀번호가 제공된 경우에만 변경 + if password: + project.encrypted_password = project.encrypt_password(password) + + project.save() + + logger.info( + f"[NHN PROJECT UPDATE] user={email} | project={name} | IP={ip} | UA={ua}" + ) + span.add_event("NHN project updated", attributes={"email": email, "project": name}) + + return Response({ + "id": project.id, + "name": project.name, + "tenant_id": project.tenant_id, + "username": project.username, + "storage_account": project.storage_account or "", + "is_active": project.is_active, + "created_at": project.created_at.isoformat(), + }) + + except Exception as e: + logger.exception( + f"[NHN PROJECT UPDATE] user={email} | status=fail | reason=exception | IP={ip} | UA={ua}" + ) + return Response({"error": f"프로젝트 수정 실패: {str(e)}"}, status=500) + def delete(self, request, project_id): """프로젝트 삭제""" with tracer.start_as_current_span("NHNCloudProjectDetailView DELETE") as span: diff --git a/version b/version index ad3e98d..6a11e82 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.0.21 +v0.0.22