From ee8c7ff9c0c7864dab88f0baa66050cbf9686e0d Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Wed, 5 Mar 2025 14:51:41 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[TNT-258]=20feat:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/application/member/MemberService.java | 10 ++++++ .../java/com/tnt/domain/member/Member.java | 6 ++++ .../request/UpdateMemberInfoRequest.java | 36 +++++++++++++++++++ .../response/UpdateMemberInfoResponse.java | 10 ++++++ .../presentation/member/MemberController.java | 11 ++++++ 5 files changed, 73 insertions(+) create mode 100644 src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java create mode 100644 src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index 80a9582d..c25ad51c 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -23,9 +23,11 @@ import com.tnt.domain.trainee.PtGoal; import com.tnt.domain.trainee.Trainee; import com.tnt.domain.trainer.Trainer; +import com.tnt.dto.member.request.UpdateMemberInfoRequest; import com.tnt.dto.member.response.GetMemberInfoResponse; import com.tnt.dto.member.response.GetMemberInfoResponse.TraineeInfo; import com.tnt.dto.member.response.GetMemberInfoResponse.TrainerInfo; +import com.tnt.dto.member.response.UpdateMemberInfoResponse; import com.tnt.gateway.dto.response.CheckSessionResponse; import com.tnt.infrastructure.mysql.repository.member.MemberRepository; import com.tnt.infrastructure.mysql.repository.member.MemberSearchRepository; @@ -118,4 +120,12 @@ public Member getMemberWithSocialIdAndSocialType(String socialId, SocialType soc return memberRepository.findBySocialIdAndSocialTypeAndDeletedAtIsNull(socialId, socialType) .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); } + + public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { + Member findMember = getMemberWithMemberId(memberId); + + if (findMember.getMemberType() == TRAINER) { + findMember.updateName(request.name()); + } + } } diff --git a/src/main/java/com/tnt/domain/member/Member.java b/src/main/java/com/tnt/domain/member/Member.java index 05100cbc..31467958 100644 --- a/src/main/java/com/tnt/domain/member/Member.java +++ b/src/main/java/com/tnt/domain/member/Member.java @@ -153,6 +153,12 @@ public void updateFcmTokenIfExpired(String fcmToken) { } } + public void updateName(String name) { + if (!isBlank(name) && !this.name.equals(name)) { + this.name = name; + } + } + public void updateProfileImageUrl(String profileImageUrl) { if (!isBlank(profileImageUrl) && !this.profileImageUrl.equals(profileImageUrl)) { this.profileImageUrl = profileImageUrl; diff --git a/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java b/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java new file mode 100644 index 00000000..b33ba9df --- /dev/null +++ b/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java @@ -0,0 +1,36 @@ +package com.tnt.dto.member.request; + +import java.time.LocalDate; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Past; + +@Schema(description = "회원 정보 수정 API 요청") +public record UpdateMemberInfoRequest( + @Schema(description = "회원 이름", example = "홍길동", nullable = false) + @NotBlank(message = "회원 이름은 필수입니다.") + String name, + + @Schema(description = "생년월일", example = "2025-01-01", nullable = true) + @Past(message = "생년월일은 과거 날짜여야 합니다.") + LocalDate birthday, + + @Schema(description = "키 (cm)", example = "180.5", nullable = true) + @Digits(integer = 3, fraction = 2, message = "키는 정수부 3자리, 소수점 2자리까지 입력 가능합니다.") + Double height, + + @Schema(description = "몸무게 (kg)", example = "75.5", nullable = true) + @Digits(integer = 3, fraction = 2, message = "몸무게는 정수부 3자리, 소수점 2자리까지 입력 가능합니다.") + Double weight, + + @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) + String cautionNote, + + @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) + List goalContents +) { + +} diff --git a/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java b/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java new file mode 100644 index 00000000..d986f491 --- /dev/null +++ b/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java @@ -0,0 +1,10 @@ +package com.tnt.dto.member.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "회원 정보 수정 API 응답") +public record UpdateMemberInfoResponse( + +) { + +} diff --git a/src/main/java/com/tnt/presentation/member/MemberController.java b/src/main/java/com/tnt/presentation/member/MemberController.java index c6289606..9df5ea68 100644 --- a/src/main/java/com/tnt/presentation/member/MemberController.java +++ b/src/main/java/com/tnt/presentation/member/MemberController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; @@ -18,8 +19,10 @@ import com.tnt.application.s3.S3Service; import com.tnt.dto.member.WithdrawDto; import com.tnt.dto.member.request.SignUpRequest; +import com.tnt.dto.member.request.UpdateMemberInfoRequest; import com.tnt.dto.member.response.GetMemberInfoResponse; import com.tnt.dto.member.response.SignUpResponse; +import com.tnt.dto.member.response.UpdateMemberInfoResponse; import com.tnt.gateway.config.AuthMember; import io.swagger.v3.oas.annotations.Operation; @@ -56,6 +59,14 @@ public GetMemberInfoResponse getMemberInfo(@AuthMember Long memberId) { return memberService.getMemberInfo(memberId); } + @Operation(summary = "회원 정보 수정 API") + @PostMapping + @ResponseStatus(OK) + public UpdateMemberInfoResponse updateMemberInfo(@AuthMember Long memberId, + @RequestBody UpdateMemberInfoRequest request) { + return memberService.updateMemberInfo(memberId, request); + } + @Operation(summary = "회원 탈퇴 API") @PostMapping("/withdraw") @ResponseStatus(OK) From 498c5606cff1576049bf537588611a7a988b19d3 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Wed, 5 Mar 2025 18:20:38 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[TNT-258]=20feat:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/application/member/MemberService.java | 110 ++++++++++++++-- .../java/com/tnt/domain/member/Member.java | 4 + .../java/com/tnt/domain/trainee/PtGoal.java | 8 +- .../java/com/tnt/domain/trainee/Trainee.java | 14 +- .../request/UpdateMemberInfoRequest.java | 7 + .../response/GetMemberInfoResponse.java | 8 +- .../response/UpdateMemberInfoResponse.java | 36 ++++++ .../presentation/member/MemberController.java | 8 +- .../member/MemberControllerTest.java | 121 ++++++++++++++++++ 9 files changed, 287 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index 47d786f1..3b844c49 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -6,7 +6,10 @@ import static com.tnt.domain.member.MemberType.TRAINER; import static com.tnt.dto.member.MemberProjection.MemberTypeDto; +import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,12 +28,14 @@ import com.tnt.domain.trainer.Trainer; import com.tnt.dto.member.request.UpdateMemberInfoRequest; import com.tnt.dto.member.response.GetMemberInfoResponse; -import com.tnt.dto.member.response.GetMemberInfoResponse.TraineeInfo; -import com.tnt.dto.member.response.GetMemberInfoResponse.TrainerInfo; +import com.tnt.dto.member.response.GetMemberInfoResponse.GetTraineeInfo; +import com.tnt.dto.member.response.GetMemberInfoResponse.GetTrainerInfo; import com.tnt.dto.member.response.UpdateMemberInfoResponse; +import com.tnt.dto.member.response.UpdateMemberInfoResponse.UpdateTraineeInfo; import com.tnt.gateway.dto.response.CheckSessionResponse; import com.tnt.infrastructure.mysql.repository.member.MemberRepository; import com.tnt.infrastructure.mysql.repository.member.MemberSearchRepository; +import com.tnt.infrastructure.mysql.repository.pt.PtGoalRepository; import lombok.RequiredArgsConstructor; @@ -45,6 +50,7 @@ public class MemberService { private final MemberRepository memberRepository; private final MemberSearchRepository memberSearchRepository; + private final PtGoalRepository ptGoalRepository; @Transactional(readOnly = true) public GetMemberInfoResponse getMemberInfo(Long memberId) { @@ -62,21 +68,25 @@ public GetMemberInfoResponse getMemberInfo(Long memberId) { int totalTraineeCount = ptTrainerTrainees.size(); - TrainerInfo trainerInfo = new TrainerInfo(activeTraineeCount, totalTraineeCount); + GetTrainerInfo getTrainerInfo = new GetTrainerInfo(activeTraineeCount, totalTraineeCount); memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), - member.getMemberType(), member.getSocialType(), trainerInfo, null); - } else if (member.getMemberType() == TRAINEE) { + member.getMemberType(), member.getSocialType(), getTrainerInfo, null); + } + + if (member.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); - List ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()).stream().map( - PtGoal::getContent).toList(); + List ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()) + .stream() + .map(PtGoal::getContent) + .toList(); boolean isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); - TraineeInfo traineeInfo = new TraineeInfo(isConnected, member.getBirthday(), + GetTraineeInfo getTraineeInfo = new GetTraineeInfo(isConnected, member.getBirthday(), member.getAge(), trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), - member.getMemberType(), member.getSocialType(), null, traineeInfo); + member.getMemberType(), member.getSocialType(), null, getTraineeInfo); } return memberInfo; @@ -92,7 +102,9 @@ public CheckSessionResponse getMemberType(Long memberId) { if (memberTypeDto.memberType() == TRAINER) { Trainer trainer = trainerService.getByMemberId(memberId); isConnected = ptService.isPtTrainerTraineeExistWithTrainerId(trainer.getId()); - } else if (memberTypeDto.memberType() == TRAINEE) { + } + + if (memberTypeDto.memberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); } @@ -100,6 +112,42 @@ public CheckSessionResponse getMemberType(Long memberId) { return new CheckSessionResponse(memberTypeDto.memberType(), isConnected); } + @Transactional + public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, + String profileImageUrl) { + Member findMember = getByMemberId(memberId); + UpdateMemberInfoResponse memberInfo = null; + + findMember.updateName(request.name()); + findMember.updateProfileImageUrl(profileImageUrl); + + // 트레이너 + if (findMember.getMemberType() == TRAINER) { + memberInfo = new UpdateMemberInfoResponse(findMember.getMemberType(), profileImageUrl, findMember.getName(), + null); + } + + // 트레이니 + if (findMember.getMemberType() == TRAINEE) { + Trainee trainee = traineeService.getByMemberId(memberId); + + findMember.updateBirthday(request.birthday()); + trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote()); + + List ptGoals = updatePtGoals(trainee, request.goalContents()).stream() + .map(PtGoal::getContent) + .toList(); + + UpdateTraineeInfo updateTraineeInfo = new UpdateTraineeInfo(findMember.getBirthday(), findMember.getAge(), + trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); + + memberInfo = new UpdateMemberInfoResponse(findMember.getMemberType(), profileImageUrl, findMember.getName(), + updateTraineeInfo); + } + + return memberInfo; + } + public void validateMemberNotExists(String socialId, SocialType socialType) { memberRepository.findBySocialIdAndSocialTypeAndDeletedAtIsNull(socialId, socialType) .ifPresent(member -> { @@ -117,11 +165,45 @@ public Member getBySocialIdAndSocialType(String socialId, SocialType socialType) .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); } - public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { - Member findMember = getMemberWithMemberId(memberId); + private List updatePtGoals(Trainee trainee, List newGoalContents) { + // 기존 PT 목표들 조회 + List currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId()); - if (findMember.getMemberType() == TRAINER) { - findMember.updateName(request.name()); + // 기존 목표 중 더 이상 필요없는 목표 삭제 + if (!currentPtGoals.isEmpty()) { + List goalsToDelete = new ArrayList<>(); + + for (PtGoal currentGoal : currentPtGoals) { + if (!newGoalContents.contains(currentGoal.getContent())) { + goalsToDelete.add(currentGoal); + } + } + + if (!goalsToDelete.isEmpty()) { + ptGoalRepository.deleteAll(goalsToDelete); + currentPtGoals.removeAll(goalsToDelete); + } } + + // 새로운 목표 추가 (기존에 없는 것만) + Set existingContents = currentPtGoals.stream().map(PtGoal::getContent).collect(Collectors.toSet()); + + List newPtGoals = newGoalContents.stream() + .filter(content -> !existingContents.contains(content)) + .map(content -> PtGoal.builder() + .traineeId(trainee.getId()) + .content(content) + .build()) + .toList(); + + if (!newPtGoals.isEmpty()) { + ptGoalRepository.saveAll(newPtGoals); + } + + // 최종 목표 목록 리턴 (기존 유지된 목표 + 새로 추가된 목표) + List result = new ArrayList<>(currentPtGoals); + result.addAll(newPtGoals); + + return result; } } diff --git a/src/main/java/com/tnt/domain/member/Member.java b/src/main/java/com/tnt/domain/member/Member.java index 31467958..09f49aba 100644 --- a/src/main/java/com/tnt/domain/member/Member.java +++ b/src/main/java/com/tnt/domain/member/Member.java @@ -165,6 +165,10 @@ public void updateProfileImageUrl(String profileImageUrl) { } } + public void updateBirthday(LocalDate birthday) { + this.birthday = birthday; + } + public void softDelete() { this.socialId = null; this.deletedAt = LocalDateTime.now(); diff --git a/src/main/java/com/tnt/domain/trainee/PtGoal.java b/src/main/java/com/tnt/domain/trainee/PtGoal.java index 7222115a..e1269198 100644 --- a/src/main/java/com/tnt/domain/trainee/PtGoal.java +++ b/src/main/java/com/tnt/domain/trainee/PtGoal.java @@ -48,6 +48,10 @@ public PtGoal(Long id, Long traineeId, String content) { this.content = validateContent(content); } + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } + private String validateContent(String content) { if (isBlank(content) || content.length() > CONTENT_LENGTH) { throw new IllegalArgumentException(PT_GOAL_INVALID_CONTENT.getMessage()); @@ -55,8 +59,4 @@ private String validateContent(String content) { return content; } - - public void softDelete() { - this.deletedAt = LocalDateTime.now(); - } } diff --git a/src/main/java/com/tnt/domain/trainee/Trainee.java b/src/main/java/com/tnt/domain/trainee/Trainee.java index c3cd0d61..01bc4c26 100644 --- a/src/main/java/com/tnt/domain/trainee/Trainee.java +++ b/src/main/java/com/tnt/domain/trainee/Trainee.java @@ -64,6 +64,16 @@ public Trainee(Long id, Member member, Double height, Double weight, @Nullable S validateAndSetCautionNote(cautionNote); } + public void updateTraineeInfo(Double height, Double weight, String cautionNote) { + this.height = height; + this.weight = weight; + this.cautionNote = cautionNote; + } + + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + } + private void validateAndSetCautionNote(String cautionNote) { if (isNull(cautionNote)) { return; @@ -75,8 +85,4 @@ private void validateAndSetCautionNote(String cautionNote) { this.cautionNote = cautionNote; } - - public void softDelete() { - this.deletedAt = LocalDateTime.now(); - } } diff --git a/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java b/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java index b33ba9df..84681955 100644 --- a/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java +++ b/src/main/java/com/tnt/dto/member/request/UpdateMemberInfoRequest.java @@ -3,13 +3,20 @@ import java.time.LocalDate; import java.util.List; +import com.tnt.domain.member.MemberType; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Past; @Schema(description = "회원 정보 수정 API 요청") public record UpdateMemberInfoRequest( + @Schema(description = "회원 타입", example = "TRAINER", nullable = false) + @NotNull(message = "회원 타입은 필수입니다.") + MemberType memberType, + @Schema(description = "회원 이름", example = "홍길동", nullable = false) @NotBlank(message = "회원 이름은 필수입니다.") String name, diff --git a/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java b/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java index 9afbede3..54f87a3e 100644 --- a/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java +++ b/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java @@ -26,13 +26,13 @@ public record GetMemberInfoResponse( SocialType socialType, @Schema(description = "트레이너 정보", nullable = true) - TrainerInfo trainer, + GetTrainerInfo trainer, @Schema(description = "트레이니 정보", nullable = true) - TraineeInfo trainee + GetTraineeInfo trainee ) { - public record TrainerInfo( + public record GetTrainerInfo( @Schema(description = "관리 중인 회원", example = "23", nullable = true) Integer activeTraineeCount, @@ -42,7 +42,7 @@ public record TrainerInfo( } - public record TraineeInfo( + public record GetTraineeInfo( @Schema(description = "트레이너 연결 여부", example = "true", nullable = false) Boolean isConnected, diff --git a/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java b/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java index d986f491..3b4a7458 100644 --- a/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java +++ b/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java @@ -1,10 +1,46 @@ package com.tnt.dto.member.response; +import java.time.LocalDate; +import java.util.List; + +import com.tnt.domain.member.MemberType; + import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "회원 정보 수정 API 응답") public record UpdateMemberInfoResponse( + @Schema(description = "회원 타입", example = "TRAINER", nullable = false) + MemberType memberType, + + @Schema(description = "프로필 사진 URL", example = "https://images.tntapp.co.kr/profiles/trainers/basic_profile_trainer.svg", nullable = false) + String profileImageUrl, + @Schema(description = "회원 이름", example = "홍길동", nullable = false) + String name, + + @Schema(description = "트레이니 정보", nullable = true) + UpdateTraineeInfo trainee ) { + public record UpdateTraineeInfo( + @Schema(description = "생년월일", example = "2025-01-01", nullable = true) + LocalDate birthday, + + @Schema(description = "나이", example = "25", nullable = true) + Integer age, + + @Schema(description = "키 (cm)", example = "180.5", nullable = true) + Double height, + + @Schema(description = "몸무게 (kg)", example = "75.5", nullable = true) + Double weight, + + @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) + String cautionNote, + + @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) + List ptGoals + ) { + + } } diff --git a/src/main/java/com/tnt/presentation/member/MemberController.java b/src/main/java/com/tnt/presentation/member/MemberController.java index aa06ddac..8387e057 100644 --- a/src/main/java/com/tnt/presentation/member/MemberController.java +++ b/src/main/java/com/tnt/presentation/member/MemberController.java @@ -6,7 +6,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; @@ -63,8 +62,11 @@ public GetMemberInfoResponse getMemberInfo(@AuthMember Long memberId) { @PostMapping @ResponseStatus(OK) public UpdateMemberInfoResponse updateMemberInfo(@AuthMember Long memberId, - @RequestBody UpdateMemberInfoRequest request) { - return memberService.updateMemberInfo(memberId, request); + @RequestPart("request") @Valid UpdateMemberInfoRequest request, + @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { + String profileImageUrl = s3Service.uploadProfileImage(profileImage, request.memberType()); + + return memberService.updateMemberInfo(memberId, request, profileImageUrl); } @Operation(summary = "회원 탈퇴 API") diff --git a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java index 627815e3..3af4d0cc 100644 --- a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java +++ b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java @@ -47,6 +47,7 @@ import com.tnt.domain.trainee.Trainee; import com.tnt.domain.trainer.Trainer; import com.tnt.dto.member.request.SignUpRequest; +import com.tnt.dto.member.request.UpdateMemberInfoRequest; import com.tnt.fixture.MemberFixture; import com.tnt.fixture.PtGoalsFixture; import com.tnt.fixture.PtTrainerTraineeFixture; @@ -319,6 +320,126 @@ void get_member_info_trainee_success() throws Exception { .andDo(print()); } + @Test + @DisplayName("통합 테스트 - 트레이너 회원 정보 수정 성공") + void update_member_info_trainer_success() throws Exception { + // given + Member trainerMember = MemberFixture.getTrainerMember1(); + Member traineeMember1 = MemberFixture.getTraineeMember1(); + Member traineeMember2 = MemberFixture.getTraineeMember3(); + Member traineeMember3 = MemberFixture.getTraineeMember4(); + + trainerMember = memberRepository.save(trainerMember); + memberRepository.save(traineeMember1); + memberRepository.save(traineeMember2); + memberRepository.save(traineeMember3); + + CustomUserDetails traineeUserDetails = new CustomUserDetails(trainerMember.getId(), + String.valueOf(trainerMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); + + Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, + authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + Trainer trainer = TrainerFixture.getTrainer1(trainerMember); + Trainee trainee1 = TraineeFixture.getTrainee2(traineeMember1); + Trainee trainee2 = TraineeFixture.getTrainee2(traineeMember2); + Trainee trainee3 = TraineeFixture.getTrainee2(traineeMember3); + + trainerRepository.save(trainer); + traineeRepository.save(trainee1); + traineeRepository.save(trainee2); + traineeRepository.save(trainee3); + + List ptGoals = PtGoalsFixture.getPtGoals(trainee1.getId()); + + ptGoalRepository.saveAll(ptGoals); + + PtTrainerTrainee ptTrainerTrainee1 = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee1); + PtTrainerTrainee ptTrainerTrainee2 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee2); + PtTrainerTrainee ptTrainerTrainee3 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee3); + + ptTrainerTrainee3.softDelete(); + + ptTrainerTraineeRepository.save(ptTrainerTrainee1); + ptTrainerTraineeRepository.save(ptTrainerTrainee2); + ptTrainerTraineeRepository.save(ptTrainerTrainee3); + + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINER, "홍길동", null, null, null, null, null); + + // when + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + var result = mockMvc.perform(multipart("/members") + .file(jsonRequest) + .file(profileImage) + .contentType(MULTIPART_FORM_DATA_VALUE)); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.memberType").value(request.memberType().toString())) + .andExpect(jsonPath("$.name").value(request.name())) + .andExpect(jsonPath("$.profileImageUrl").value(trainerMember.getProfileImageUrl())) + .andDo(print()); + } + + @Test + @DisplayName("통합 테스트 - 트레이니 회원 정보 수정 성공") + void update_member_info_trainee_success() throws Exception { + // given + Member trainerMember = MemberFixture.getTrainerMember1(); + Member traineeMember = MemberFixture.getTraineeMember1(); + + memberRepository.save(trainerMember); + traineeMember = memberRepository.save(traineeMember); + + CustomUserDetails traineeUserDetails = new CustomUserDetails(traineeMember.getId(), + String.valueOf(traineeMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); + + Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, + authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + Trainer trainer = TrainerFixture.getTrainer1(trainerMember); + Trainee trainee = TraineeFixture.getTrainee2(traineeMember); + + trainerRepository.save(trainer); + traineeRepository.save(trainee); + + List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); + + ptGoalRepository.saveAll(ptGoals); + + PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); + + ptTrainerTraineeRepository.save(ptTrainerTrainee); + + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINEE, "홍길동", LocalDate.of(1990, 1, 1), 175.0, + 70.0, "테스트 주의사항", List.of("체중 감량", "건강 관리")); + + // when + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + var result = mockMvc.perform(multipart("/members") + .file(jsonRequest) + .file(profileImage) + .contentType(MULTIPART_FORM_DATA_VALUE)); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.memberType").value(request.memberType().toString())) + .andExpect(jsonPath("$.name").value(request.name())) + .andExpect(jsonPath("$.profileImageUrl").value(traineeMember.getProfileImageUrl())) + .andExpect(jsonPath("$.trainee.birthday").value(request.birthday().toString())) + .andExpect(jsonPath("$.trainee.height").value(request.height())) + .andExpect(jsonPath("$.trainee.weight").value(request.weight())) + .andExpect(jsonPath("$.trainee.cautionNote").value(request.cautionNote())) + .andExpect(jsonPath("$.trainee.ptGoals[0]").value(request.goalContents().getFirst())) + .andDo(print()); + } + @TestConfiguration static class TestConfig { From f7058c13e541c76b0d6d145738ddad1f60c7aea5 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Wed, 5 Mar 2025 18:34:18 +0900 Subject: [PATCH 03/13] =?UTF-8?q?[TNT-258]=20feat:=20else=20if=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tnt/application/member/MemberService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index 3b844c49..a7572c1a 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -102,9 +102,7 @@ public CheckSessionResponse getMemberType(Long memberId) { if (memberTypeDto.memberType() == TRAINER) { Trainer trainer = trainerService.getByMemberId(memberId); isConnected = ptService.isPtTrainerTraineeExistWithTrainerId(trainer.getId()); - } - - if (memberTypeDto.memberType() == TRAINEE) { + } else if (memberTypeDto.memberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); } From cb566a0e267874f70972ce3e24d072e33f482629 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Fri, 14 Mar 2025 18:08:07 +0900 Subject: [PATCH 04/13] =?UTF-8?q?[TNT-258]=20feat:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/application/member/MemberService.java | 56 ++++++------------- .../application/member/WithdrawService.java | 4 +- .../java/com/tnt/domain/trainee/PtGoal.java | 9 --- .../java/com/tnt/domain/trainee/Trainee.java | 2 +- ...oResponse.java => MemberInfoResponse.java} | 10 ++-- .../response/UpdateMemberInfoResponse.java | 46 --------------- .../presentation/member/MemberController.java | 9 ++- .../member/MemberControllerTest.java | 8 --- 8 files changed, 29 insertions(+), 115 deletions(-) rename src/main/java/com/tnt/dto/member/response/{GetMemberInfoResponse.java => MemberInfoResponse.java} (92%) delete mode 100644 src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index a7572c1a..f9569944 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -27,11 +27,9 @@ import com.tnt.domain.trainee.Trainee; import com.tnt.domain.trainer.Trainer; import com.tnt.dto.member.request.UpdateMemberInfoRequest; -import com.tnt.dto.member.response.GetMemberInfoResponse; -import com.tnt.dto.member.response.GetMemberInfoResponse.GetTraineeInfo; -import com.tnt.dto.member.response.GetMemberInfoResponse.GetTrainerInfo; -import com.tnt.dto.member.response.UpdateMemberInfoResponse; -import com.tnt.dto.member.response.UpdateMemberInfoResponse.UpdateTraineeInfo; +import com.tnt.dto.member.response.MemberInfoResponse; +import com.tnt.dto.member.response.MemberInfoResponse.TraineeInfo; +import com.tnt.dto.member.response.MemberInfoResponse.TrainerInfo; import com.tnt.gateway.dto.response.CheckSessionResponse; import com.tnt.infrastructure.mysql.repository.member.MemberRepository; import com.tnt.infrastructure.mysql.repository.member.MemberSearchRepository; @@ -53,9 +51,9 @@ public class MemberService { private final PtGoalRepository ptGoalRepository; @Transactional(readOnly = true) - public GetMemberInfoResponse getMemberInfo(Long memberId) { + public MemberInfoResponse getMemberInfo(Long memberId) { Member member = getByMemberId(memberId); - GetMemberInfoResponse memberInfo = null; + MemberInfoResponse memberInfo = null; if (member.getMemberType() == TRAINER) { Trainer trainer = trainerService.getByMemberId(memberId); @@ -68,10 +66,10 @@ public GetMemberInfoResponse getMemberInfo(Long memberId) { int totalTraineeCount = ptTrainerTrainees.size(); - GetTrainerInfo getTrainerInfo = new GetTrainerInfo(activeTraineeCount, totalTraineeCount); + TrainerInfo trainerInfo = new TrainerInfo(activeTraineeCount, totalTraineeCount); - memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), - member.getMemberType(), member.getSocialType(), getTrainerInfo, null); + memberInfo = new MemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), + member.getMemberType(), member.getSocialType(), trainerInfo, null); } if (member.getMemberType() == TRAINEE) { @@ -82,11 +80,11 @@ public GetMemberInfoResponse getMemberInfo(Long memberId) { .toList(); boolean isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); - GetTraineeInfo getTraineeInfo = new GetTraineeInfo(isConnected, member.getBirthday(), - member.getAge(), trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); + TraineeInfo traineeInfo = new TraineeInfo(isConnected, member.getBirthday(), member.getAge(), + trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); - memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), - member.getMemberType(), member.getSocialType(), null, getTraineeInfo); + memberInfo = new MemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), + member.getMemberType(), member.getSocialType(), null, traineeInfo); } return memberInfo; @@ -111,39 +109,20 @@ public CheckSessionResponse getMemberType(Long memberId) { } @Transactional - public UpdateMemberInfoResponse updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, + public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, String profileImageUrl) { Member findMember = getByMemberId(memberId); - UpdateMemberInfoResponse memberInfo = null; findMember.updateName(request.name()); findMember.updateProfileImageUrl(profileImageUrl); - // 트레이너 - if (findMember.getMemberType() == TRAINER) { - memberInfo = new UpdateMemberInfoResponse(findMember.getMemberType(), profileImageUrl, findMember.getName(), - null); - } - - // 트레이니 if (findMember.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); findMember.updateBirthday(request.birthday()); trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote()); - - List ptGoals = updatePtGoals(trainee, request.goalContents()).stream() - .map(PtGoal::getContent) - .toList(); - - UpdateTraineeInfo updateTraineeInfo = new UpdateTraineeInfo(findMember.getBirthday(), findMember.getAge(), - trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); - - memberInfo = new UpdateMemberInfoResponse(findMember.getMemberType(), profileImageUrl, findMember.getName(), - updateTraineeInfo); + updatePtGoals(trainee, request.goalContents()); } - - return memberInfo; } public void validateMemberNotExists(String socialId, SocialType socialType) { @@ -163,7 +142,7 @@ public Member getBySocialIdAndSocialType(String socialId, SocialType socialType) .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); } - private List updatePtGoals(Trainee trainee, List newGoalContents) { + private void updatePtGoals(Trainee trainee, List newGoalContents) { // 기존 PT 목표들 조회 List currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId()); @@ -196,12 +175,9 @@ private List updatePtGoals(Trainee trainee, List newGoalContents if (!newPtGoals.isEmpty()) { ptGoalRepository.saveAll(newPtGoals); + currentPtGoals.addAll(newPtGoals); } // 최종 목표 목록 리턴 (기존 유지된 목표 + 새로 추가된 목표) - List result = new ArrayList<>(currentPtGoals); - result.addAll(newPtGoals); - - return result; } } diff --git a/src/main/java/com/tnt/application/member/WithdrawService.java b/src/main/java/com/tnt/application/member/WithdrawService.java index 61fad953..9b6e3b93 100644 --- a/src/main/java/com/tnt/application/member/WithdrawService.java +++ b/src/main/java/com/tnt/application/member/WithdrawService.java @@ -23,6 +23,7 @@ import com.tnt.domain.trainer.Trainer; import com.tnt.dto.member.WithdrawDto; import com.tnt.gateway.service.SessionService; +import com.tnt.infrastructure.mysql.repository.pt.PtGoalRepository; import lombok.RequiredArgsConstructor; @@ -37,6 +38,7 @@ public class WithdrawService { private final PtGoalService ptGoalService; private final DietService dietService; private final PtService ptService; + private final PtGoalRepository ptGoalRepository; @Transactional public WithdrawDto withdraw(Long memberId) { @@ -85,7 +87,7 @@ private void deleteMemberData(Member member) { } } - ptGoals.forEach(PtGoal::softDelete); + ptGoalRepository.deleteAll(ptGoals); diets.forEach(Diet::softDelete); diff --git a/src/main/java/com/tnt/domain/trainee/PtGoal.java b/src/main/java/com/tnt/domain/trainee/PtGoal.java index e1269198..379b7dfc 100644 --- a/src/main/java/com/tnt/domain/trainee/PtGoal.java +++ b/src/main/java/com/tnt/domain/trainee/PtGoal.java @@ -4,8 +4,6 @@ import static io.micrometer.common.util.StringUtils.isBlank; import static java.util.Objects.requireNonNull; -import java.time.LocalDateTime; - import com.tnt.infrastructure.mysql.BaseTimeEntity; import jakarta.persistence.Column; @@ -38,9 +36,6 @@ public class PtGoal extends BaseTimeEntity { @Column(name = "content", nullable = false, length = CONTENT_LENGTH) private String content; - @Column(name = "deleted_at", nullable = true) - private LocalDateTime deletedAt; - @Builder public PtGoal(Long id, Long traineeId, String content) { this.id = id; @@ -48,10 +43,6 @@ public PtGoal(Long id, Long traineeId, String content) { this.content = validateContent(content); } - public void softDelete() { - this.deletedAt = LocalDateTime.now(); - } - private String validateContent(String content) { if (isBlank(content) || content.length() > CONTENT_LENGTH) { throw new IllegalArgumentException(PT_GOAL_INVALID_CONTENT.getMessage()); diff --git a/src/main/java/com/tnt/domain/trainee/Trainee.java b/src/main/java/com/tnt/domain/trainee/Trainee.java index 01bc4c26..0743529e 100644 --- a/src/main/java/com/tnt/domain/trainee/Trainee.java +++ b/src/main/java/com/tnt/domain/trainee/Trainee.java @@ -67,7 +67,7 @@ public Trainee(Long id, Member member, Double height, Double weight, @Nullable S public void updateTraineeInfo(Double height, Double weight, String cautionNote) { this.height = height; this.weight = weight; - this.cautionNote = cautionNote; + validateAndSetCautionNote(cautionNote); } public void softDelete() { diff --git a/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java b/src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java similarity index 92% rename from src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java rename to src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java index 54f87a3e..6ec6b17b 100644 --- a/src/main/java/com/tnt/dto/member/response/GetMemberInfoResponse.java +++ b/src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java @@ -9,7 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "회원 조회 API 응답") -public record GetMemberInfoResponse( +public record MemberInfoResponse( @Schema(description = "회원 이름", example = "홍길동", nullable = false) String name, @@ -26,13 +26,13 @@ public record GetMemberInfoResponse( SocialType socialType, @Schema(description = "트레이너 정보", nullable = true) - GetTrainerInfo trainer, + TrainerInfo trainer, @Schema(description = "트레이니 정보", nullable = true) - GetTraineeInfo trainee + TraineeInfo trainee ) { - public record GetTrainerInfo( + public record TrainerInfo( @Schema(description = "관리 중인 회원", example = "23", nullable = true) Integer activeTraineeCount, @@ -42,7 +42,7 @@ public record GetTrainerInfo( } - public record GetTraineeInfo( + public record TraineeInfo( @Schema(description = "트레이너 연결 여부", example = "true", nullable = false) Boolean isConnected, diff --git a/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java b/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java deleted file mode 100644 index 3b4a7458..00000000 --- a/src/main/java/com/tnt/dto/member/response/UpdateMemberInfoResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.tnt.dto.member.response; - -import java.time.LocalDate; -import java.util.List; - -import com.tnt.domain.member.MemberType; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "회원 정보 수정 API 응답") -public record UpdateMemberInfoResponse( - @Schema(description = "회원 타입", example = "TRAINER", nullable = false) - MemberType memberType, - - @Schema(description = "프로필 사진 URL", example = "https://images.tntapp.co.kr/profiles/trainers/basic_profile_trainer.svg", nullable = false) - String profileImageUrl, - - @Schema(description = "회원 이름", example = "홍길동", nullable = false) - String name, - - @Schema(description = "트레이니 정보", nullable = true) - UpdateTraineeInfo trainee -) { - - public record UpdateTraineeInfo( - @Schema(description = "생년월일", example = "2025-01-01", nullable = true) - LocalDate birthday, - - @Schema(description = "나이", example = "25", nullable = true) - Integer age, - - @Schema(description = "키 (cm)", example = "180.5", nullable = true) - Double height, - - @Schema(description = "몸무게 (kg)", example = "75.5", nullable = true) - Double weight, - - @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) - String cautionNote, - - @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) - List ptGoals - ) { - - } -} diff --git a/src/main/java/com/tnt/presentation/member/MemberController.java b/src/main/java/com/tnt/presentation/member/MemberController.java index 8387e057..9da56660 100644 --- a/src/main/java/com/tnt/presentation/member/MemberController.java +++ b/src/main/java/com/tnt/presentation/member/MemberController.java @@ -19,9 +19,8 @@ import com.tnt.dto.member.WithdrawDto; import com.tnt.dto.member.request.SignUpRequest; import com.tnt.dto.member.request.UpdateMemberInfoRequest; -import com.tnt.dto.member.response.GetMemberInfoResponse; +import com.tnt.dto.member.response.MemberInfoResponse; import com.tnt.dto.member.response.SignUpResponse; -import com.tnt.dto.member.response.UpdateMemberInfoResponse; import com.tnt.gateway.config.AuthMember; import io.swagger.v3.oas.annotations.Operation; @@ -54,19 +53,19 @@ public SignUpResponse signUp(@RequestPart("request") @Valid SignUpRequest reques @Operation(summary = "회원 조회 API") @GetMapping @ResponseStatus(OK) - public GetMemberInfoResponse getMemberInfo(@AuthMember Long memberId) { + public MemberInfoResponse getMemberInfo(@AuthMember Long memberId) { return memberService.getMemberInfo(memberId); } @Operation(summary = "회원 정보 수정 API") @PostMapping @ResponseStatus(OK) - public UpdateMemberInfoResponse updateMemberInfo(@AuthMember Long memberId, + public void updateMemberInfo(@AuthMember Long memberId, @RequestPart("request") @Valid UpdateMemberInfoRequest request, @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { String profileImageUrl = s3Service.uploadProfileImage(profileImage, request.memberType()); - return memberService.updateMemberInfo(memberId, request, profileImageUrl); + memberService.updateMemberInfo(memberId, request, profileImageUrl); } @Operation(summary = "회원 탈퇴 API") diff --git a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java index 3af4d0cc..1d848451 100644 --- a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java +++ b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java @@ -429,14 +429,6 @@ void update_member_info_trainee_success() throws Exception { // then result.andExpect(status().isOk()) - .andExpect(jsonPath("$.memberType").value(request.memberType().toString())) - .andExpect(jsonPath("$.name").value(request.name())) - .andExpect(jsonPath("$.profileImageUrl").value(traineeMember.getProfileImageUrl())) - .andExpect(jsonPath("$.trainee.birthday").value(request.birthday().toString())) - .andExpect(jsonPath("$.trainee.height").value(request.height())) - .andExpect(jsonPath("$.trainee.weight").value(request.weight())) - .andExpect(jsonPath("$.trainee.cautionNote").value(request.cautionNote())) - .andExpect(jsonPath("$.trainee.ptGoals[0]").value(request.goalContents().getFirst())) .andDo(print()); } From 16f02284e3f875c3a0141848a7baf27c22da02d2 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Fri, 21 Mar 2025 09:00:24 +0900 Subject: [PATCH 05/13] =?UTF-8?q?[TNT-258]=20feat:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/application/member/MemberService.java | 27 +++-- .../application/trainee/PtGoalService.java | 2 +- ...emberInfoResponse.java => MemberInfo.java} | 4 +- .../mysql/repository/pt/PtGoalRepository.java | 2 +- .../presentation/member/MemberController.java | 43 +++++-- .../member/WithdrawServiceTest.java | 4 + .../tnt/application/pt/PtGoalServiceTest.java | 4 +- .../member/MemberControllerTest.java | 107 +++++++++--------- 8 files changed, 112 insertions(+), 81 deletions(-) rename src/main/java/com/tnt/dto/member/{response/MemberInfoResponse.java => MemberInfo.java} (96%) diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index f9569944..b52b18ed 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -26,10 +26,10 @@ import com.tnt.domain.trainee.PtGoal; import com.tnt.domain.trainee.Trainee; import com.tnt.domain.trainer.Trainer; +import com.tnt.dto.member.MemberInfo; +import com.tnt.dto.member.MemberInfo.TraineeInfo; +import com.tnt.dto.member.MemberInfo.TrainerInfo; import com.tnt.dto.member.request.UpdateMemberInfoRequest; -import com.tnt.dto.member.response.MemberInfoResponse; -import com.tnt.dto.member.response.MemberInfoResponse.TraineeInfo; -import com.tnt.dto.member.response.MemberInfoResponse.TrainerInfo; import com.tnt.gateway.dto.response.CheckSessionResponse; import com.tnt.infrastructure.mysql.repository.member.MemberRepository; import com.tnt.infrastructure.mysql.repository.member.MemberSearchRepository; @@ -51,9 +51,9 @@ public class MemberService { private final PtGoalRepository ptGoalRepository; @Transactional(readOnly = true) - public MemberInfoResponse getMemberInfo(Long memberId) { + public MemberInfo getMemberInfo(Long memberId) { Member member = getByMemberId(memberId); - MemberInfoResponse memberInfo = null; + MemberInfo memberInfo = null; if (member.getMemberType() == TRAINER) { Trainer trainer = trainerService.getByMemberId(memberId); @@ -68,7 +68,7 @@ public MemberInfoResponse getMemberInfo(Long memberId) { TrainerInfo trainerInfo = new TrainerInfo(activeTraineeCount, totalTraineeCount); - memberInfo = new MemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), + memberInfo = new MemberInfo(member.getName(), member.getEmail(), member.getProfileImageUrl(), member.getMemberType(), member.getSocialType(), trainerInfo, null); } @@ -83,7 +83,7 @@ public MemberInfoResponse getMemberInfo(Long memberId) { TraineeInfo traineeInfo = new TraineeInfo(isConnected, member.getBirthday(), member.getAge(), trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); - memberInfo = new MemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(), + memberInfo = new MemberInfo(member.getName(), member.getEmail(), member.getProfileImageUrl(), member.getMemberType(), member.getSocialType(), null, traineeInfo); } @@ -109,12 +109,17 @@ public CheckSessionResponse getMemberType(Long memberId) { } @Transactional - public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, - String profileImageUrl) { + public void updateMemberProfileImage(Long memberId, String profileImageUrl) { Member findMember = getByMemberId(memberId); - findMember.updateName(request.name()); findMember.updateProfileImageUrl(profileImageUrl); + } + + @Transactional + public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { + Member findMember = getByMemberId(memberId); + + findMember.updateName(request.name()); if (findMember.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); @@ -177,7 +182,5 @@ private void updatePtGoals(Trainee trainee, List newGoalContents) { ptGoalRepository.saveAll(newPtGoals); currentPtGoals.addAll(newPtGoals); } - - // 최종 목표 목록 리턴 (기존 유지된 목표 + 새로 추가된 목표) } } diff --git a/src/main/java/com/tnt/application/trainee/PtGoalService.java b/src/main/java/com/tnt/application/trainee/PtGoalService.java index 9faaf84a..13c9c697 100644 --- a/src/main/java/com/tnt/application/trainee/PtGoalService.java +++ b/src/main/java/com/tnt/application/trainee/PtGoalService.java @@ -16,6 +16,6 @@ public class PtGoalService { private final PtGoalRepository ptGoalRepository; public List getAllByTraineeId(Long traineeId) { - return ptGoalRepository.findAllByTraineeIdAndDeletedAtIsNull(traineeId); + return ptGoalRepository.findAllByTraineeId(traineeId); } } diff --git a/src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java b/src/main/java/com/tnt/dto/member/MemberInfo.java similarity index 96% rename from src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java rename to src/main/java/com/tnt/dto/member/MemberInfo.java index 6ec6b17b..eb3d80ca 100644 --- a/src/main/java/com/tnt/dto/member/response/MemberInfoResponse.java +++ b/src/main/java/com/tnt/dto/member/MemberInfo.java @@ -1,4 +1,4 @@ -package com.tnt.dto.member.response; +package com.tnt.dto.member; import java.time.LocalDate; import java.util.List; @@ -9,7 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "회원 조회 API 응답") -public record MemberInfoResponse( +public record MemberInfo( @Schema(description = "회원 이름", example = "홍길동", nullable = false) String name, diff --git a/src/main/java/com/tnt/infrastructure/mysql/repository/pt/PtGoalRepository.java b/src/main/java/com/tnt/infrastructure/mysql/repository/pt/PtGoalRepository.java index eb0973a0..89a90b6e 100644 --- a/src/main/java/com/tnt/infrastructure/mysql/repository/pt/PtGoalRepository.java +++ b/src/main/java/com/tnt/infrastructure/mysql/repository/pt/PtGoalRepository.java @@ -8,5 +8,5 @@ public interface PtGoalRepository extends JpaRepository { - List findAllByTraineeIdAndDeletedAtIsNull(Long traineeId); + List findAllByTraineeId(Long traineeId); } diff --git a/src/main/java/com/tnt/presentation/member/MemberController.java b/src/main/java/com/tnt/presentation/member/MemberController.java index 9da56660..839214c1 100644 --- a/src/main/java/com/tnt/presentation/member/MemberController.java +++ b/src/main/java/com/tnt/presentation/member/MemberController.java @@ -1,11 +1,17 @@ package com.tnt.presentation.member; +import static com.tnt.common.constant.ImageConstant.TRAINEE_DEFAULT_IMAGE; +import static com.tnt.common.constant.ImageConstant.TRAINER_DEFAULT_IMAGE; +import static com.tnt.domain.member.MemberType.TRAINEE; +import static com.tnt.domain.member.MemberType.TRAINER; +import static java.util.Objects.isNull; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; @@ -16,10 +22,10 @@ import com.tnt.application.member.SignUpService; import com.tnt.application.member.WithdrawService; import com.tnt.application.s3.S3Service; +import com.tnt.dto.member.MemberInfo; import com.tnt.dto.member.WithdrawDto; import com.tnt.dto.member.request.SignUpRequest; import com.tnt.dto.member.request.UpdateMemberInfoRequest; -import com.tnt.dto.member.response.MemberInfoResponse; import com.tnt.dto.member.response.SignUpResponse; import com.tnt.gateway.config.AuthMember; @@ -53,19 +59,40 @@ public SignUpResponse signUp(@RequestPart("request") @Valid SignUpRequest reques @Operation(summary = "회원 조회 API") @GetMapping @ResponseStatus(OK) - public MemberInfoResponse getMemberInfo(@AuthMember Long memberId) { + public MemberInfo getMemberInfo(@AuthMember Long memberId) { return memberService.getMemberInfo(memberId); } - @Operation(summary = "회원 정보 수정 API") - @PostMapping + @Operation(summary = "프로필 사진 수정 API") + @PostMapping(value = "/update-profile", consumes = MULTIPART_FORM_DATA_VALUE) @ResponseStatus(OK) - public void updateMemberInfo(@AuthMember Long memberId, - @RequestPart("request") @Valid UpdateMemberInfoRequest request, + public void updateMemberProfileImage(@AuthMember Long memberId, @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { - String profileImageUrl = s3Service.uploadProfileImage(profileImage, request.memberType()); + MemberInfo memberInfo = memberService.getMemberInfo(memberId); + String currentProfileImageUrl = memberInfo.profileImageUrl(); + + s3Service.deleteProfileImage(currentProfileImageUrl); + + if (isNull(profileImage)) { + if (memberInfo.memberType() == TRAINER) { + currentProfileImageUrl = TRAINER_DEFAULT_IMAGE; + } + + if (memberInfo.memberType() == TRAINEE) { + currentProfileImageUrl = TRAINEE_DEFAULT_IMAGE; + } + } else { + currentProfileImageUrl = s3Service.uploadProfileImage(profileImage, memberInfo.memberType()); + } - memberService.updateMemberInfo(memberId, request, profileImageUrl); + memberService.updateMemberProfileImage(memberId, currentProfileImageUrl); + } + + @Operation(summary = "회원 정보 수정 API") + @PostMapping + @ResponseStatus(OK) + public void updateMemberInfo(@AuthMember Long memberId, @RequestBody @Valid UpdateMemberInfoRequest request) { + memberService.updateMemberInfo(memberId, request); } @Operation(summary = "회원 탈퇴 API") diff --git a/src/test/java/com/tnt/application/member/WithdrawServiceTest.java b/src/test/java/com/tnt/application/member/WithdrawServiceTest.java index d5edc147..0f9e24fe 100644 --- a/src/test/java/com/tnt/application/member/WithdrawServiceTest.java +++ b/src/test/java/com/tnt/application/member/WithdrawServiceTest.java @@ -32,6 +32,7 @@ import com.tnt.fixture.TraineeFixture; import com.tnt.fixture.TrainerFixture; import com.tnt.gateway.service.SessionService; +import com.tnt.infrastructure.mysql.repository.pt.PtGoalRepository; @ExtendWith(MockitoExtension.class) class WithdrawServiceTest { @@ -57,6 +58,9 @@ class WithdrawServiceTest { @Mock private PtService ptService; + @Mock + private PtGoalRepository ptGoalRepository; + @InjectMocks private WithdrawService withdrawService; diff --git a/src/test/java/com/tnt/application/pt/PtGoalServiceTest.java b/src/test/java/com/tnt/application/pt/PtGoalServiceTest.java index a90f423f..db79bd9f 100644 --- a/src/test/java/com/tnt/application/pt/PtGoalServiceTest.java +++ b/src/test/java/com/tnt/application/pt/PtGoalServiceTest.java @@ -42,7 +42,7 @@ void get_all_pt_goals_with_trainee_id_success() { .build() ); - given(ptGoalRepository.findAllByTraineeIdAndDeletedAtIsNull(traineeId)) + given(ptGoalRepository.findAllByTraineeId(traineeId)) .willReturn(ptGoals); // when @@ -50,7 +50,7 @@ void get_all_pt_goals_with_trainee_id_success() { // then assertThat(result).isNotNull().hasSize(2).isEqualTo(ptGoals); - verify(ptGoalRepository).findAllByTraineeIdAndDeletedAtIsNull(traineeId); + verify(ptGoalRepository).findAllByTraineeId(traineeId); } @Test diff --git a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java index 1d848451..de426a2a 100644 --- a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java +++ b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java @@ -5,6 +5,7 @@ import static com.tnt.domain.member.MemberType.TRAINEE; import static com.tnt.domain.member.MemberType.TRAINER; import static com.tnt.domain.member.SocialType.KAKAO; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.IMAGE_JPEG_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; @@ -321,18 +322,12 @@ void get_member_info_trainee_success() throws Exception { } @Test - @DisplayName("통합 테스트 - 트레이너 회원 정보 수정 성공") - void update_member_info_trainer_success() throws Exception { + @DisplayName("통합 테스트 - 회원 프로필 사진 수정 성공") + void update_member_profile_image_success() throws Exception { // given Member trainerMember = MemberFixture.getTrainerMember1(); - Member traineeMember1 = MemberFixture.getTraineeMember1(); - Member traineeMember2 = MemberFixture.getTraineeMember3(); - Member traineeMember3 = MemberFixture.getTraineeMember4(); trainerMember = memberRepository.save(trainerMember); - memberRepository.save(traineeMember1); - memberRepository.save(traineeMember2); - memberRepository.save(traineeMember3); CustomUserDetails traineeUserDetails = new CustomUserDetails(trainerMember.getId(), String.valueOf(trainerMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); @@ -343,56 +338,61 @@ void update_member_info_trainer_success() throws Exception { SecurityContextHolder.getContext().setAuthentication(authentication); Trainer trainer = TrainerFixture.getTrainer1(trainerMember); - Trainee trainee1 = TraineeFixture.getTrainee2(traineeMember1); - Trainee trainee2 = TraineeFixture.getTrainee2(traineeMember2); - Trainee trainee3 = TraineeFixture.getTrainee2(traineeMember3); trainerRepository.save(trainer); - traineeRepository.save(trainee1); - traineeRepository.save(trainee2); - traineeRepository.save(trainee3); - List ptGoals = PtGoalsFixture.getPtGoals(trainee1.getId()); + // when & then + mockMvc.perform(multipart("/members/update-profile") + .file(profileImage) + .contentType(MULTIPART_FORM_DATA_VALUE)) + .andExpect(status().isOk()) + .andDo(print()); - ptGoalRepository.saveAll(ptGoals); + Member updateMember = memberRepository.findAll().getFirst(); + assertThat(updateMember).isNotNull(); + assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINER_DEFAULT_IMAGE); + } - PtTrainerTrainee ptTrainerTrainee1 = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee1); - PtTrainerTrainee ptTrainerTrainee2 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee2); - PtTrainerTrainee ptTrainerTrainee3 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee3); + @Test + @DisplayName("통합 테스트 - 트레이너 회원 정보 수정 성공") + void update_member_info_trainer_success() throws Exception { + // given + Member trainerMember = MemberFixture.getTrainerMember1(); - ptTrainerTrainee3.softDelete(); + trainerMember = memberRepository.save(trainerMember); - ptTrainerTraineeRepository.save(ptTrainerTrainee1); - ptTrainerTraineeRepository.save(ptTrainerTrainee2); - ptTrainerTraineeRepository.save(ptTrainerTrainee3); + CustomUserDetails traineeUserDetails = new CustomUserDetails(trainerMember.getId(), + String.valueOf(trainerMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); - UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINER, "홍길동", null, null, null, null, null); + Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, + authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); - // when - var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, - objectMapper.writeValueAsString(request).getBytes()); - var result = mockMvc.perform(multipart("/members") - .file(jsonRequest) - .file(profileImage) - .contentType(MULTIPART_FORM_DATA_VALUE)); + SecurityContextHolder.getContext().setAuthentication(authentication); - // then - result.andExpect(status().isOk()) - .andExpect(jsonPath("$.memberType").value(request.memberType().toString())) - .andExpect(jsonPath("$.name").value(request.name())) - .andExpect(jsonPath("$.profileImageUrl").value(trainerMember.getProfileImageUrl())) + Trainer trainer = TrainerFixture.getTrainer1(trainerMember); + + trainerRepository.save(trainer); + + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINER, "홍길동", null, null, null, null, null); + + // when & then + mockMvc.perform(post("/members") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) .andDo(print()); + + Member updateMember = memberRepository.findAll().getFirst(); + assertThat(updateMember).isNotNull(); + assertThat(updateMember.getName()).isEqualTo(request.name()); } @Test @DisplayName("통합 테스트 - 트레이니 회원 정보 수정 성공") void update_member_info_trainee_success() throws Exception { // given - Member trainerMember = MemberFixture.getTrainerMember1(); Member traineeMember = MemberFixture.getTraineeMember1(); - memberRepository.save(trainerMember); - traineeMember = memberRepository.save(traineeMember); + memberRepository.save(traineeMember); CustomUserDetails traineeUserDetails = new CustomUserDetails(traineeMember.getId(), String.valueOf(traineeMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); @@ -402,34 +402,31 @@ void update_member_info_trainee_success() throws Exception { SecurityContextHolder.getContext().setAuthentication(authentication); - Trainer trainer = TrainerFixture.getTrainer1(trainerMember); Trainee trainee = TraineeFixture.getTrainee2(traineeMember); - trainerRepository.save(trainer); traineeRepository.save(trainee); List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); ptGoalRepository.saveAll(ptGoals); - PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); - - ptTrainerTraineeRepository.save(ptTrainerTrainee); - UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINEE, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "건강 관리")); - // when - var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, - objectMapper.writeValueAsString(request).getBytes()); - var result = mockMvc.perform(multipart("/members") - .file(jsonRequest) - .file(profileImage) - .contentType(MULTIPART_FORM_DATA_VALUE)); - - // then - result.andExpect(status().isOk()) + // when & then + mockMvc.perform(post("/members") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) .andDo(print()); + + Member updateMember = memberRepository.findAll().getFirst(); + Trainee updateTrainee = traineeRepository.findAll().getFirst(); + assertThat(updateMember).isNotNull(); + assertThat(updateMember.getName()).isEqualTo(request.name()); + assertThat(updateMember.getBirthday()).isEqualTo(request.birthday()); + assertThat(updateTrainee.getHeight()).isEqualTo(request.height()); + assertThat(updateTrainee.getWeight()).isEqualTo(request.weight()); + assertThat(updateTrainee.getCautionNote()).isEqualTo(request.cautionNote()); } @TestConfiguration From 0fca4fe37527c72d04829e78af66d8fd7553410c Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Fri, 21 Mar 2025 09:18:36 +0900 Subject: [PATCH 06/13] =?UTF-8?q?[TNT-258]=20feat:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=88=98=EC=A0=95=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=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 --- .../member/MemberControllerTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java index de426a2a..0bfb22c1 100644 --- a/src/test/java/com/tnt/presentation/member/MemberControllerTest.java +++ b/src/test/java/com/tnt/presentation/member/MemberControllerTest.java @@ -321,6 +321,72 @@ void get_member_info_trainee_success() throws Exception { .andDo(print()); } + @Test + @DisplayName("통합 테스트 - 트레이너 회원 기존 프로필 사진 삭제 성공") + void update_trainer_profile_image_success() throws Exception { + // given + Member trainerMember = MemberFixture.getTrainerMember1(); + + trainerMember = memberRepository.save(trainerMember); + + CustomUserDetails traineeUserDetails = new CustomUserDetails(trainerMember.getId(), + String.valueOf(trainerMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); + + Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, + authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + Trainer trainer = TrainerFixture.getTrainer1(trainerMember); + + trainerRepository.save(trainer); + + // when & then + mockMvc.perform(multipart("/members/update-profile") + .contentType(MULTIPART_FORM_DATA_VALUE)) + .andExpect(status().isOk()) + .andDo(print()); + + Member updateMember = memberRepository.findAll().getFirst(); + assertThat(updateMember).isNotNull(); + assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINER_DEFAULT_IMAGE); + } + + @Test + @DisplayName("통합 테스트 - 트레이니 회원 기존 프로필 사진 삭제 성공") + void update_trainee_profile_image_success() throws Exception { + // given + Member traineeMember = MemberFixture.getTraineeMember1(); + + memberRepository.save(traineeMember); + + CustomUserDetails traineeUserDetails = new CustomUserDetails(traineeMember.getId(), + String.valueOf(traineeMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); + + Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, + authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + Trainee trainee = TraineeFixture.getTrainee2(traineeMember); + + traineeRepository.save(trainee); + + List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); + + ptGoalRepository.saveAll(ptGoals); + + // when & then + mockMvc.perform(multipart("/members/update-profile") + .contentType(MULTIPART_FORM_DATA_VALUE)) + .andExpect(status().isOk()) + .andDo(print()); + + Member updateMember = memberRepository.findAll().getFirst(); + assertThat(updateMember).isNotNull(); + assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINEE_DEFAULT_IMAGE); + } + @Test @DisplayName("통합 테스트 - 회원 프로필 사진 수정 성공") void update_member_profile_image_success() throws Exception { From 35128d3a7da90356f0f3653e6b1dfde8c9cc5292 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Thu, 27 Mar 2025 21:29:44 +0900 Subject: [PATCH 07/13] =?UTF-8?q?[TNT-258]=20feat:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20s3=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/tnt/application/s3/S3Service.java | 29 +++++++++++++++++++ .../presentation/member/MemberController.java | 22 +------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/tnt/application/s3/S3Service.java b/src/main/java/com/tnt/application/s3/S3Service.java index 69f9b71b..65c53e1a 100644 --- a/src/main/java/com/tnt/application/s3/S3Service.java +++ b/src/main/java/com/tnt/application/s3/S3Service.java @@ -8,6 +8,8 @@ import static com.tnt.common.error.model.ErrorMessage.IMAGE_NOT_FOUND; import static com.tnt.common.error.model.ErrorMessage.IMAGE_NOT_SUPPORT; import static com.tnt.common.error.model.ErrorMessage.UNSUPPORTED_MEMBER_TYPE; +import static com.tnt.domain.member.MemberType.TRAINEE; +import static com.tnt.domain.member.MemberType.TRAINER; import static java.util.Objects.isNull; import java.awt.image.BufferedImage; @@ -31,8 +33,10 @@ import com.drew.metadata.Metadata; import com.drew.metadata.MetadataException; import com.drew.metadata.exif.ExifIFD0Directory; +import com.tnt.application.member.MemberService; import com.tnt.common.error.exception.ImageException; import com.tnt.domain.member.MemberType; +import com.tnt.dto.member.MemberInfo; import com.tnt.infrastructure.s3.S3Adapter; import lombok.RequiredArgsConstructor; @@ -49,6 +53,7 @@ public class S3Service { private static final double IMAGE_QUALITY = 0.85; private final S3Adapter s3Adapter; + private final MemberService memberService; public String uploadProfileImage(@Nullable MultipartFile profileImage, MemberType memberType) { String defaultImage; @@ -90,6 +95,30 @@ public String uploadImage(String defaultImage, String folderPath, @Nullable Mult } } + public String updateProfileImage(Long memberId, @Nullable MultipartFile profileImage) { + MemberInfo memberInfo = memberService.getMemberInfo(memberId); + String currentProfileImageUrl = memberInfo.profileImageUrl(); + + if (!currentProfileImageUrl.equals(TRAINER_DEFAULT_IMAGE) && !currentProfileImageUrl.equals( + TRAINEE_DEFAULT_IMAGE)) { + deleteProfileImage(currentProfileImageUrl); + } + + if (isNull(profileImage)) { + if (memberInfo.memberType() == TRAINER) { + currentProfileImageUrl = TRAINER_DEFAULT_IMAGE; + } + + if (memberInfo.memberType() == TRAINEE) { + currentProfileImageUrl = TRAINEE_DEFAULT_IMAGE; + } + } else { + currentProfileImageUrl = uploadProfileImage(profileImage, memberInfo.memberType()); + } + + return currentProfileImageUrl; + } + public void deleteProfileImage(String imageUrl) { if (imageUrl.equals(TRAINER_DEFAULT_IMAGE) || imageUrl.equals(TRAINEE_DEFAULT_IMAGE)) { return; diff --git a/src/main/java/com/tnt/presentation/member/MemberController.java b/src/main/java/com/tnt/presentation/member/MemberController.java index 839214c1..9a5ce8ad 100644 --- a/src/main/java/com/tnt/presentation/member/MemberController.java +++ b/src/main/java/com/tnt/presentation/member/MemberController.java @@ -1,10 +1,5 @@ package com.tnt.presentation.member; -import static com.tnt.common.constant.ImageConstant.TRAINEE_DEFAULT_IMAGE; -import static com.tnt.common.constant.ImageConstant.TRAINER_DEFAULT_IMAGE; -import static com.tnt.domain.member.MemberType.TRAINEE; -import static com.tnt.domain.member.MemberType.TRAINER; -import static java.util.Objects.isNull; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; @@ -68,22 +63,7 @@ public MemberInfo getMemberInfo(@AuthMember Long memberId) { @ResponseStatus(OK) public void updateMemberProfileImage(@AuthMember Long memberId, @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { - MemberInfo memberInfo = memberService.getMemberInfo(memberId); - String currentProfileImageUrl = memberInfo.profileImageUrl(); - - s3Service.deleteProfileImage(currentProfileImageUrl); - - if (isNull(profileImage)) { - if (memberInfo.memberType() == TRAINER) { - currentProfileImageUrl = TRAINER_DEFAULT_IMAGE; - } - - if (memberInfo.memberType() == TRAINEE) { - currentProfileImageUrl = TRAINEE_DEFAULT_IMAGE; - } - } else { - currentProfileImageUrl = s3Service.uploadProfileImage(profileImage, memberInfo.memberType()); - } + String currentProfileImageUrl = s3Service.updateProfileImage(memberId, profileImage); memberService.updateMemberProfileImage(memberId, currentProfileImageUrl); } From 5bc49b4d3280f72308911f53a95751e9dcf0fd91 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sun, 30 Mar 2025 07:31:26 +0900 Subject: [PATCH 08/13] =?UTF-8?q?[TNT-258]=20feat:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20member=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/tnt/application/member/MemberService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/tnt/application/member/MemberService.java b/src/main/java/com/tnt/application/member/MemberService.java index b52b18ed..28c8eb6e 100644 --- a/src/main/java/com/tnt/application/member/MemberService.java +++ b/src/main/java/com/tnt/application/member/MemberService.java @@ -110,21 +110,21 @@ public CheckSessionResponse getMemberType(Long memberId) { @Transactional public void updateMemberProfileImage(Long memberId, String profileImageUrl) { - Member findMember = getByMemberId(memberId); + Member member = getByMemberId(memberId); - findMember.updateProfileImageUrl(profileImageUrl); + member.updateProfileImageUrl(profileImageUrl); } @Transactional public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { - Member findMember = getByMemberId(memberId); + Member member = getByMemberId(memberId); - findMember.updateName(request.name()); + member.updateName(request.name()); - if (findMember.getMemberType() == TRAINEE) { + if (member.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); - findMember.updateBirthday(request.birthday()); + member.updateBirthday(request.birthday()); trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote()); updatePtGoals(trainee, request.goalContents()); } From 432d29169925e4a92181a0cbaecf70f805b020e1 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Tue, 6 May 2025 13:56:37 +0900 Subject: [PATCH 09/13] =?UTF-8?q?[TNT-258]=20refactor:=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B8=B0=EB=B0=98=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=94=84=EB=A1=9C=ED=95=84/?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tnt/image/application/S3Service.java | 2 +- src/main/java/com/tnt/image/{ => infrastructure}/S3Adapter.java | 2 +- src/main/java/com/tnt/member/dto/MemberInfo.java | 2 +- src/main/resources/config | 2 +- src/test/java/com/tnt/image/S3AdapterTest.java | 1 + src/test/java/com/tnt/image/application/S3ServiceTest.java | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) rename src/main/java/com/tnt/image/{ => infrastructure}/S3Adapter.java (97%) diff --git a/src/main/java/com/tnt/image/application/S3Service.java b/src/main/java/com/tnt/image/application/S3Service.java index 57d27a00..b1599898 100644 --- a/src/main/java/com/tnt/image/application/S3Service.java +++ b/src/main/java/com/tnt/image/application/S3Service.java @@ -34,7 +34,7 @@ import com.drew.metadata.MetadataException; import com.drew.metadata.exif.ExifIFD0Directory; import com.tnt.common.error.exception.ImageException; -import com.tnt.image.S3Adapter; +import com.tnt.image.infrastructure.S3Adapter; import com.tnt.member.application.MemberService; import com.tnt.member.domain.MemberType; import com.tnt.member.dto.MemberInfo; diff --git a/src/main/java/com/tnt/image/S3Adapter.java b/src/main/java/com/tnt/image/infrastructure/S3Adapter.java similarity index 97% rename from src/main/java/com/tnt/image/S3Adapter.java rename to src/main/java/com/tnt/image/infrastructure/S3Adapter.java index 7081d21b..869efddb 100644 --- a/src/main/java/com/tnt/image/S3Adapter.java +++ b/src/main/java/com/tnt/image/infrastructure/S3Adapter.java @@ -1,4 +1,4 @@ -package com.tnt.image; +package com.tnt.image.infrastructure; import static com.tnt.common.error.model.ErrorMessage.S3_DELETE_ERROR; import static com.tnt.common.error.model.ErrorMessage.S3_UPLOAD_ERROR; diff --git a/src/main/java/com/tnt/member/dto/MemberInfo.java b/src/main/java/com/tnt/member/dto/MemberInfo.java index cef6257d..6b8f287c 100644 --- a/src/main/java/com/tnt/member/dto/MemberInfo.java +++ b/src/main/java/com/tnt/member/dto/MemberInfo.java @@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "회원 조회 API 응답") +@Schema(description = "회원 정보") public record MemberInfo( @Schema(description = "회원 이름", example = "홍길동", nullable = false) String name, diff --git a/src/main/resources/config b/src/main/resources/config index 0a95cb9e..095dc124 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 0a95cb9e45b97e15be886022cdc9b8267dfed2ca +Subproject commit 095dc1242730fd233ef34fae095d6125bfd8f3c8 diff --git a/src/test/java/com/tnt/image/S3AdapterTest.java b/src/test/java/com/tnt/image/S3AdapterTest.java index 23611296..f09d3cbf 100644 --- a/src/test/java/com/tnt/image/S3AdapterTest.java +++ b/src/test/java/com/tnt/image/S3AdapterTest.java @@ -16,6 +16,7 @@ import org.springframework.test.util.ReflectionTestUtils; import com.tnt.common.error.exception.ImageException; +import com.tnt.image.infrastructure.S3Adapter; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; diff --git a/src/test/java/com/tnt/image/application/S3ServiceTest.java b/src/test/java/com/tnt/image/application/S3ServiceTest.java index 16100138..e21b6bbb 100644 --- a/src/test/java/com/tnt/image/application/S3ServiceTest.java +++ b/src/test/java/com/tnt/image/application/S3ServiceTest.java @@ -33,7 +33,7 @@ import org.springframework.test.util.ReflectionTestUtils; import com.tnt.common.error.exception.ImageException; -import com.tnt.image.S3Adapter; +import com.tnt.image.infrastructure.S3Adapter; @ExtendWith(MockitoExtension.class) class S3ServiceTest { From 0fca7b69a38d00621b0a337823c232d075941b0b Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Wed, 7 May 2025 16:46:58 +0900 Subject: [PATCH 10/13] =?UTF-8?q?[TNT-258]=20refactor:=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84/=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/tnt/image/application/S3Service.java | 52 +++++----- .../tnt/member/application/MemberService.java | 54 +++++++++-- .../tnt/member/application/SignUpService.java | 2 + .../com/tnt/member/dto/UpdateProfile.java | 11 +++ .../dto/request/UpdateMemberInfoRequest.java | 3 + .../member/presentation/MemberController.java | 27 +++--- .../tnt/image/application/S3ServiceTest.java | 6 +- .../presentation/MemberControllerTest.java | 95 ++++++++++++------- 8 files changed, 167 insertions(+), 83 deletions(-) create mode 100644 src/main/java/com/tnt/member/dto/UpdateProfile.java diff --git a/src/main/java/com/tnt/image/application/S3Service.java b/src/main/java/com/tnt/image/application/S3Service.java index b1599898..bbc52b87 100644 --- a/src/main/java/com/tnt/image/application/S3Service.java +++ b/src/main/java/com/tnt/image/application/S3Service.java @@ -8,8 +8,6 @@ import static com.tnt.common.error.model.ErrorMessage.IMAGE_NOT_FOUND; import static com.tnt.common.error.model.ErrorMessage.IMAGE_NOT_SUPPORT; import static com.tnt.common.error.model.ErrorMessage.UNSUPPORTED_MEMBER_TYPE; -import static com.tnt.member.domain.MemberType.TRAINEE; -import static com.tnt.member.domain.MemberType.TRAINER; import static java.util.Objects.isNull; import java.awt.image.BufferedImage; @@ -35,9 +33,8 @@ import com.drew.metadata.exif.ExifIFD0Directory; import com.tnt.common.error.exception.ImageException; import com.tnt.image.infrastructure.S3Adapter; -import com.tnt.member.application.MemberService; import com.tnt.member.domain.MemberType; -import com.tnt.member.dto.MemberInfo; +import com.tnt.member.dto.UpdateProfile; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -53,7 +50,6 @@ public class S3Service { private static final double IMAGE_QUALITY = 0.85; private final S3Adapter s3Adapter; - private final MemberService memberService; public String uploadProfileImage(@Nullable MultipartFile profileImage, MemberType memberType) { String defaultImage; @@ -95,30 +91,6 @@ public String uploadImage(String defaultImage, String folderPath, @Nullable Mult } } - public String updateProfileImage(Long memberId, @Nullable MultipartFile profileImage) { - MemberInfo memberInfo = memberService.getMemberInfo(memberId); - String currentProfileImageUrl = memberInfo.profileImageUrl(); - - if (!currentProfileImageUrl.equals(TRAINER_DEFAULT_IMAGE) && !currentProfileImageUrl.equals( - TRAINEE_DEFAULT_IMAGE)) { - deleteProfileImage(currentProfileImageUrl); - } - - if (isNull(profileImage)) { - if (memberInfo.memberType() == TRAINER) { - currentProfileImageUrl = TRAINER_DEFAULT_IMAGE; - } - - if (memberInfo.memberType() == TRAINEE) { - currentProfileImageUrl = TRAINEE_DEFAULT_IMAGE; - } - } else { - currentProfileImageUrl = uploadProfileImage(profileImage, memberInfo.memberType()); - } - - return currentProfileImageUrl; - } - public void deleteProfileImage(String imageUrl) { if (imageUrl.equals(TRAINER_DEFAULT_IMAGE) || imageUrl.equals(TRAINEE_DEFAULT_IMAGE)) { return; @@ -208,4 +180,26 @@ private BufferedImage rotateImageIfRequired(BufferedImage image, MultipartFile m return image; } + + public String handleProfileImage(UpdateProfile profileUpdate, @Nullable MultipartFile profileImage, + MemberType memberType) { + // 새 이미지 없음 + if (isNull(profileImage)) { + // 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우 + if (profileUpdate.removeCurrentImage() && !profileUpdate.isCurrentImageDefault()) { + deleteProfileImage(profileUpdate.currentImageUrl()); + + return profileUpdate.changeImageUrl(); + } else { // 이미지 유지 요청 + return profileUpdate.currentImageUrl(); + } + } else { // 새 이미지 있음 + // 이미지 수정 요청 - 현재 이미지가 기본 이미지가 아닌 경우 + if (profileUpdate.removeCurrentImage() && !profileUpdate.isCurrentImageDefault()) { + deleteProfileImage(profileUpdate.currentImageUrl()); + } + + return uploadProfileImage(profileImage, memberType); + } + } } diff --git a/src/main/java/com/tnt/member/application/MemberService.java b/src/main/java/com/tnt/member/application/MemberService.java index d998963f..5d9a2e40 100644 --- a/src/main/java/com/tnt/member/application/MemberService.java +++ b/src/main/java/com/tnt/member/application/MemberService.java @@ -1,24 +1,32 @@ package com.tnt.member.application; +import static com.tnt.common.constant.ImageConstant.TRAINEE_DEFAULT_IMAGE; +import static com.tnt.common.constant.ImageConstant.TRAINER_DEFAULT_IMAGE; import static com.tnt.common.error.model.ErrorMessage.MEMBER_CONFLICT; import static com.tnt.member.domain.MemberType.TRAINEE; import static com.tnt.member.domain.MemberType.TRAINER; import static com.tnt.member.dto.MemberProjection.MemberTypeDto; +import static java.util.Objects.isNull; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import com.tnt.common.error.exception.ConflictException; import com.tnt.gateway.dto.response.CheckSessionResponse; +import com.tnt.image.application.S3Service; import com.tnt.member.application.repository.MemberRepository; import com.tnt.member.domain.Member; +import com.tnt.member.domain.MemberType; import com.tnt.member.domain.SocialType; import com.tnt.member.dto.MemberInfo; +import com.tnt.member.dto.UpdateProfile; import com.tnt.member.dto.request.UpdateMemberInfoRequest; import com.tnt.pt.application.PtService; import com.tnt.pt.domain.PtTrainerTrainee; @@ -41,6 +49,7 @@ public class MemberService { private final TraineeService traineeService; private final PtGoalService ptGoalService; private final PtService ptService; + private final S3Service s3Service; private final MemberRepository memberRepository; private final TraineeRepository traineeRepository; @@ -104,20 +113,50 @@ public CheckSessionResponse getMemberType(Long memberId) { } @Transactional - public void updateMemberProfileImage(Long memberId, String profileImageUrl) { + public UpdateProfile checkMemberProfileImage(Long memberId, boolean removeImage, + @Nullable MultipartFile profileImage) { Member member = getByMemberId(memberId); + String currentProfileImageUrl = member.getProfileImageUrl(); + String changeProfileImageUrl = ""; + boolean removeCurrentImage = true; + boolean isCurrentImageDefault = isDefaultImage(currentProfileImageUrl); + boolean changeImageIsDefault = false; + + // 새 이미지 없음 + if (isNull(profileImage)) { + // 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우 + if (removeImage && !isCurrentImageDefault) { + changeProfileImageUrl = getDefaultImageUrl(member.getMemberType()); + changeImageIsDefault = true; + } else if (!removeImage && isCurrentImageDefault) { // 이미지 유지 요청 - 현재 이미지가 기본 이미지인 경우 + changeProfileImageUrl = currentProfileImageUrl; + removeCurrentImage = false; + } else { // 이미지 유지 요청 - 현재 이미지가 기본 이미지가 아닌 경우 + removeCurrentImage = false; + } + } else { // 새 이미지 있음 + // 이미지 수정 요청 - 현재 이미지가 기본 이미지인 경우 + if (isCurrentImageDefault) { + removeCurrentImage = false; + } + } - member.updateProfileImageUrl(profileImageUrl); + return new UpdateProfile(currentProfileImageUrl, changeProfileImageUrl, removeCurrentImage, + isCurrentImageDefault, changeImageIsDefault); + } - memberRepository.save(member); + private boolean isDefaultImage(String imageUrl) { + return imageUrl.equals(TRAINER_DEFAULT_IMAGE) || imageUrl.equals(TRAINEE_DEFAULT_IMAGE); + } + + private String getDefaultImageUrl(MemberType memberType) { + return memberType == TRAINER ? TRAINER_DEFAULT_IMAGE : TRAINEE_DEFAULT_IMAGE; } @Transactional - public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { + public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, String profileImageUrl) { Member member = getByMemberId(memberId); - member.updateName(request.name()); - if (member.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); @@ -128,6 +167,9 @@ public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request) { traineeRepository.save(trainee); } + member.updateName(request.name()); + member.updateProfileImageUrl(profileImageUrl); + memberRepository.save(member); } diff --git a/src/main/java/com/tnt/member/application/SignUpService.java b/src/main/java/com/tnt/member/application/SignUpService.java index eed66660..57d5b06e 100644 --- a/src/main/java/com/tnt/member/application/SignUpService.java +++ b/src/main/java/com/tnt/member/application/SignUpService.java @@ -58,6 +58,8 @@ public SignUpResponse finishSignUpAfterImageUpload(String profileImageUrl, Long sessionService.createSession(sessionId, String.valueOf(member.getId())); + memberRepository.save(member); + return new SignUpResponse(memberType, sessionId, member.getName(), member.getProfileImageUrl()); } diff --git a/src/main/java/com/tnt/member/dto/UpdateProfile.java b/src/main/java/com/tnt/member/dto/UpdateProfile.java new file mode 100644 index 00000000..673e5d94 --- /dev/null +++ b/src/main/java/com/tnt/member/dto/UpdateProfile.java @@ -0,0 +1,11 @@ +package com.tnt.member.dto; + +public record UpdateProfile( + String currentImageUrl, + String changeImageUrl, + boolean removeCurrentImage, + boolean isCurrentImageDefault, + boolean changeImageIsDefault +) { + +} diff --git a/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java b/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java index f6515e25..ee0f2a65 100644 --- a/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java +++ b/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java @@ -13,6 +13,9 @@ @Schema(description = "회원 정보 수정 API 요청") public record UpdateMemberInfoRequest( + @Schema(description = "기존 프로필 사진 삭제 여부", example = "true", nullable = false) + Boolean removeImage, + @Schema(description = "회원 타입", example = "TRAINER", nullable = false) @NotNull(message = "회원 타입은 필수입니다.") MemberType memberType, diff --git a/src/main/java/com/tnt/member/presentation/MemberController.java b/src/main/java/com/tnt/member/presentation/MemberController.java index 10b54223..b7ca4b2f 100644 --- a/src/main/java/com/tnt/member/presentation/MemberController.java +++ b/src/main/java/com/tnt/member/presentation/MemberController.java @@ -4,9 +4,10 @@ import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import org.springframework.lang.Nullable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; @@ -19,6 +20,7 @@ import com.tnt.member.application.SignUpService; import com.tnt.member.application.WithdrawService; import com.tnt.member.dto.MemberInfo; +import com.tnt.member.dto.UpdateProfile; import com.tnt.member.dto.WithdrawDto; import com.tnt.member.dto.request.SignUpRequest; import com.tnt.member.dto.request.UpdateMemberInfoRequest; @@ -44,7 +46,7 @@ public class MemberController { @PostMapping(value = "/sign-up", consumes = MULTIPART_FORM_DATA_VALUE) @ResponseStatus(CREATED) public SignUpResponse signUp(@RequestPart("request") @Valid SignUpRequest request, - @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { + @RequestPart(value = "profileImage", required = false) @Nullable MultipartFile profileImage) { Long memberId = signUpService.signUp(request); String profileImageUrl = s3Service.uploadProfileImage(profileImage, request.memberType()); @@ -58,21 +60,18 @@ public MemberInfo getMemberInfo(@AuthMember Long memberId) { return memberService.getMemberInfo(memberId); } - @Operation(summary = "프로필 사진 수정 API") - @PostMapping(value = "/update-profile", consumes = MULTIPART_FORM_DATA_VALUE) - @ResponseStatus(OK) - public void updateMemberProfileImage(@AuthMember Long memberId, - @RequestPart(value = "profileImage", required = false) MultipartFile profileImage) { - String currentProfileImageUrl = s3Service.updateProfileImage(memberId, profileImage); - - memberService.updateMemberProfileImage(memberId, currentProfileImageUrl); - } - @Operation(summary = "회원 정보 수정 API") @PostMapping @ResponseStatus(OK) - public void updateMemberInfo(@AuthMember Long memberId, @RequestBody @Valid UpdateMemberInfoRequest request) { - memberService.updateMemberInfo(memberId, request); + public void updateMemberInfo(@AuthMember Long memberId, + @RequestPart("request") @Valid UpdateMemberInfoRequest request, + @RequestPart(value = "profileImage", required = false) @Nullable MultipartFile profileImage) { + UpdateProfile profileUpdate = memberService.checkMemberProfileImage(memberId, request.removeImage(), + profileImage); + + String profileImageUrl = s3Service.handleProfileImage(profileUpdate, profileImage, request.memberType()); + + memberService.updateMemberInfo(memberId, request, profileImageUrl); } @Operation(summary = "회원 탈퇴 API") diff --git a/src/test/java/com/tnt/image/application/S3ServiceTest.java b/src/test/java/com/tnt/image/application/S3ServiceTest.java index e21b6bbb..f453e169 100644 --- a/src/test/java/com/tnt/image/application/S3ServiceTest.java +++ b/src/test/java/com/tnt/image/application/S3ServiceTest.java @@ -123,7 +123,8 @@ void rotate_image_orientation_3_success() throws IOException { MockMultipartFile image = new MockMultipartFile("image", "test.jpg", IMAGE_JPEG_VALUE, createDummyImageData(3)); // when - BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", originalImage, + BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", + originalImage, image); // then @@ -139,7 +140,8 @@ void rotate_image_orientation_6_success() throws IOException { MockMultipartFile image = new MockMultipartFile("image", "test.jpg", IMAGE_JPEG_VALUE, createDummyImageData(6)); // when - BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", originalImage, + BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", + originalImage, image); // then diff --git a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java index e8b18e81..3d5f19fb 100644 --- a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java +++ b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java @@ -6,6 +6,7 @@ import static com.tnt.member.domain.MemberType.TRAINER; import static com.tnt.member.domain.SocialType.KAKAO; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpMethod.PUT; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.IMAGE_JPEG_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; @@ -324,8 +325,8 @@ void get_member_info_trainee_success() throws Exception { } @Test - @DisplayName("통합 테스트 - 트레이너 회원 기존 프로필 사진 삭제 성공") - void update_trainer_profile_image_success() throws Exception { + @DisplayName("통합 테스트 - 회원 프로필 사진 수정 성공") + void update_member_profile_success() throws Exception { // given Member trainerMember = MemberFixture.getTrainerMember1(); @@ -343,55 +344,68 @@ void update_trainer_profile_image_success() throws Exception { trainerRepository.save(trainer); + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINER, "홍길동", null, null, null, null, + null); + // when & then - mockMvc.perform(multipart("/members/update-profile") + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + + mockMvc.perform(multipart(PUT, "/members/change") + .file(jsonRequest) + .file(profileImage) .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) .andDo(print()); - Member updateMember = memberRepository.findAll().getFirst(); + Member updateMember = memberRepository.findById(trainerMember.getId()); assertThat(updateMember).isNotNull(); + assertThat(updateMember.getName()).isEqualTo(request.name()); assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINER_DEFAULT_IMAGE); } @Test - @DisplayName("통합 테스트 - 트레이니 회원 기존 프로필 사진 삭제 성공") - void update_trainee_profile_image_success() throws Exception { + @DisplayName("통합 테스트 - 회원 프로필 사진 유지 성공") + void maintain_member_profile_success() throws Exception { // given - Member traineeMember = MemberFixture.getTraineeMember1(); + Member trainerMember = MemberFixture.getTrainerMember1(); - traineeMember = memberRepository.save(traineeMember); + trainerMember = memberRepository.save(trainerMember); - CustomUserDetails traineeUserDetails = new CustomUserDetails(traineeMember.getId(), - String.valueOf(traineeMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); + CustomUserDetails traineeUserDetails = new CustomUserDetails(trainerMember.getId(), + String.valueOf(trainerMember.getId()), List.of(new SimpleGrantedAuthority("ROLE_USER"))); Authentication authentication = new UsernamePasswordAuthenticationToken(traineeUserDetails, null, authoritiesMapper.mapAuthorities(traineeUserDetails.getAuthorities())); SecurityContextHolder.getContext().setAuthentication(authentication); - Trainee trainee = TraineeFixture.getTrainee2(traineeMember); - - trainee = traineeRepository.save(trainee); + Trainer trainer = TrainerFixture.getTrainer1(trainerMember); - List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); + trainerRepository.save(trainer); - ptGoalRepository.saveAll(ptGoals); + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(false, TRAINER, "홍길동", null, null, null, null, + null); // when & then - mockMvc.perform(multipart("/members/update-profile") + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + + mockMvc.perform(multipart(PUT, "/members/change") + .file(jsonRequest) .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) .andDo(print()); - Member updateMember = memberRepository.findAll().getFirst(); + Member updateMember = memberRepository.findById(trainerMember.getId()); assertThat(updateMember).isNotNull(); - assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINEE_DEFAULT_IMAGE); + assertThat(updateMember.getName()).isEqualTo(request.name()); + assertThat(updateMember.getProfileImageUrl()).isEqualTo(trainerMember.getProfileImageUrl()); } @Test - @DisplayName("통합 테스트 - 회원 프로필 사진 수정 성공") - void update_member_profile_image_success() throws Exception { + @DisplayName("통합 테스트 - 회원 프로필 사진 삭제 성공") + void delete_member_profile_success() throws Exception { // given Member trainerMember = MemberFixture.getTrainerMember1(); @@ -409,15 +423,22 @@ void update_member_profile_image_success() throws Exception { trainerRepository.save(trainer); + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINER, "홍길동", null, null, null, null, + null); + // when & then - mockMvc.perform(multipart("/members/update-profile") - .file(profileImage) + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + + mockMvc.perform(multipart(PUT, "/members/change") + .file(jsonRequest) .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) .andDo(print()); - Member updateMember = memberRepository.findAll().getFirst(); + Member updateMember = memberRepository.findById(trainerMember.getId()); assertThat(updateMember).isNotNull(); + assertThat(updateMember.getName()).isEqualTo(request.name()); assertThat(updateMember.getProfileImageUrl()).isEqualTo(TRAINER_DEFAULT_IMAGE); } @@ -441,12 +462,18 @@ void update_member_info_trainer_success() throws Exception { trainerRepository.save(trainer); - UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINER, "홍길동", null, null, null, null, null); + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINER, "홍길동", null, null, null, null, + null); // when & then - mockMvc.perform(post("/members") - .contentType("application/json") - .content(objectMapper.writeValueAsString(request))) + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + + mockMvc.perform(multipart(PUT, "/members/change") + .file(jsonRequest) + .file(profileImage) + .contentType(MULTIPART_FORM_DATA_VALUE)) + .andExpect(status().isOk()) .andDo(print()); Member updateMember = memberRepository.findAll().getFirst(); @@ -478,13 +505,17 @@ void update_member_info_trainee_success() throws Exception { ptGoalRepository.saveAll(ptGoals); - UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(TRAINEE, "홍길동", LocalDate.of(1990, 1, 1), 175.0, - 70.0, "테스트 주의사항", List.of("체중 감량", "건강 관리")); + UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINEE, "홍길동", LocalDate.of(1990, 1, 1), + 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "건강 관리")); // when & then - mockMvc.perform(post("/members") - .contentType("application/json") - .content(objectMapper.writeValueAsString(request))) + var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(request).getBytes()); + + mockMvc.perform(multipart(PUT, "/members/change") + .file(jsonRequest) + .file(profileImage) + .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) .andDo(print()); From 96c3e28892cf1fc7ee751fe5c0a59ec1408597f7 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Wed, 7 May 2025 16:47:34 +0900 Subject: [PATCH 11/13] =?UTF-8?q?[TNT-258]=20refactor:=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84/=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/tnt/member/presentation/MemberController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tnt/member/presentation/MemberController.java b/src/main/java/com/tnt/member/presentation/MemberController.java index b7ca4b2f..42d53315 100644 --- a/src/main/java/com/tnt/member/presentation/MemberController.java +++ b/src/main/java/com/tnt/member/presentation/MemberController.java @@ -61,7 +61,7 @@ public MemberInfo getMemberInfo(@AuthMember Long memberId) { } @Operation(summary = "회원 정보 수정 API") - @PostMapping + @PutMapping(value = "/change", consumes = MULTIPART_FORM_DATA_VALUE) @ResponseStatus(OK) public void updateMemberInfo(@AuthMember Long memberId, @RequestPart("request") @Valid UpdateMemberInfoRequest request, From 2a668ca455f9cb627e636cb7dce275b220a947a8 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Thu, 29 May 2025 22:45:19 +0900 Subject: [PATCH 12/13] =?UTF-8?q?[TNT-258]feat:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/tnt/image/application/S3Service.java | 4 +- .../tnt/member/application/MemberService.java | 59 ++++++++----------- .../member/application/WithdrawService.java | 5 +- ...{UpdateProfile.java => ProfileUpdate.java} | 5 +- .../MemberInfoResponse.java} | 4 +- .../member/presentation/MemberController.java | 10 ++-- .../com/tnt/pt/application/PtService.java | 2 + .../tnt/image/application/S3ServiceTest.java | 6 +- .../presentation/MemberControllerTest.java | 10 ++-- 9 files changed, 49 insertions(+), 56 deletions(-) rename src/main/java/com/tnt/member/dto/{UpdateProfile.java => ProfileUpdate.java} (55%) rename src/main/java/com/tnt/member/dto/{MemberInfo.java => response/MemberInfoResponse.java} (96%) diff --git a/src/main/java/com/tnt/image/application/S3Service.java b/src/main/java/com/tnt/image/application/S3Service.java index bbc52b87..cdd282db 100644 --- a/src/main/java/com/tnt/image/application/S3Service.java +++ b/src/main/java/com/tnt/image/application/S3Service.java @@ -34,7 +34,7 @@ import com.tnt.common.error.exception.ImageException; import com.tnt.image.infrastructure.S3Adapter; import com.tnt.member.domain.MemberType; -import com.tnt.member.dto.UpdateProfile; +import com.tnt.member.dto.ProfileUpdate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -181,7 +181,7 @@ private BufferedImage rotateImageIfRequired(BufferedImage image, MultipartFile m return image; } - public String handleProfileImage(UpdateProfile profileUpdate, @Nullable MultipartFile profileImage, + public String handleProfileImage(ProfileUpdate profileUpdate, @Nullable MultipartFile profileImage, MemberType memberType) { // 새 이미지 없음 if (isNull(profileImage)) { diff --git a/src/main/java/com/tnt/member/application/MemberService.java b/src/main/java/com/tnt/member/application/MemberService.java index 5d9a2e40..72aaa450 100644 --- a/src/main/java/com/tnt/member/application/MemberService.java +++ b/src/main/java/com/tnt/member/application/MemberService.java @@ -9,6 +9,7 @@ import static java.util.Objects.isNull; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -20,14 +21,12 @@ import com.tnt.common.error.exception.ConflictException; import com.tnt.gateway.dto.response.CheckSessionResponse; -import com.tnt.image.application.S3Service; import com.tnt.member.application.repository.MemberRepository; import com.tnt.member.domain.Member; -import com.tnt.member.domain.MemberType; import com.tnt.member.domain.SocialType; -import com.tnt.member.dto.MemberInfo; -import com.tnt.member.dto.UpdateProfile; +import com.tnt.member.dto.ProfileUpdate; import com.tnt.member.dto.request.UpdateMemberInfoRequest; +import com.tnt.member.dto.response.MemberInfoResponse; import com.tnt.pt.application.PtService; import com.tnt.pt.domain.PtTrainerTrainee; import com.tnt.trainee.application.PtGoalService; @@ -49,16 +48,15 @@ public class MemberService { private final TraineeService traineeService; private final PtGoalService ptGoalService; private final PtService ptService; - private final S3Service s3Service; private final MemberRepository memberRepository; private final TraineeRepository traineeRepository; private final PtGoalRepository ptGoalRepository; @Transactional(readOnly = true) - public MemberInfo getMemberInfo(Long memberId) { + public MemberInfoResponse getMemberInfo(Long memberId) { Member member = getByMemberId(memberId); - MemberInfo memberInfo = null; + MemberInfoResponse memberInfoResponse = null; if (member.getMemberType() == TRAINER) { Trainer trainer = trainerService.getByMemberId(memberId); @@ -71,9 +69,11 @@ public MemberInfo getMemberInfo(Long memberId) { int totalTraineeCount = ptTrainerTrainees.size(); - MemberInfo.TrainerInfo trainerInfo = new MemberInfo.TrainerInfo(activeTraineeCount, totalTraineeCount); + MemberInfoResponse.TrainerInfo trainerInfo = new MemberInfoResponse.TrainerInfo(activeTraineeCount, + totalTraineeCount); - memberInfo = new MemberInfo(member.getName(), member.getEmail(), member.getProfileImageUrl(), + memberInfoResponse = new MemberInfoResponse(member.getName(), member.getEmail(), + member.getProfileImageUrl(), member.getMemberType(), member.getSocialType(), trainerInfo, null); } @@ -85,15 +85,17 @@ public MemberInfo getMemberInfo(Long memberId) { .toList(); boolean isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); - MemberInfo.TraineeInfo traineeInfo = new MemberInfo.TraineeInfo(isConnected, member.getBirthday(), + MemberInfoResponse.TraineeInfo traineeInfo = new MemberInfoResponse.TraineeInfo(isConnected, + member.getBirthday(), member.getAge(), trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals); - memberInfo = new MemberInfo(member.getName(), member.getEmail(), member.getProfileImageUrl(), + memberInfoResponse = new MemberInfoResponse(member.getName(), member.getEmail(), + member.getProfileImageUrl(), member.getMemberType(), member.getSocialType(), null, traineeInfo); } - return memberInfo; + return memberInfoResponse; } @Transactional(readOnly = true) @@ -113,21 +115,21 @@ public CheckSessionResponse getMemberType(Long memberId) { } @Transactional - public UpdateProfile checkMemberProfileImage(Long memberId, boolean removeImage, + public ProfileUpdate checkMemberProfileImage(Long memberId, boolean removeImage, @Nullable MultipartFile profileImage) { Member member = getByMemberId(memberId); String currentProfileImageUrl = member.getProfileImageUrl(); String changeProfileImageUrl = ""; boolean removeCurrentImage = true; - boolean isCurrentImageDefault = isDefaultImage(currentProfileImageUrl); - boolean changeImageIsDefault = false; + boolean isCurrentImageDefault = currentProfileImageUrl.equals(TRAINER_DEFAULT_IMAGE) || + currentProfileImageUrl.equals(TRAINEE_DEFAULT_IMAGE); // 새 이미지 없음 if (isNull(profileImage)) { // 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우 if (removeImage && !isCurrentImageDefault) { - changeProfileImageUrl = getDefaultImageUrl(member.getMemberType()); - changeImageIsDefault = true; + changeProfileImageUrl = + member.getMemberType() == TRAINER ? TRAINER_DEFAULT_IMAGE : TRAINEE_DEFAULT_IMAGE; } else if (!removeImage && isCurrentImageDefault) { // 이미지 유지 요청 - 현재 이미지가 기본 이미지인 경우 changeProfileImageUrl = currentProfileImageUrl; removeCurrentImage = false; @@ -141,16 +143,8 @@ public UpdateProfile checkMemberProfileImage(Long memberId, boolean removeImage, } } - return new UpdateProfile(currentProfileImageUrl, changeProfileImageUrl, removeCurrentImage, - isCurrentImageDefault, changeImageIsDefault); - } - - private boolean isDefaultImage(String imageUrl) { - return imageUrl.equals(TRAINER_DEFAULT_IMAGE) || imageUrl.equals(TRAINEE_DEFAULT_IMAGE); - } - - private String getDefaultImageUrl(MemberType memberType) { - return memberType == TRAINER ? TRAINER_DEFAULT_IMAGE : TRAINEE_DEFAULT_IMAGE; + return new ProfileUpdate(currentProfileImageUrl, changeProfileImageUrl, removeCurrentImage, + isCurrentImageDefault); } @Transactional @@ -162,7 +156,7 @@ public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, Str member.updateBirthday(request.birthday()); trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote()); - updatePtGoals(trainee, request.goalContents()); + updatePtGoals(trainee, new HashSet<>(request.goalContents())); traineeRepository.save(trainee); } @@ -183,9 +177,9 @@ public Member getByMemberId(Long memberId) { return memberRepository.findById(memberId); } - private void updatePtGoals(Trainee trainee, List newGoalContents) { + private void updatePtGoals(Trainee trainee, HashSet newGoalContents) { // 기존 PT 목표들 조회 - List currentPtGoals = new ArrayList<>(ptGoalService.getAllByTraineeId(trainee.getId())); + List currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId()); // 기존 목표 중 더 이상 필요없는 목표 삭제 if (!currentPtGoals.isEmpty()) { @@ -210,10 +204,7 @@ private void updatePtGoals(Trainee trainee, List newGoalContents) { List newPtGoals = newGoalContents.stream() .filter(content -> !existingContents.contains(content)) - .map(content -> PtGoal.builder() - .traineeId(trainee.getId()) - .content(content) - .build()) + .map(content -> PtGoal.builder().traineeId(trainee.getId()).content(content).build()) .toList(); if (!newPtGoals.isEmpty()) { diff --git a/src/main/java/com/tnt/member/application/WithdrawService.java b/src/main/java/com/tnt/member/application/WithdrawService.java index cc2b502d..097048be 100644 --- a/src/main/java/com/tnt/member/application/WithdrawService.java +++ b/src/main/java/com/tnt/member/application/WithdrawService.java @@ -21,6 +21,7 @@ import com.tnt.trainee.application.DietService; import com.tnt.trainee.application.PtGoalService; import com.tnt.trainee.application.TraineeService; +import com.tnt.trainee.application.repository.DietRepository; import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.Diet; @@ -43,13 +44,14 @@ public class WithdrawService { private final PtGoalService ptGoalService; private final DietService dietService; private final PtService ptService; - private final PtGoalRepository ptGoalRepository; + private final PtGoalRepository ptGoalRepository; private final MemberRepository memberRepository; private final TrainerRepository trainerRepository; private final TraineeRepository traineeRepository; private final PtLessonRepository ptLessonRepository; private final PtTrainerTraineeRepository ptTrainerTraineeRepository; + private final DietRepository dietRepository; @Transactional public WithdrawDto withdraw(Long memberId) { @@ -109,6 +111,7 @@ private void deleteMemberData(Member member) { ptGoalRepository.deleteAll(ptGoals); diets.forEach(Diet::softDelete); + dietRepository.saveAll(diets); trainee.softDelete(); traineeRepository.save(trainee); diff --git a/src/main/java/com/tnt/member/dto/UpdateProfile.java b/src/main/java/com/tnt/member/dto/ProfileUpdate.java similarity index 55% rename from src/main/java/com/tnt/member/dto/UpdateProfile.java rename to src/main/java/com/tnt/member/dto/ProfileUpdate.java index 673e5d94..17df5768 100644 --- a/src/main/java/com/tnt/member/dto/UpdateProfile.java +++ b/src/main/java/com/tnt/member/dto/ProfileUpdate.java @@ -1,11 +1,10 @@ package com.tnt.member.dto; -public record UpdateProfile( +public record ProfileUpdate( String currentImageUrl, String changeImageUrl, boolean removeCurrentImage, - boolean isCurrentImageDefault, - boolean changeImageIsDefault + boolean isCurrentImageDefault ) { } diff --git a/src/main/java/com/tnt/member/dto/MemberInfo.java b/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java similarity index 96% rename from src/main/java/com/tnt/member/dto/MemberInfo.java rename to src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java index 6b8f287c..3c0bb179 100644 --- a/src/main/java/com/tnt/member/dto/MemberInfo.java +++ b/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java @@ -1,4 +1,4 @@ -package com.tnt.member.dto; +package com.tnt.member.dto.response; import java.time.LocalDate; import java.util.List; @@ -9,7 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "회원 정보") -public record MemberInfo( +public record MemberInfoResponse( @Schema(description = "회원 이름", example = "홍길동", nullable = false) String name, diff --git a/src/main/java/com/tnt/member/presentation/MemberController.java b/src/main/java/com/tnt/member/presentation/MemberController.java index 42d53315..94fe863b 100644 --- a/src/main/java/com/tnt/member/presentation/MemberController.java +++ b/src/main/java/com/tnt/member/presentation/MemberController.java @@ -19,11 +19,11 @@ import com.tnt.member.application.MemberService; import com.tnt.member.application.SignUpService; import com.tnt.member.application.WithdrawService; -import com.tnt.member.dto.MemberInfo; -import com.tnt.member.dto.UpdateProfile; +import com.tnt.member.dto.ProfileUpdate; import com.tnt.member.dto.WithdrawDto; import com.tnt.member.dto.request.SignUpRequest; import com.tnt.member.dto.request.UpdateMemberInfoRequest; +import com.tnt.member.dto.response.MemberInfoResponse; import com.tnt.member.dto.response.SignUpResponse; import io.swagger.v3.oas.annotations.Operation; @@ -56,17 +56,17 @@ public SignUpResponse signUp(@RequestPart("request") @Valid SignUpRequest reques @Operation(summary = "회원 조회 API") @GetMapping @ResponseStatus(OK) - public MemberInfo getMemberInfo(@AuthMember Long memberId) { + public MemberInfoResponse getMemberInfo(@AuthMember Long memberId) { return memberService.getMemberInfo(memberId); } @Operation(summary = "회원 정보 수정 API") - @PutMapping(value = "/change", consumes = MULTIPART_FORM_DATA_VALUE) + @PutMapping(consumes = MULTIPART_FORM_DATA_VALUE) @ResponseStatus(OK) public void updateMemberInfo(@AuthMember Long memberId, @RequestPart("request") @Valid UpdateMemberInfoRequest request, @RequestPart(value = "profileImage", required = false) @Nullable MultipartFile profileImage) { - UpdateProfile profileUpdate = memberService.checkMemberProfileImage(memberId, request.removeImage(), + ProfileUpdate profileUpdate = memberService.checkMemberProfileImage(memberId, request.removeImage(), profileImage); String profileImageUrl = s3Service.handleProfileImage(profileUpdate, profileImage, request.memberType()); diff --git a/src/main/java/com/tnt/pt/application/PtService.java b/src/main/java/com/tnt/pt/application/PtService.java index 858895fe..c998fbb6 100644 --- a/src/main/java/com/tnt/pt/application/PtService.java +++ b/src/main/java/com/tnt/pt/application/PtService.java @@ -213,6 +213,7 @@ public void completePtLesson(Long memberId, Long ptLessonId) { } }); + ptLessonRepository.save(ptLesson); ptLessonRepository.saveAll(lessonsNotCompleted); ptTrainerTraineeRepository.save(ptTrainerTrainee); } @@ -236,6 +237,7 @@ public void cancelPtLesson(Long memberId, Long ptLessonId) { ptTrainerTrainee.cancelLesson(); ptLesson.cancel(ptTrainerTrainee.getCurrentPtSession()); + ptLessonRepository.save(ptLesson); ptLessonRepository.saveAll(lessonsNotCompleted); ptTrainerTraineeRepository.save(ptTrainerTrainee); } diff --git a/src/test/java/com/tnt/image/application/S3ServiceTest.java b/src/test/java/com/tnt/image/application/S3ServiceTest.java index f453e169..c9f8ad7b 100644 --- a/src/test/java/com/tnt/image/application/S3ServiceTest.java +++ b/src/test/java/com/tnt/image/application/S3ServiceTest.java @@ -123,8 +123,7 @@ void rotate_image_orientation_3_success() throws IOException { MockMultipartFile image = new MockMultipartFile("image", "test.jpg", IMAGE_JPEG_VALUE, createDummyImageData(3)); // when - BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", - originalImage, + BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", originalImage, image); // then @@ -141,8 +140,7 @@ void rotate_image_orientation_6_success() throws IOException { // when BufferedImage rotatedImage = ReflectionTestUtils.invokeMethod(s3Service, "rotateImageIfRequired", - originalImage, - image); + originalImage, image); // then assertThat(requireNonNull(rotatedImage).getRGB(25, 50)).isEqualTo(Color.BLACK.getRGB()); diff --git a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java index 3d5f19fb..cbd2af41 100644 --- a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java +++ b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java @@ -351,7 +351,7 @@ void update_member_profile_success() throws Exception { var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(request).getBytes()); - mockMvc.perform(multipart(PUT, "/members/change") + mockMvc.perform(multipart(PUT, "/members") .file(jsonRequest) .file(profileImage) .contentType(MULTIPART_FORM_DATA_VALUE)) @@ -391,7 +391,7 @@ void maintain_member_profile_success() throws Exception { var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(request).getBytes()); - mockMvc.perform(multipart(PUT, "/members/change") + mockMvc.perform(multipart(PUT, "/members") .file(jsonRequest) .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) @@ -430,7 +430,7 @@ void delete_member_profile_success() throws Exception { var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(request).getBytes()); - mockMvc.perform(multipart(PUT, "/members/change") + mockMvc.perform(multipart(PUT, "/members") .file(jsonRequest) .contentType(MULTIPART_FORM_DATA_VALUE)) .andExpect(status().isOk()) @@ -469,7 +469,7 @@ void update_member_info_trainer_success() throws Exception { var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(request).getBytes()); - mockMvc.perform(multipart(PUT, "/members/change") + mockMvc.perform(multipart(PUT, "/members") .file(jsonRequest) .file(profileImage) .contentType(MULTIPART_FORM_DATA_VALUE)) @@ -512,7 +512,7 @@ void update_member_info_trainee_success() throws Exception { var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(request).getBytes()); - mockMvc.perform(multipart(PUT, "/members/change") + mockMvc.perform(multipart(PUT, "/members") .file(jsonRequest) .file(profileImage) .contentType(MULTIPART_FORM_DATA_VALUE)) From 320f015cd25a592b0f523193d6c4541983e587fc Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Thu, 29 May 2025 23:26:38 +0900 Subject: [PATCH 13/13] =?UTF-8?q?[TNT-258]=20refactor:=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84/=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/member/application/MemberService.java | 36 ++++++++----------- .../repository/DietRepository.java | 2 +- .../infrastructure/DietRepositoryImpl.java | 4 +-- .../application/WithdrawServiceTest.java | 7 ++++ 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/tnt/member/application/MemberService.java b/src/main/java/com/tnt/member/application/MemberService.java index 72aaa450..6ec73cd8 100644 --- a/src/main/java/com/tnt/member/application/MemberService.java +++ b/src/main/java/com/tnt/member/application/MemberService.java @@ -7,12 +7,11 @@ import static com.tnt.member.domain.MemberType.TRAINER; import static com.tnt.member.dto.MemberProjection.MemberTypeDto; import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -118,20 +117,20 @@ public CheckSessionResponse getMemberType(Long memberId) { public ProfileUpdate checkMemberProfileImage(Long memberId, boolean removeImage, @Nullable MultipartFile profileImage) { Member member = getByMemberId(memberId); - String currentProfileImageUrl = member.getProfileImageUrl(); - String changeProfileImageUrl = ""; + String currentImageUrl = member.getProfileImageUrl(); + String changeImageUrl = ""; boolean removeCurrentImage = true; - boolean isCurrentImageDefault = currentProfileImageUrl.equals(TRAINER_DEFAULT_IMAGE) || - currentProfileImageUrl.equals(TRAINEE_DEFAULT_IMAGE); + boolean isCurrentImageDefault = + currentImageUrl.equals(TRAINER_DEFAULT_IMAGE) || currentImageUrl.equals(TRAINEE_DEFAULT_IMAGE); // 새 이미지 없음 if (isNull(profileImage)) { // 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우 if (removeImage && !isCurrentImageDefault) { - changeProfileImageUrl = + changeImageUrl = member.getMemberType() == TRAINER ? TRAINER_DEFAULT_IMAGE : TRAINEE_DEFAULT_IMAGE; } else if (!removeImage && isCurrentImageDefault) { // 이미지 유지 요청 - 현재 이미지가 기본 이미지인 경우 - changeProfileImageUrl = currentProfileImageUrl; + changeImageUrl = currentImageUrl; removeCurrentImage = false; } else { // 이미지 유지 요청 - 현재 이미지가 기본 이미지가 아닌 경우 removeCurrentImage = false; @@ -143,7 +142,7 @@ public ProfileUpdate checkMemberProfileImage(Long memberId, boolean removeImage, } } - return new ProfileUpdate(currentProfileImageUrl, changeProfileImageUrl, removeCurrentImage, + return new ProfileUpdate(currentImageUrl, changeImageUrl, removeCurrentImage, isCurrentImageDefault); } @@ -182,25 +181,18 @@ private void updatePtGoals(Trainee trainee, HashSet newGoalContents) { List currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId()); // 기존 목표 중 더 이상 필요없는 목표 삭제 - if (!currentPtGoals.isEmpty()) { - List goalsToDelete = new ArrayList<>(); - - for (PtGoal currentGoal : currentPtGoals) { - if (!newGoalContents.contains(currentGoal.getContent())) { - goalsToDelete.add(currentGoal); - } - } + List goalsToDelete = currentPtGoals.stream() + .filter(goal -> !newGoalContents.contains(goal.getContent())) + .toList(); - if (!goalsToDelete.isEmpty()) { - ptGoalRepository.deleteAll(goalsToDelete); - currentPtGoals.removeAll(goalsToDelete); - } + if (!goalsToDelete.isEmpty()) { + ptGoalRepository.deleteAll(goalsToDelete); } // 새로운 목표 추가 (기존에 없는 것만) Set existingContents = currentPtGoals.stream() .map(PtGoal::getContent) - .collect(Collectors.toSet()); + .collect(toSet()); List newPtGoals = newGoalContents.stream() .filter(content -> !existingContents.contains(content)) diff --git a/src/main/java/com/tnt/trainee/application/repository/DietRepository.java b/src/main/java/com/tnt/trainee/application/repository/DietRepository.java index 55152797..3fcd956d 100644 --- a/src/main/java/com/tnt/trainee/application/repository/DietRepository.java +++ b/src/main/java/com/tnt/trainee/application/repository/DietRepository.java @@ -10,7 +10,7 @@ public interface DietRepository { Diet save(Diet diet); - void saveAll(List diets); + List saveAll(List diets); Diet findByIdAndTraineeId(Long id, Long traineeId); diff --git a/src/main/java/com/tnt/trainee/infrastructure/DietRepositoryImpl.java b/src/main/java/com/tnt/trainee/infrastructure/DietRepositoryImpl.java index fd099ce7..43d73126 100644 --- a/src/main/java/com/tnt/trainee/infrastructure/DietRepositoryImpl.java +++ b/src/main/java/com/tnt/trainee/infrastructure/DietRepositoryImpl.java @@ -30,12 +30,12 @@ public Diet save(Diet diet) { } @Override - public void saveAll(List diets) { + public List saveAll(List diets) { List dietJpaEntities = diets.stream() .map(DietJpaEntity::from) .toList(); - dietJpaRepository.saveAll(dietJpaEntities); + return dietJpaRepository.saveAll(dietJpaEntities).stream().map(DietJpaEntity::toModel).toList(); } @Override diff --git a/src/test/java/com/tnt/member/application/WithdrawServiceTest.java b/src/test/java/com/tnt/member/application/WithdrawServiceTest.java index dbf363c7..fa9aae20 100644 --- a/src/test/java/com/tnt/member/application/WithdrawServiceTest.java +++ b/src/test/java/com/tnt/member/application/WithdrawServiceTest.java @@ -32,6 +32,7 @@ import com.tnt.trainee.application.DietService; import com.tnt.trainee.application.PtGoalService; import com.tnt.trainee.application.TraineeService; +import com.tnt.trainee.application.repository.DietRepository; import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.Diet; @@ -83,6 +84,9 @@ class WithdrawServiceTest { @Mock private PtGoalRepository ptGoalRepository; + @Mock + private DietRepository dietRepository; + @InjectMocks private WithdrawService withdrawService; @@ -119,6 +123,7 @@ void withdraw_trainee_success() { given(traineeService.getByMemberId(traineeMember.getId())).willReturn(trainee); given(ptGoalService.getAllByTraineeId(trainee.getId())).willReturn(ptGoals); given(dietService.getAllByTraineeId(trainee.getId())).willReturn(diets); + given(dietRepository.saveAll(diets)).willReturn(diets); // when withdrawService.withdraw(traineeMember.getId()); @@ -180,6 +185,7 @@ void withdraw_trainee_with_pt_success() { given(dietService.getAllByTraineeId(trainee.getId())).willReturn(diets); given(ptService.getPtTrainerTraineeWithTraineeId(trainee.getId())).willReturn(ptTrainerTrainee); given(ptService.getPtLessonWithPtTrainerTrainee(ptTrainerTrainee)).willReturn(ptLessons); + given(dietRepository.saveAll(diets)).willReturn(diets); // when withdrawService.withdraw(traineeMember.getId()); @@ -231,6 +237,7 @@ void withdraw_trainee_without_pt_success() { given(dietService.getAllByTraineeId(trainee.getId())).willReturn(diets); given(ptService.getPtTrainerTraineeWithTraineeId(trainee.getId())).willThrow(NotFoundException.class); given(traineeRepository.save(trainee)).willReturn(trainee); + given(dietRepository.saveAll(diets)).willReturn(diets); given(memberRepository.save(traineeMember)).willReturn(traineeMember); // when