getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } $this->logUserActivity('profile.show'); return $this->success( 'user.profile_success', (new UserResource($user))->toProfileArray() ); } catch (\Exception $e) { return $this->error('user.profile_failed', 500, null, ['error' => $e->getMessage()]); } } /** * 사용자 프로필을 업데이트합니다. * * @param UpdateProfileRequest $request 프로필 업데이트 요청 데이터 * @return JsonResponse 업데이트된 사용자 정보를 포함한 JSON 응답 */ public function update(UpdateProfileRequest $request): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } $updatedUser = $this->userService->updateUser($user, $request->validated()); $this->logUserActivity('profile.update', $request->validated()); return $this->successWithResource( 'user.update_success', new UserResource($updatedUser) ); } catch (ValidationException $e) { return $this->validationError($e->errors(), 'user.update_failed'); } catch (\Exception $e) { return $this->error('user.update_failed', 500, $e->getMessage()); } } /** * 사용자의 언어 설정을 변경합니다. * * @return JsonResponse 언어 변경 결과와 사용자 정보를 포함한 JSON 응답 */ public function updateLanguage(): JsonResponse { try { $user = $this->getCurrentUser(); $language = request('language'); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } if (! in_array($language, config('app.supported_locales', ['ko', 'en']), true)) { return $this->error('user.invalid_language', 400); } $updatedUser = $this->userService->updateUser($user, ['language' => $language]); $this->logUserActivity('profile.update_language', ['language' => $language]); return $this->successWithResource( 'user.language_update_success', new UserResource($updatedUser) ); } catch (\Exception $e) { return $this->error('user.language_update_failed', 500, $e->getMessage()); } } /** * 사용자의 활동 기록을 조회합니다. * * @return JsonResponse 사용자 활동 기록을 포함한 JSON 응답 */ public function activityLog(): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } $activities = $this->userService->getUserActivityLogs($user->id); $this->logUserActivity('profile.activity_log'); return $this->success( 'user.activity_log_success', ['activities' => $activities] ); } catch (\Exception $e) { return $this->error('user.activity_log_failed', 500, null, ['error' => $e->getMessage()]); } } /** * 사용자 아바타를 업로드합니다. * * attachments 테이블의 다형성 관계를 사용하여 아바타를 저장합니다. * * @param UploadAvatarRequest $request 아바타 업로드 요청 데이터 * @return JsonResponse 업로드 결과를 포함한 JSON 응답 */ public function uploadAvatar(UploadAvatarRequest $request): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } // 기존 아바타 Attachment 삭제 $existingAttachment = $user->avatarAttachment; if ($existingAttachment) { $this->attachmentService->delete($existingAttachment->id); } // 새 Attachment 생성 (다형성 관계) $attachment = $this->attachmentService->upload( $request->file('avatar'), User::class, $user->id, 'avatar', AttachmentSourceType::Core ); $this->logUserActivity('profile.avatar.upload', [ 'attachment_id' => $attachment->id, ]); // 관계 새로고침 $user->load('avatarAttachment'); return $this->success( 'user.avatar_upload_success', [ 'avatar' => $user->getAvatarUrl(), 'attachment_id' => $attachment->id, ] ); } catch (\Exception $e) { return $this->error('user.avatar_upload_failed', 500, $e->getMessage()); } } /** * 사용자 아바타를 삭제합니다. * * attachments 테이블의 다형성 관계를 사용하여 아바타를 삭제합니다. * * @return JsonResponse 삭제 결과를 포함한 JSON 응답 */ public function deleteAvatar(): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } // 아바타 Attachment 확인 $attachment = $user->avatarAttachment; if (! $attachment) { return $this->error('user.avatar_not_found', 404); } // Attachment 삭제 (파일도 함께 삭제됨) $this->attachmentService->delete($attachment->id); $this->logUserActivity('profile.avatar.delete'); return $this->success('user.avatar_delete_success'); } catch (\Exception $e) { return $this->error('user.avatar_delete_failed', 500, null, ['error' => $e->getMessage()]); } } /** * 사용자 비밀번호를 확인합니다. * * @param Request $request 비밀번호 확인 요청 데이터 * @return JsonResponse 확인 결과를 포함한 JSON 응답 */ public function verifyPassword(VerifyPasswordRequest $request): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } if (! Hash::check($request->password, $user->password)) { return $this->error('user.password_incorrect', 401); } $this->logUserActivity('profile.verify_password'); return $this->success('user.password_verified'); } catch (ValidationException $e) { return $this->validationError($e->errors(), 'user.password_verify_failed'); } catch (\Exception $e) { return $this->error('user.password_verify_failed', 500, null, ['error' => $e->getMessage()]); } } /** * 사용자 비밀번호를 변경합니다. * * @param ChangePasswordRequest $request 비밀번호 변경 요청 데이터 * @return JsonResponse 비밀번호 변경 결과를 포함한 JSON 응답 */ public function changePassword(ChangePasswordRequest $request): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } // 새 비밀번호로 업데이트 (UserService에서 해싱 처리) $this->userService->updateUser($user, [ 'password' => $request->password, ]); $this->logUserActivity('profile.change_password'); return $this->success('user.password_change_success'); } catch (ValidationException $e) { return $this->validationError($e->errors(), 'user.password_change_failed'); } catch (\Exception $e) { // 주의: IdentityVerificationRequiredException 은 \Error 자식이라 이 catch 에 잡히지 않음. // 글로벌 핸들러가 자동으로 428 매핑 — 모든 컨트롤러 (코어/모듈/플러그인) 에 동일 보장. return $this->error('user.password_change_failed', 500, null, ['error' => $e->getMessage()]); } } /** * 사용자 계정을 탈퇴 처리합니다. * * @return JsonResponse 탈퇴 처리 결과를 포함한 JSON 응답 */ public function destroy(): JsonResponse { try { $user = $this->getCurrentUser(); if (! $user) { return $this->unauthorized('auth.unauthenticated'); } // 사용자 탈퇴 처리 (아바타, 토큰 삭제 및 익명화) $this->userService->withdrawUser($user); // 활동 로그는 성공한 뒤에만 남긴다 — 실패한 탈퇴가 기록으로 남으면 // 이력만 보고는 탈퇴한 것으로 읽힌다. $this->logUserActivity('profile.withdraw'); return $this->success('user.withdraw_success'); } catch (ValidationException $e) { // 관리자/수퍼관리자 탈퇴 차단은 요청이 잘못된 것이지 서버 오류가 아니다. return $this->validationError($e->errors(), 'user.withdraw_failed'); } catch (CannotDeleteSuperAdminException $e) { // 이 예외는 ValidationException 을 상속하지 않으므로 별도 매핑이 필요하다. return $this->validationError( ['general' => [$e->getMessage()]], 'user.withdraw_failed' ); } catch (\Exception $e) { return $this->error('user.withdraw_failed', 500, null, ['error' => $e->getMessage()]); } } }