From c254eb75a425a39fe63ef44dabaef3d34ff1aae6 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sun, 6 Jul 2025 19:21:03 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[TNT-304]=20refactor:=20pt=20=EB=AA=A9?= =?UTF-8?q?=EC=A0=81=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 --- build.gradle | 5 +- .../tnt/common/error/model/ErrorMessage.java | 3 +- .../tnt/member/application/MemberService.java | 43 +--------- .../tnt/member/application/SignUpService.java | 22 +---- .../member/application/WithdrawService.java | 9 --- .../tnt/member/dto/request/SignUpRequest.java | 5 +- .../dto/request/UpdateMemberInfoRequest.java | 5 +- .../dto/response/MemberInfoResponse.java | 5 +- .../com/tnt/pt/application/PtService.java | 12 +-- .../tnt/trainee/application/DietService.java | 1 - .../trainee/application/PtGoalService.java | 21 ----- .../repository/PtGoalRepository.java | 14 ---- .../java/com/tnt/trainee/domain/PtGoal.java | 48 +++++------ .../trainee/domain/PtGoalListConverter.java | 46 +++++++++++ .../java/com/tnt/trainee/domain/Trainee.java | 9 ++- .../infrastructure/PtGoalJpaEntity.java | 56 ------------- .../infrastructure/PtGoalJpaRepository.java | 10 --- .../infrastructure/PtGoalRepositoryImpl.java | 44 ---------- .../infrastructure/TraineeJpaEntity.java | 14 +++- .../response/ConnectWithTraineeResponse.java | 8 +- .../response/GetActiveTraineesResponse.java | 6 +- src/main/resources/config | 2 +- .../java/com/tnt/fixture/PtGoalsFixture.java | 21 ----- .../java/com/tnt/fixture/TraineeFixture.java | 12 +++ .../member/application/SignUpServiceTest.java | 18 ++--- .../application/WithdrawServiceTest.java | 22 +---- .../presentation/MemberControllerTest.java | 34 +++----- .../application/PtGoalServiceTest.java | 79 ------------------ .../presentation/TrainerControllerTest.java | 80 +++---------------- 29 files changed, 159 insertions(+), 495 deletions(-) delete mode 100644 src/main/java/com/tnt/trainee/application/PtGoalService.java delete mode 100644 src/main/java/com/tnt/trainee/application/repository/PtGoalRepository.java create mode 100644 src/main/java/com/tnt/trainee/domain/PtGoalListConverter.java delete mode 100644 src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaEntity.java delete mode 100644 src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaRepository.java delete mode 100644 src/main/java/com/tnt/trainee/infrastructure/PtGoalRepositoryImpl.java delete mode 100644 src/test/java/com/tnt/fixture/PtGoalsFixture.java delete mode 100644 src/test/java/com/tnt/trainee/application/PtGoalServiceTest.java diff --git a/build.gradle b/build.gradle index 4e48378d..f19b1469 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ sonar { property "sonar.host.url", "https://sonarcloud.io" property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml" property "sonar.exclusions", "**/*Application*.java, **/*Config*.java, **/*GlobalExceptionHandler.java, **/Q*.java, **/DynamicQuery.java, " + - "**/*Exception.java, **/*Adapter.java, **/CustomOAuth2UserService.java, **/*Filter.java" + "**/*Exception.java, **/*Adapter.java, **/CustomOAuth2UserService.java, **/*Filter.java, **/*Converter.java" property "sonar.java.coveragePlugin", "jacoco" } } @@ -126,7 +126,8 @@ jacocoTestCoverageVerification { '*.*.CustomUserDetails', 'scouter.*', 'reactor.*', - '*.*RepositoryImpl' + '*.*RepositoryImpl', + '*.*Converter' ] } } diff --git a/src/main/java/com/tnt/common/error/model/ErrorMessage.java b/src/main/java/com/tnt/common/error/model/ErrorMessage.java index a5d97c53..fe3b91bd 100644 --- a/src/main/java/com/tnt/common/error/model/ErrorMessage.java +++ b/src/main/java/com/tnt/common/error/model/ErrorMessage.java @@ -8,6 +8,7 @@ public enum ErrorMessage { SERVER_ERROR("서버 에러가 발생했습니다."), + FAILED_TO_CONVERT_JSON("JSON 직렬화에 실패했습니다."), FCM_FAILED("FCM 전송에 실패했습니다."), S3_UPLOAD_ERROR("S3로 이미지 업로드 중 오류가 발생했습니다."), S3_DELETE_ERROR("S3 이미지 삭제 중 오류가 발생했습니다."), @@ -48,7 +49,7 @@ public enum ErrorMessage { TRAINEE_INVALID_CAUTION_NOTE("주의사항이 올바르지 않습니다."), TRAINEE_NOT_FOUND("존재하지 않는 트레이니입니다."), - PT_GOAL_INVALID_CONTENT("목적 내용이 올바르지 않습니다."), + UNSUPPORTED_PT_GOAL("지원하지 않는 PT 목적입니다."), PT_TRAINER_TRAINEE_ALREADY_EXIST("이미 연결된 트레이너-트레이니입니다."), PT_TRAINEE_ALREADY_EXIST("이미 다른 트레이너와 연결되어 있습니다."), diff --git a/src/main/java/com/tnt/member/application/MemberService.java b/src/main/java/com/tnt/member/application/MemberService.java index 6ec73cd8..bbbf7ad6 100644 --- a/src/main/java/com/tnt/member/application/MemberService.java +++ b/src/main/java/com/tnt/member/application/MemberService.java @@ -7,11 +7,8 @@ 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.HashSet; import java.util.List; -import java.util.Set; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -28,9 +25,7 @@ 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; import com.tnt.trainee.application.TraineeService; -import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; @@ -45,12 +40,10 @@ public class MemberService { private final TrainerService trainerService; private final TraineeService traineeService; - private final PtGoalService ptGoalService; private final PtService ptService; private final MemberRepository memberRepository; private final TraineeRepository traineeRepository; - private final PtGoalRepository ptGoalRepository; @Transactional(readOnly = true) public MemberInfoResponse getMemberInfo(Long memberId) { @@ -78,10 +71,7 @@ public MemberInfoResponse getMemberInfo(Long memberId) { if (member.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(memberId); - List ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()) - .stream() - .map(PtGoal::getContent) - .toList(); + List ptGoals = trainee.getPtGoals(); boolean isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId()); MemberInfoResponse.TraineeInfo traineeInfo = new MemberInfoResponse.TraineeInfo(isConnected, @@ -154,8 +144,7 @@ public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, Str Trainee trainee = traineeService.getByMemberId(memberId); member.updateBirthday(request.birthday()); - trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote()); - updatePtGoals(trainee, new HashSet<>(request.goalContents())); + trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote(), request.ptGoals()); traineeRepository.save(trainee); } @@ -175,32 +164,4 @@ public void validateMemberNotExists(String socialId, SocialType socialType) { public Member getByMemberId(Long memberId) { return memberRepository.findById(memberId); } - - private void updatePtGoals(Trainee trainee, HashSet newGoalContents) { - // 기존 PT 목표들 조회 - List currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId()); - - // 기존 목표 중 더 이상 필요없는 목표 삭제 - List goalsToDelete = currentPtGoals.stream() - .filter(goal -> !newGoalContents.contains(goal.getContent())) - .toList(); - - if (!goalsToDelete.isEmpty()) { - ptGoalRepository.deleteAll(goalsToDelete); - } - - // 새로운 목표 추가 (기존에 없는 것만) - Set existingContents = currentPtGoals.stream() - .map(PtGoal::getContent) - .collect(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); - } - } } diff --git a/src/main/java/com/tnt/member/application/SignUpService.java b/src/main/java/com/tnt/member/application/SignUpService.java index 57d5b06e..7501511f 100644 --- a/src/main/java/com/tnt/member/application/SignUpService.java +++ b/src/main/java/com/tnt/member/application/SignUpService.java @@ -6,8 +6,6 @@ import static com.tnt.member.domain.MemberType.TRAINER; import static io.hypersistence.tsid.TSID.Factory.getTsid; -import java.util.List; - import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,9 +15,7 @@ import com.tnt.member.domain.MemberType; import com.tnt.member.dto.request.SignUpRequest; import com.tnt.member.dto.response.SignUpResponse; -import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; -import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; import com.tnt.trainer.application.repository.TrainerRepository; import com.tnt.trainer.domain.Trainer; @@ -32,11 +28,9 @@ public class SignUpService { private final SessionService sessionService; private final MemberService memberService; - private final MemberRepository memberRepository; private final TrainerRepository trainerRepository; private final TraineeRepository traineeRepository; - private final PtGoalRepository ptGoalRepository; @Transactional public Long signUp(SignUpRequest request) { @@ -81,11 +75,10 @@ private Long createTrainee(SignUpRequest request) { .height(request.height()) .weight(request.weight()) .cautionNote(request.cautionNote()) + .ptGoals(request.ptGoals()) .build(); - trainee = traineeRepository.save(trainee); - - createPtGoals(trainee, request.goalContents()); + traineeRepository.save(trainee); return member.getId(); } @@ -107,15 +100,4 @@ private Member createMember(SignUpRequest request, String defaultImageUrl, Membe return memberRepository.save(member); } - - private void createPtGoals(Trainee trainee, List goalContents) { - List ptGoals = goalContents.stream() - .map(content -> PtGoal.builder() - .traineeId(trainee.getId()) - .content(content) - .build()) - .toList(); - - ptGoalRepository.saveAll(ptGoals); - } } diff --git a/src/main/java/com/tnt/member/application/WithdrawService.java b/src/main/java/com/tnt/member/application/WithdrawService.java index 097048be..fd98303f 100644 --- a/src/main/java/com/tnt/member/application/WithdrawService.java +++ b/src/main/java/com/tnt/member/application/WithdrawService.java @@ -19,13 +19,10 @@ import com.tnt.pt.domain.PtLesson; import com.tnt.pt.domain.PtTrainerTrainee; 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; -import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; import com.tnt.trainer.application.TrainerService; import com.tnt.trainer.application.repository.TrainerRepository; @@ -41,11 +38,8 @@ public class WithdrawService { private final MemberService memberService; private final TrainerService trainerService; private final TraineeService traineeService; - private final PtGoalService ptGoalService; private final DietService dietService; private final PtService ptService; - - private final PtGoalRepository ptGoalRepository; private final MemberRepository memberRepository; private final TrainerRepository trainerRepository; private final TraineeRepository traineeRepository; @@ -90,7 +84,6 @@ private void deleteMemberData(Member member) { if (member.getMemberType() == TRAINEE) { Trainee trainee = traineeService.getByMemberId(member.getId()); - List ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()); List diets = dietService.getAllByTraineeId(trainee.getId()); if (ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId())) { @@ -108,8 +101,6 @@ private void deleteMemberData(Member member) { } } - ptGoalRepository.deleteAll(ptGoals); - diets.forEach(Diet::softDelete); dietRepository.saveAll(diets); diff --git a/src/main/java/com/tnt/member/dto/request/SignUpRequest.java b/src/main/java/com/tnt/member/dto/request/SignUpRequest.java index 23fbb33d..41d3eec2 100644 --- a/src/main/java/com/tnt/member/dto/request/SignUpRequest.java +++ b/src/main/java/com/tnt/member/dto/request/SignUpRequest.java @@ -5,6 +5,7 @@ import com.tnt.member.domain.MemberType; import com.tnt.member.domain.SocialType; +import com.tnt.trainee.domain.PtGoal; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Digits; @@ -66,8 +67,8 @@ public record SignUpRequest( @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) String cautionNote, - @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) - List goalContents + @Schema(description = "PT 목적", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) + List ptGoals ) { } 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 ee0f2a65..c1c268dd 100644 --- a/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java +++ b/src/main/java/com/tnt/member/dto/request/UpdateMemberInfoRequest.java @@ -4,6 +4,7 @@ import java.util.List; import com.tnt.member.domain.MemberType; +import com.tnt.trainee.domain.PtGoal; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Digits; @@ -39,8 +40,8 @@ public record UpdateMemberInfoRequest( @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) String cautionNote, - @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) - List goalContents + @Schema(description = "PT 목적", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) + List ptGoals ) { } diff --git a/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java b/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java index 3c0bb179..fe913046 100644 --- a/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java +++ b/src/main/java/com/tnt/member/dto/response/MemberInfoResponse.java @@ -5,6 +5,7 @@ import com.tnt.member.domain.MemberType; import com.tnt.member.domain.SocialType; +import com.tnt.trainee.domain.PtGoal; import io.swagger.v3.oas.annotations.media.Schema; @@ -61,8 +62,8 @@ public record TraineeInfo( @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) String cautionNote, - @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) - List ptGoals + @Schema(description = "PT 목적", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) + List ptGoals ) { } diff --git a/src/main/java/com/tnt/pt/application/PtService.java b/src/main/java/com/tnt/pt/application/PtService.java index de3b3860..87e57c05 100644 --- a/src/main/java/com/tnt/pt/application/PtService.java +++ b/src/main/java/com/tnt/pt/application/PtService.java @@ -30,7 +30,6 @@ import com.tnt.pt.domain.PtTrainerTrainee; import com.tnt.pt.dto.PtTrainerTraineeProjection; import com.tnt.trainee.application.DietService; -import com.tnt.trainee.application.PtGoalService; import com.tnt.trainee.application.TraineeService; import com.tnt.trainee.domain.Diet; import com.tnt.trainee.domain.PtGoal; @@ -60,7 +59,6 @@ public class PtService { private final TrainerService trainerService; private final TraineeService traineeService; - private final PtGoalService ptGoalService; private final DietService dietService; private final PtTrainerTraineeRepository ptTrainerTraineeRepository; @@ -100,13 +98,12 @@ public ConnectWithTraineeResponse getFirstTrainerTraineeConnect(Long memberId, L Member trainerMember = trainer.getMember(); // fetch join 으로 가져온 member Member traineeMember = trainee.getMember(); // fetch join 으로 가져온 member - List ptGoals = ptGoalService.getAllByTraineeId(traineeId); - String ptGoal = ptGoals.stream().map(PtGoal::getContent).collect(Collectors.joining(", ")); + List ptGoals = trainee.getPtGoals(); return new ConnectWithTraineeResponse( new ConnectTrainerInfo(trainerMember.getName(), trainerMember.getProfileImageUrl()), new ConnectTraineeInfo(traineeMember.getName(), traineeMember.getProfileImageUrl(), - traineeMember.getAge(), trainee.getHeight(), trainee.getWeight(), ptGoal, trainee.getCautionNote()) + traineeMember.getAge(), trainee.getHeight(), trainee.getWeight(), ptGoals, trainee.getCautionNote()) ); } @@ -158,10 +155,7 @@ public GetActiveTraineesResponse getActiveTrainees(Long memberId) { List activeTraineeInfo = trainees.stream().map(trainee -> { PtTrainerTrainee ptTrainerTrainee = ptTrainerTraineeRepository.findByTraineeId(trainee.getId()); - List ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()) - .stream() - .map(PtGoal::getContent) - .toList(); + List ptGoals = ptTrainerTrainee.getTrainee().getPtGoals(); // Memo 추가 구현 필요 return new ActiveTraineeInfo(trainee.getId(), trainee.getMember().getName(), diff --git a/src/main/java/com/tnt/trainee/application/DietService.java b/src/main/java/com/tnt/trainee/application/DietService.java index 39e93320..93a8981e 100644 --- a/src/main/java/com/tnt/trainee/application/DietService.java +++ b/src/main/java/com/tnt/trainee/application/DietService.java @@ -21,7 +21,6 @@ public class DietService { private final TraineeService traineeService; - private final DietRepository dietRepository; @Transactional diff --git a/src/main/java/com/tnt/trainee/application/PtGoalService.java b/src/main/java/com/tnt/trainee/application/PtGoalService.java deleted file mode 100644 index 6034dc19..00000000 --- a/src/main/java/com/tnt/trainee/application/PtGoalService.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tnt.trainee.application; - -import java.util.List; - -import org.springframework.stereotype.Service; - -import com.tnt.trainee.application.repository.PtGoalRepository; -import com.tnt.trainee.domain.PtGoal; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class PtGoalService { - - private final PtGoalRepository ptGoalRepository; - - public List getAllByTraineeId(Long traineeId) { - return ptGoalRepository.findAllByTraineeId(traineeId); - } -} diff --git a/src/main/java/com/tnt/trainee/application/repository/PtGoalRepository.java b/src/main/java/com/tnt/trainee/application/repository/PtGoalRepository.java deleted file mode 100644 index 6f34649e..00000000 --- a/src/main/java/com/tnt/trainee/application/repository/PtGoalRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.tnt.trainee.application.repository; - -import java.util.List; - -import com.tnt.trainee.domain.PtGoal; - -public interface PtGoalRepository { - - List saveAll(List ptGoals); - - List findAllByTraineeId(Long traineeId); - - void deleteAll(List goalsToDelete); -} diff --git a/src/main/java/com/tnt/trainee/domain/PtGoal.java b/src/main/java/com/tnt/trainee/domain/PtGoal.java index 7f3ffd5a..a5761fc7 100644 --- a/src/main/java/com/tnt/trainee/domain/PtGoal.java +++ b/src/main/java/com/tnt/trainee/domain/PtGoal.java @@ -1,33 +1,25 @@ package com.tnt.trainee.domain; -import static com.tnt.common.error.model.ErrorMessage.PT_GOAL_INVALID_CONTENT; -import static io.micrometer.common.util.StringUtils.isBlank; -import static java.util.Objects.requireNonNull; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class PtGoal { - - public static final int CONTENT_LENGTH = 100; - - private final Long id; - private final Long traineeId; - private final String content; - - @Builder - public PtGoal(Long id, Long traineeId, String content) { - this.id = id; - this.traineeId = requireNonNull(traineeId); - this.content = validateContent(content); - } - - private String validateContent(String content) { - if (isBlank(content) || content.length() > CONTENT_LENGTH) { - throw new IllegalArgumentException(PT_GOAL_INVALID_CONTENT.getMessage()); +import static com.tnt.common.error.model.ErrorMessage.UNSUPPORTED_PT_GOAL; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.tnt.common.error.exception.TnTException; + +public enum PtGoal { + WEIGHT_LOSS, + STRENGTH_ENHANCE, + HEALTH_MANAGE, + FLEXIBILITY_ENHANCE, + BODY_PROFILE, + POSTURE_CORRECTION; + + @JsonCreator + public static PtGoal of(String value) { + for (PtGoal type : PtGoal.values()) { + if (type.name().equalsIgnoreCase(value)) { // 대소문자 구분 없이 처리 + return type; + } } - - return content; + throw new TnTException(UNSUPPORTED_PT_GOAL); } } diff --git a/src/main/java/com/tnt/trainee/domain/PtGoalListConverter.java b/src/main/java/com/tnt/trainee/domain/PtGoalListConverter.java new file mode 100644 index 00000000..c7fb41ba --- /dev/null +++ b/src/main/java/com/tnt/trainee/domain/PtGoalListConverter.java @@ -0,0 +1,46 @@ +package com.tnt.trainee.domain; + +import static com.tnt.common.error.model.ErrorMessage.FAILED_TO_CONVERT_JSON; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tnt.common.error.exception.TnTException; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class PtGoalListConverter implements AttributeConverter, String> { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(List attribute) { + if (attribute == null || attribute.isEmpty()) { + return "[]"; + } + + try { + return objectMapper.writeValueAsString(attribute); + } catch (JsonProcessingException e) { + throw new TnTException(FAILED_TO_CONVERT_JSON, e); + } + } + + @Override + public List convertToEntityAttribute(String dbData) { + if (dbData == null || dbData.trim().isEmpty()) { + return new ArrayList<>(); + } + + try { + return objectMapper.readValue(dbData, + objectMapper.getTypeFactory().constructCollectionType(List.class, PtGoal.class)); + } catch (JsonProcessingException e) { + throw new TnTException(FAILED_TO_CONVERT_JSON, e); + } + } +} diff --git a/src/main/java/com/tnt/trainee/domain/Trainee.java b/src/main/java/com/tnt/trainee/domain/Trainee.java index a3b3b5f5..32fdde0a 100644 --- a/src/main/java/com/tnt/trainee/domain/Trainee.java +++ b/src/main/java/com/tnt/trainee/domain/Trainee.java @@ -4,6 +4,7 @@ import static java.util.Objects.isNull; import java.time.LocalDateTime; +import java.util.List; import com.tnt.member.domain.Member; @@ -20,22 +21,26 @@ public class Trainee { private Double height; private Double weight; private String cautionNote; + private List ptGoals; private LocalDateTime deletedAt; @Builder - public Trainee(Long id, Member member, Double height, Double weight, String cautionNote, LocalDateTime deletedAt) { + public Trainee(Long id, Member member, Double height, Double weight, String cautionNote, List ptGoals, + LocalDateTime deletedAt) { this.id = id; this.member = member; this.height = height; this.weight = weight; + this.ptGoals = ptGoals; this.deletedAt = deletedAt; validateAndSetCautionNote(cautionNote); } - public void updateTraineeInfo(Double height, Double weight, String cautionNote) { + public void updateTraineeInfo(Double height, Double weight, String cautionNote, List ptGoals) { this.height = height; this.weight = weight; validateAndSetCautionNote(cautionNote); + this.ptGoals = ptGoals; } private void validateAndSetCautionNote(String cautionNote) { diff --git a/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaEntity.java b/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaEntity.java deleted file mode 100644 index c89ce0db..00000000 --- a/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaEntity.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.tnt.trainee.infrastructure; - -import com.tnt.common.jpa.BaseTimeEntity; -import com.tnt.trainee.domain.PtGoal; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@Table(name = "pt_goal") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class PtGoalJpaEntity extends BaseTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false, unique = true) - private Long id; - - @Column(name = "trainee_id", nullable = false) - private Long traineeId; - - @Column(name = "content", nullable = false) - private String content; - - @Builder - public PtGoalJpaEntity(Long id, Long traineeId, String content) { - this.id = id; - this.traineeId = traineeId; - this.content = content; - } - - public static PtGoalJpaEntity from(PtGoal ptGoal) { - return PtGoalJpaEntity.builder() - .id(ptGoal.getId()) - .traineeId(ptGoal.getTraineeId()) - .content(ptGoal.getContent()) - .build(); - } - - public PtGoal toModel() { - return PtGoal.builder() - .id(id) - .traineeId(traineeId) - .content(content) - .build(); - } -} diff --git a/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaRepository.java b/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaRepository.java deleted file mode 100644 index 8e7598d4..00000000 --- a/src/main/java/com/tnt/trainee/infrastructure/PtGoalJpaRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.tnt.trainee.infrastructure; - -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PtGoalJpaRepository extends JpaRepository { - - List findAllByTraineeId(Long traineeId); -} diff --git a/src/main/java/com/tnt/trainee/infrastructure/PtGoalRepositoryImpl.java b/src/main/java/com/tnt/trainee/infrastructure/PtGoalRepositoryImpl.java deleted file mode 100644 index 398ca69d..00000000 --- a/src/main/java/com/tnt/trainee/infrastructure/PtGoalRepositoryImpl.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.tnt.trainee.infrastructure; - -import java.util.List; - -import org.springframework.stereotype.Repository; - -import com.tnt.trainee.application.repository.PtGoalRepository; -import com.tnt.trainee.domain.PtGoal; - -import lombok.RequiredArgsConstructor; - -@Repository -@RequiredArgsConstructor -public class PtGoalRepositoryImpl implements PtGoalRepository { - - private final PtGoalJpaRepository ptGoalJpaRepository; - - @Override - public List saveAll(List ptGoals) { - List ptGoalJpaEntities = ptGoals.stream() - .map(PtGoalJpaEntity::from) - .toList(); - - return ptGoalJpaRepository.saveAll(ptGoalJpaEntities).stream() - .map(PtGoalJpaEntity::toModel) - .toList(); - } - - @Override - public List findAllByTraineeId(Long traineeId) { - return ptGoalJpaRepository.findAllByTraineeId(traineeId).stream() - .map(PtGoalJpaEntity::toModel) - .toList(); - } - - @Override - public void deleteAll(List goalsToDelete) { - List ptGoalJpaEntities = goalsToDelete.stream() - .map(PtGoalJpaEntity::from) - .toList(); - - ptGoalJpaRepository.deleteAll(ptGoalJpaEntities); - } -} diff --git a/src/main/java/com/tnt/trainee/infrastructure/TraineeJpaEntity.java b/src/main/java/com/tnt/trainee/infrastructure/TraineeJpaEntity.java index 7d470c17..4607ffc5 100644 --- a/src/main/java/com/tnt/trainee/infrastructure/TraineeJpaEntity.java +++ b/src/main/java/com/tnt/trainee/infrastructure/TraineeJpaEntity.java @@ -4,13 +4,18 @@ import static java.util.Objects.requireNonNull; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import com.tnt.common.jpa.BaseTimeEntity; import com.tnt.member.infrastructure.MemberJpaEntity; +import com.tnt.trainee.domain.PtGoal; +import com.tnt.trainee.domain.PtGoalListConverter; import com.tnt.trainee.domain.Trainee; import io.hypersistence.utils.hibernate.id.Tsid; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; @@ -50,14 +55,19 @@ public class TraineeJpaEntity extends BaseTimeEntity { @Column(name = "deleted_at", nullable = true) private LocalDateTime deletedAt; + @Convert(converter = PtGoalListConverter.class) + @Column(name = "pt_goals", columnDefinition = "TEXT", nullable = false) + private List ptGoals; + @Builder public TraineeJpaEntity(Long id, MemberJpaEntity member, Double height, Double weight, String cautionNote, - LocalDateTime deletedAt) { + List ptGoals, LocalDateTime deletedAt) { this.id = id; this.member = requireNonNull(member); this.height = height; this.weight = weight; this.cautionNote = cautionNote; + this.ptGoals = ptGoals != null ? new ArrayList<>(ptGoals) : new ArrayList<>(); this.deletedAt = deletedAt; } @@ -68,6 +78,7 @@ public static TraineeJpaEntity from(Trainee trainee) { .height(trainee.getHeight()) .weight(trainee.getWeight()) .cautionNote(trainee.getCautionNote()) + .ptGoals(trainee.getPtGoals()) .deletedAt(trainee.getDeletedAt()) .build(); } @@ -79,6 +90,7 @@ public Trainee toModel() { .height(height) .weight(weight) .cautionNote(cautionNote) + .ptGoals(ptGoals) .deletedAt(deletedAt) .build(); } diff --git a/src/main/java/com/tnt/trainer/dto/response/ConnectWithTraineeResponse.java b/src/main/java/com/tnt/trainer/dto/response/ConnectWithTraineeResponse.java index a9653692..449fd344 100644 --- a/src/main/java/com/tnt/trainer/dto/response/ConnectWithTraineeResponse.java +++ b/src/main/java/com/tnt/trainer/dto/response/ConnectWithTraineeResponse.java @@ -1,5 +1,9 @@ package com.tnt.trainer.dto.response; +import java.util.List; + +import com.tnt.trainee.domain.PtGoal; + import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "트레이니와 연결 응답 - 트레이너의 화면") @@ -37,8 +41,8 @@ public record ConnectTraineeInfo( @Schema(description = "트레이니 몸무게", example = "70.2kg", nullable = true) Double weight, - @Schema(description = "PT 목표", example = "체중 감량, 근력 향상, 건강 관리", nullable = false) - String ptGoal, + @Schema(description = "PT 목표", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) + List ptGoals, @Schema(description = "주의 사항", example = "왼쪽 발목 골절", nullable = true) String cautionNote diff --git a/src/main/java/com/tnt/trainer/dto/response/GetActiveTraineesResponse.java b/src/main/java/com/tnt/trainer/dto/response/GetActiveTraineesResponse.java index 7e4d3e7d..517c1bec 100644 --- a/src/main/java/com/tnt/trainer/dto/response/GetActiveTraineesResponse.java +++ b/src/main/java/com/tnt/trainer/dto/response/GetActiveTraineesResponse.java @@ -2,6 +2,8 @@ import java.util.List; +import com.tnt.trainee.domain.PtGoal; + import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "관리중인 트레이니 목록 응답") @@ -32,8 +34,8 @@ public record ActiveTraineeInfo( @Schema(description = "메모", example = "건강하지 않음", nullable = true) String memo, - @Schema(description = "PT 목적들", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) - List ptGoals + @Schema(description = "PT 목적", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) + List ptGoals ) { } diff --git a/src/main/resources/config b/src/main/resources/config index 095dc124..7372f14e 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 095dc1242730fd233ef34fae095d6125bfd8f3c8 +Subproject commit 7372f14e730625a190ef6e8f3a1f948ae3291194 diff --git a/src/test/java/com/tnt/fixture/PtGoalsFixture.java b/src/test/java/com/tnt/fixture/PtGoalsFixture.java deleted file mode 100644 index 583aa56b..00000000 --- a/src/test/java/com/tnt/fixture/PtGoalsFixture.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.tnt.fixture; - -import java.util.List; - -import com.tnt.trainee.domain.PtGoal; - -public class PtGoalsFixture { - - public static List getPtGoals(Long traineeId) { - return List.of( - PtGoal.builder() - .traineeId(traineeId) - .content("체중 감량") - .build(), - PtGoal.builder() - .traineeId(traineeId) - .content("근력 향상") - .build() - ); - } -} diff --git a/src/test/java/com/tnt/fixture/TraineeFixture.java b/src/test/java/com/tnt/fixture/TraineeFixture.java index 6001bc55..d45f2e16 100644 --- a/src/test/java/com/tnt/fixture/TraineeFixture.java +++ b/src/test/java/com/tnt/fixture/TraineeFixture.java @@ -1,10 +1,19 @@ package com.tnt.fixture; +import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; + +import java.util.Arrays; +import java.util.List; + import com.tnt.member.domain.Member; +import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; public final class TraineeFixture { + static List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + public static Trainee getTrainee1WithId(Member member) { Long traineeId = 1L; @@ -14,6 +23,7 @@ public static Trainee getTrainee1WithId(Member member) { .height(180.4) .weight(70.5) .cautionNote("주의사항00") + .ptGoals(ptGoals) .build(); } @@ -23,6 +33,7 @@ public static Trainee getTrainee1(Member member) { .height(170.5) .weight(60.5) .cautionNote("주의사항11") + .ptGoals(ptGoals) .build(); } @@ -32,6 +43,7 @@ public static Trainee getTrainee2(Member member) { .height(173.3) .weight(65.5) .cautionNote("주의사항22") + .ptGoals(ptGoals) .build(); } } diff --git a/src/test/java/com/tnt/member/application/SignUpServiceTest.java b/src/test/java/com/tnt/member/application/SignUpServiceTest.java index 9e5b6db9..c0230bfb 100644 --- a/src/test/java/com/tnt/member/application/SignUpServiceTest.java +++ b/src/test/java/com/tnt/member/application/SignUpServiceTest.java @@ -3,17 +3,18 @@ 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.TRAINER; +import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; +import java.util.Arrays; import java.util.List; -import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -29,7 +30,6 @@ import com.tnt.member.domain.Member; import com.tnt.member.dto.request.SignUpRequest; import com.tnt.member.dto.response.SignUpResponse; -import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; @@ -57,9 +57,6 @@ class SignUpServiceTest { @Mock private TraineeRepository traineeRepository; - @Mock - private PtGoalRepository ptGoalRepository; - @Test @DisplayName("트레이너 회원가입 성공") void save_trainer_success() { @@ -90,19 +87,17 @@ void save_trainer_success() { void save_trainee_success() { // given Member traineeMember = MemberFixture.getTraineeMemberWithId1(); + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); given(memberRepository.save(any(Member.class))).willReturn(traineeMember); given(traineeRepository.save(any(Trainee.class))).willReturn( - Trainee.builder().id(1L).member(traineeMember).height(180.0).weight(75.0).build()); - given(ptGoalRepository.saveAll(anyList())).willReturn(Stream.of("목표1", "목표2") - .map(content -> PtGoal.builder().traineeId(traineeMember.getId()).content(content).build()) - .toList()); + Trainee.builder().id(1L).member(traineeMember).height(180.0).weight(75.0).ptGoals(ptGoals).build()); SignUpRequest request = new SignUpRequest(traineeMember.getFcmToken(), traineeMember.getMemberType(), traineeMember.getSocialType(), traineeMember.getSocialId(), traineeMember.getEmail(), traineeMember.getServiceAgreement(), traineeMember.getCollectionAgreement(), traineeMember.getAdvertisementAgreement(), traineeMember.getName(), traineeMember.getBirthday(), 180.0, - 75.0, "주의사항", List.of("목표1", "목표2")); + 75.0, "주의사항", ptGoals); // when Long result = signUpService.signUp(request); @@ -111,7 +106,6 @@ void save_trainee_success() { assertThat(result).isNotNull().isEqualTo(traineeMember.getId()); verify(memberRepository).save(any(Member.class)); verify(traineeRepository).save(any(Trainee.class)); - verify(ptGoalRepository).saveAll(any()); } @Test diff --git a/src/test/java/com/tnt/member/application/WithdrawServiceTest.java b/src/test/java/com/tnt/member/application/WithdrawServiceTest.java index fa9aae20..f79e4ece 100644 --- a/src/test/java/com/tnt/member/application/WithdrawServiceTest.java +++ b/src/test/java/com/tnt/member/application/WithdrawServiceTest.java @@ -30,13 +30,10 @@ import com.tnt.pt.domain.PtLesson; import com.tnt.pt.domain.PtTrainerTrainee; 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; -import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; import com.tnt.trainer.application.TrainerService; import com.tnt.trainer.application.repository.TrainerRepository; @@ -57,9 +54,6 @@ class WithdrawServiceTest { @Mock private TraineeService traineeService; - @Mock - private PtGoalService ptGoalService; - @Mock private DietService dietService; @@ -81,9 +75,6 @@ class WithdrawServiceTest { @Mock private PtLessonRepository ptLessonRepository; - @Mock - private PtGoalRepository ptGoalRepository; - @Mock private DietRepository dietRepository; @@ -113,15 +104,10 @@ void withdraw_trainee_success() { // given Member traineeMember = MemberFixture.getTraineeMemberWithId1(); Trainee trainee = TraineeFixture.getTrainee1WithId(traineeMember); - - List ptGoals = List.of( - PtGoal.builder().id(1L).traineeId(trainee.getId()).content("test").build()); - List diets = List.of(DietFixture.getDiet1(trainee.getId()), - DietFixture.getDiet2(trainee.getId())); + List diets = List.of(DietFixture.getDiet1(trainee.getId()), DietFixture.getDiet2(trainee.getId())); given(memberService.getByMemberId(traineeMember.getId())).willReturn(traineeMember); 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); @@ -171,8 +157,6 @@ void withdraw_trainee_with_pt_success() { PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); - List ptGoals = List.of( - PtGoal.builder().id(1L).traineeId(trainee.getId()).content("test").build()); List diets = List.of(DietFixture.getDiet1(trainee.getId()), DietFixture.getDiet2(trainee.getId())); @@ -181,7 +165,6 @@ void withdraw_trainee_with_pt_success() { given(memberService.getByMemberId(traineeMember.getId())).willReturn(traineeMember); given(traineeService.getByMemberId(traineeMember.getId())).willReturn(trainee); given(ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId())).willReturn(true); - given(ptGoalService.getAllByTraineeId(trainee.getId())).willReturn(ptGoals); given(dietService.getAllByTraineeId(trainee.getId())).willReturn(diets); given(ptService.getPtTrainerTraineeWithTraineeId(trainee.getId())).willReturn(ptTrainerTrainee); given(ptService.getPtLessonWithPtTrainerTrainee(ptTrainerTrainee)).willReturn(ptLessons); @@ -225,15 +208,12 @@ void withdraw_trainee_without_pt_success() { Trainee trainee = TraineeFixture.getTrainee1WithId(traineeMember); - List ptGoals = List.of( - PtGoal.builder().id(1L).traineeId(trainee.getId()).content("test").build()); List diets = List.of(DietFixture.getDiet1(trainee.getId()), DietFixture.getDiet2(trainee.getId())); given(memberService.getByMemberId(traineeMember.getId())).willReturn(traineeMember); given(traineeService.getByMemberId(traineeMember.getId())).willReturn(trainee); given(ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId())).willReturn(true); - given(ptGoalService.getAllByTraineeId(trainee.getId())).willReturn(ptGoals); given(dietService.getAllByTraineeId(trainee.getId())).willReturn(diets); given(ptService.getPtTrainerTraineeWithTraineeId(trainee.getId())).willThrow(NotFoundException.class); given(traineeRepository.save(trainee)).willReturn(trainee); diff --git a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java index cbd2af41..c59a5d5d 100644 --- a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java +++ b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java @@ -5,6 +5,8 @@ import static com.tnt.member.domain.MemberType.TRAINEE; import static com.tnt.member.domain.MemberType.TRAINER; import static com.tnt.member.domain.SocialType.KAKAO; +import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpMethod.PUT; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -18,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.time.LocalDate; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -46,7 +49,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.tnt.AbstractContainerBaseTest; import com.tnt.fixture.MemberFixture; -import com.tnt.fixture.PtGoalsFixture; import com.tnt.fixture.PtTrainerTraineeFixture; import com.tnt.fixture.TraineeFixture; import com.tnt.fixture.TrainerFixture; @@ -57,7 +59,6 @@ import com.tnt.member.dto.request.UpdateMemberInfoRequest; import com.tnt.pt.application.repository.PtTrainerTraineeRepository; import com.tnt.pt.domain.PtTrainerTrainee; -import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; @@ -93,9 +94,6 @@ class MemberControllerTest extends AbstractContainerBaseTest { @Autowired private TraineeRepository traineeRepository; - @Autowired - private PtGoalRepository ptGoalRepository; - @Autowired private PtTrainerTraineeRepository ptTrainerTraineeRepository; @@ -104,7 +102,7 @@ class MemberControllerTest extends AbstractContainerBaseTest { void sign_up_trainer_success() throws Exception { // given SignUpRequest request = new SignUpRequest("fcm-token-test", TRAINER, KAKAO, "12345", "test@kakao.com", true, - true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "근력 향상")); + true, true, "홍길동", LocalDate.of(1990, 1, 1), null, null, null, List.of()); // when var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, @@ -126,8 +124,10 @@ void sign_up_trainer_success() throws Exception { @DisplayName("통합 테스트 - 트레이니 회원가입 성공") void sign_up_trainee_success() throws Exception { // given + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + SignUpRequest request = new SignUpRequest("fcm-token-test", TRAINEE, KAKAO, "12345", "test@kakao.com", true, - true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "근력 향상")); + true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", ptGoals); // when var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, @@ -149,8 +149,10 @@ void sign_up_trainee_success() throws Exception { @DisplayName("통합 테스트 - 필수 필드 누락으로 회원가입 실패") void sign_up_missing_required_field_fail() throws Exception { // given + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + SignUpRequest request = new SignUpRequest("", TRAINER, KAKAO, "12345", "test@kakao.com", true, - true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "근력 향상")); + true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", ptGoals); // when var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, @@ -248,10 +250,6 @@ void get_member_info_trainer_success() throws Exception { trainee2 = traineeRepository.save(trainee2); trainee3 = traineeRepository.save(trainee3); - List ptGoals = PtGoalsFixture.getPtGoals(trainee1.getId()); - - ptGoalRepository.saveAll(ptGoals); - PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee1); PtTrainerTrainee ptTrainerTrainee2 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee2); PtTrainerTrainee ptTrainerTrainee3 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee3); @@ -300,10 +298,6 @@ void get_member_info_trainee_success() throws Exception { trainer = trainerRepository.save(trainer); trainee = traineeRepository.save(trainee); - List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); - - ptGoalRepository.saveAll(ptGoals); - PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); ptTrainerTraineeRepository.save(ptTrainerTrainee); @@ -463,7 +457,7 @@ void update_member_info_trainer_success() throws Exception { trainerRepository.save(trainer); UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINER, "홍길동", null, null, null, null, - null); + List.of()); // when & then var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, @@ -501,12 +495,10 @@ void update_member_info_trainee_success() throws Exception { trainee = traineeRepository.save(trainee); - List ptGoals = PtGoalsFixture.getPtGoals(trainee.getId()); - - ptGoalRepository.saveAll(ptGoals); + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); UpdateMemberInfoRequest request = new UpdateMemberInfoRequest(true, TRAINEE, "홍길동", LocalDate.of(1990, 1, 1), - 175.0, 70.0, "테스트 주의사항", List.of("체중 감량", "건강 관리")); + 175.0, 70.0, "테스트 주의사항", ptGoals); // when & then var jsonRequest = new MockMultipartFile("request", "", APPLICATION_JSON_VALUE, diff --git a/src/test/java/com/tnt/trainee/application/PtGoalServiceTest.java b/src/test/java/com/tnt/trainee/application/PtGoalServiceTest.java deleted file mode 100644 index c0f106ff..00000000 --- a/src/test/java/com/tnt/trainee/application/PtGoalServiceTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.tnt.trainee.application; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; - -import java.util.List; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.tnt.trainee.application.repository.PtGoalRepository; -import com.tnt.trainee.domain.PtGoal; - -@ExtendWith(MockitoExtension.class) -class PtGoalServiceTest { - - @Mock - private PtGoalRepository ptGoalRepository; - - @InjectMocks - private PtGoalService ptGoalService; - - @Test - @DisplayName("traineeId로 PT 목표 목록 조회 성공") - void get_all_pt_goals_with_trainee_id_success() { - // given - Long traineeId = 1L; - List ptGoals = List.of( - PtGoal.builder() - .traineeId(traineeId) - .content("목표1") - .build(), - PtGoal.builder() - .traineeId(traineeId) - .content("목표2") - .build() - ); - - given(ptGoalRepository.findAllByTraineeId(traineeId)) - .willReturn(ptGoals); - - // when - List result = ptGoalService.getAllByTraineeId(traineeId); - - // then - assertThat(result).isNotNull().hasSize(2).isEqualTo(ptGoals); - verify(ptGoalRepository).findAllByTraineeId(traineeId); - } - - @Test - @DisplayName("PT 목표 목록 저장 성공") - void save_all_pt_goals_success() { - // given - List ptGoals = List.of( - PtGoal.builder() - .traineeId(1L) - .content("목표1") - .build(), - PtGoal.builder() - .traineeId(1L) - .content("목표2") - .build() - ); - - given(ptGoalRepository.saveAll(ptGoals)).willReturn(ptGoals); - - // when - List savedPtGoals = ptGoalRepository.saveAll(ptGoals); - - // then - assertThat(savedPtGoals).isNotNull().hasSize(2).isEqualTo(ptGoals); - verify(ptGoalRepository).saveAll(ptGoals); - } -} diff --git a/src/test/java/com/tnt/trainer/presentation/TrainerControllerTest.java b/src/test/java/com/tnt/trainer/presentation/TrainerControllerTest.java index 3874152e..dec74505 100644 --- a/src/test/java/com/tnt/trainer/presentation/TrainerControllerTest.java +++ b/src/test/java/com/tnt/trainer/presentation/TrainerControllerTest.java @@ -2,6 +2,8 @@ import static com.tnt.member.domain.MemberType.TRAINER; import static com.tnt.member.domain.SocialType.KAKAO; +import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -13,6 +15,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -33,7 +36,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.tnt.annotation.WithMockCustomUser; import com.tnt.fixture.MemberFixture; -import com.tnt.fixture.PtGoalsFixture; import com.tnt.fixture.PtTrainerTraineeFixture; import com.tnt.fixture.TraineeFixture; import com.tnt.fixture.TrainerFixture; @@ -44,7 +46,6 @@ import com.tnt.pt.application.repository.PtTrainerTraineeRepository; import com.tnt.pt.domain.PtLesson; import com.tnt.pt.domain.PtTrainerTrainee; -import com.tnt.trainee.application.repository.PtGoalRepository; import com.tnt.trainee.application.repository.TraineeRepository; import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; @@ -78,9 +79,6 @@ class TrainerControllerTest { @Autowired private PtTrainerTraineeRepository ptTrainerTraineeRepository; - @Autowired - private PtGoalRepository ptGoalRepository; - @Autowired private PtLessonRepository ptLessonRepository; @@ -281,11 +279,14 @@ void get_first_connected_trainee_success() throws Exception { .member(trainerMember) .build(); + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + Trainee trainee = Trainee.builder() .member(traineeMember) .height(180.5) .weight(78.4) .cautionNote("주의사항") + .ptGoals(ptGoals) .build(); trainer = trainerRepository.save(trainer); @@ -295,18 +296,6 @@ void get_first_connected_trainee_success() throws Exception { ptTrainerTraineeRepository.save(ptTrainerTrainee); - PtGoal ptGoal1 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("다이어트") - .build(); - - PtGoal ptGoal2 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("체중 감량") - .build(); - - ptGoalRepository.saveAll(List.of(ptGoal1, ptGoal2)); - // when & then mockMvc.perform(get("/trainers/first-connected-trainee") .param("trainerId", trainer.getId().toString()) @@ -462,12 +451,6 @@ void get_active_trainees_success() throws Exception { trainee1 = traineeRepository.save(trainee1); trainee2 = traineeRepository.save(trainee2); - List ptGoals1 = PtGoalsFixture.getPtGoals(trainee1.getId()); - List ptGoals2 = PtGoalsFixture.getPtGoals(trainee2.getId()); - - ptGoalRepository.saveAll(ptGoals1); - ptGoalRepository.saveAll(ptGoals2); - PtTrainerTrainee ptTrainerTrainee1 = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee1); PtTrainerTrainee ptTrainerTrainee2 = PtTrainerTraineeFixture.getPtTrainerTrainee2(trainer, trainee2); @@ -516,11 +499,14 @@ void add_pt_lesson_success1() throws Exception { .member(trainerMember) .build(); + List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + Trainee trainee = Trainee.builder() .member(traineeMember) .height(180.5) .weight(78.4) .cautionNote("주의사항") + .ptGoals(ptGoals) .build(); trainer = trainerRepository.save(trainer); @@ -529,18 +515,6 @@ void add_pt_lesson_success1() throws Exception { PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); ptTrainerTraineeRepository.save(ptTrainerTrainee); - PtGoal ptGoal1 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("다이어트") - .build(); - - PtGoal ptGoal2 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("체중 감량") - .build(); - - ptGoalRepository.saveAll(List.of(ptGoal1, ptGoal2)); - LocalDateTime start = LocalDateTime.of(2025, 1, 1, 10, 0); LocalDateTime end = LocalDateTime.of(2025, 1, 1, 11, 0); String memo = "THIS IS MEMO"; @@ -588,18 +562,6 @@ void add_pt_lesson_success2() throws Exception { PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); ptTrainerTrainee = ptTrainerTraineeRepository.save(ptTrainerTrainee); - PtGoal ptGoal1 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("다이어트") - .build(); - - PtGoal ptGoal2 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("체중 감량") - .build(); - - ptGoalRepository.saveAll(List.of(ptGoal1, ptGoal2)); - LocalDateTime startDate1 = LocalDateTime.parse("2025-02-01T11:30"); LocalDateTime endDate1 = LocalDateTime.parse("2025-02-01T13:00"); @@ -745,18 +707,6 @@ void add_pt_lesson_success4() throws Exception { PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); ptTrainerTrainee = ptTrainerTraineeRepository.save(ptTrainerTrainee); - PtGoal ptGoal1 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("다이어트") - .build(); - - PtGoal ptGoal2 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("체중 감량") - .build(); - - ptGoalRepository.saveAll(List.of(ptGoal1, ptGoal2)); - LocalDateTime startDate1 = LocalDateTime.parse("2025-02-01T11:30"); LocalDateTime endDate1 = LocalDateTime.parse("2025-02-01T13:00"); @@ -897,18 +847,6 @@ void add_pt_lesson_fail1() throws Exception { PtTrainerTrainee ptTrainerTrainee = PtTrainerTraineeFixture.getPtTrainerTrainee1(trainer, trainee); ptTrainerTrainee = ptTrainerTraineeRepository.save(ptTrainerTrainee); - PtGoal ptGoal1 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("다이어트") - .build(); - - PtGoal ptGoal2 = PtGoal.builder() - .traineeId(trainee.getId()) - .content("체중 감량") - .build(); - - ptGoalRepository.saveAll(List.of(ptGoal1, ptGoal2)); - LocalDateTime createdStart = LocalDateTime.of(2025, 1, 1, 10, 0); LocalDateTime createdEnd = LocalDateTime.of(2025, 1, 1, 11, 0); From 99c0c8d7aa99d1b2c018d34cb57a5ee0648048e1 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 14:54:26 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[TNT-304]chore:=20claude=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1939075b..99c7c98f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ application-local.yml application-dev.yml application-prod.yml /src/main/generated/ +.claude ### NetBeans ### /nbproject/private/ @@ -47,3 +48,6 @@ application-prod.yml ./src/main/resources/config /logs + +# AI Tool Configuration +CLAUDE.md From 39192c7edc8d45eaca78a6f3c9e61004a3064e26 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 15:15:49 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/member/application/SignUpService.java | 8 ++++- .../tnt/member/dto/request/SignUpRequest.java | 5 ++- .../java/com/tnt/trainee/domain/PtGoal.java | 33 ++++++++++++++----- .../java/com/tnt/trainee/domain/Trainee.java | 4 +++ .../member/application/SignUpServiceTest.java | 3 +- .../presentation/MemberControllerTest.java | 4 +-- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/tnt/member/application/SignUpService.java b/src/main/java/com/tnt/member/application/SignUpService.java index 7501511f..6fc98211 100644 --- a/src/main/java/com/tnt/member/application/SignUpService.java +++ b/src/main/java/com/tnt/member/application/SignUpService.java @@ -6,6 +6,8 @@ import static com.tnt.member.domain.MemberType.TRAINER; import static io.hypersistence.tsid.TSID.Factory.getTsid; +import java.util.List; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,6 +18,7 @@ import com.tnt.member.dto.request.SignUpRequest; import com.tnt.member.dto.response.SignUpResponse; import com.tnt.trainee.application.repository.TraineeRepository; +import com.tnt.trainee.domain.PtGoal; import com.tnt.trainee.domain.Trainee; import com.tnt.trainer.application.repository.TrainerRepository; import com.tnt.trainer.domain.Trainer; @@ -70,12 +73,15 @@ private Long createTrainer(SignUpRequest request) { private Long createTrainee(SignUpRequest request) { Member member = createMember(request, TRAINEE_DEFAULT_IMAGE, TRAINEE); + + List ptGoals = request.ptGoals().stream().map(PtGoal::of).toList(); + Trainee trainee = Trainee.builder() .member(member) .height(request.height()) .weight(request.weight()) .cautionNote(request.cautionNote()) - .ptGoals(request.ptGoals()) + .ptGoals(ptGoals) .build(); traineeRepository.save(trainee); diff --git a/src/main/java/com/tnt/member/dto/request/SignUpRequest.java b/src/main/java/com/tnt/member/dto/request/SignUpRequest.java index 41d3eec2..e0afa7cd 100644 --- a/src/main/java/com/tnt/member/dto/request/SignUpRequest.java +++ b/src/main/java/com/tnt/member/dto/request/SignUpRequest.java @@ -5,7 +5,6 @@ import com.tnt.member.domain.MemberType; import com.tnt.member.domain.SocialType; -import com.tnt.trainee.domain.PtGoal; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Digits; @@ -67,8 +66,8 @@ public record SignUpRequest( @Schema(description = "주의사항", example = "가냘퍼요", nullable = true) String cautionNote, - @Schema(description = "PT 목적", example = "[\"WEIGHT_LOSS\", \"STRENGTH_ENHANCE\"]", nullable = false) - List ptGoals + @Schema(description = "PT 목적", example = "[\"체중 감량\", \"근력 향상\"]", nullable = false) + List ptGoals ) { } diff --git a/src/main/java/com/tnt/trainee/domain/PtGoal.java b/src/main/java/com/tnt/trainee/domain/PtGoal.java index a5761fc7..1fef4d31 100644 --- a/src/main/java/com/tnt/trainee/domain/PtGoal.java +++ b/src/main/java/com/tnt/trainee/domain/PtGoal.java @@ -6,20 +6,35 @@ import com.tnt.common.error.exception.TnTException; public enum PtGoal { - WEIGHT_LOSS, - STRENGTH_ENHANCE, - HEALTH_MANAGE, - FLEXIBILITY_ENHANCE, - BODY_PROFILE, - POSTURE_CORRECTION; + WEIGHT_LOSS("체중 감량"), + STRENGTH_ENHANCE("근력 향상"), + HEALTH_MANAGE("건강 관리"), + FLEXIBILITY_ENHANCE("유연성향상"), + BODY_PROFILE("바디프로필"), + POSTURE_CORRECTION("자세 교정"); + + private final String koreanName; + + PtGoal(String koreanName) { + this.koreanName = koreanName; + } @JsonCreator public static PtGoal of(String value) { - for (PtGoal type : PtGoal.values()) { - if (type.name().equalsIgnoreCase(value)) { // 대소문자 구분 없이 처리 - return type; + // 1. 영어 enum 이름으로 시도 + for (PtGoal goal : PtGoal.values()) { + if (goal.name().equalsIgnoreCase(value)) { + return goal; } } + + // 2. 한글 이름으로 시도 + for (PtGoal goal : PtGoal.values()) { + if (goal.koreanName.equals(value)) { + return goal; + } + } + throw new TnTException(UNSUPPORTED_PT_GOAL); } } diff --git a/src/main/java/com/tnt/trainee/domain/Trainee.java b/src/main/java/com/tnt/trainee/domain/Trainee.java index 32fdde0a..3c36380e 100644 --- a/src/main/java/com/tnt/trainee/domain/Trainee.java +++ b/src/main/java/com/tnt/trainee/domain/Trainee.java @@ -36,6 +36,10 @@ public Trainee(Long id, Member member, Double height, Double weight, String caut validateAndSetCautionNote(cautionNote); } + public void updatePtGoals(List ptGoals) { + this.ptGoals = ptGoals; + } + public void updateTraineeInfo(Double height, Double weight, String cautionNote, List ptGoals) { this.height = height; this.weight = weight; diff --git a/src/test/java/com/tnt/member/application/SignUpServiceTest.java b/src/test/java/com/tnt/member/application/SignUpServiceTest.java index c0230bfb..beccb2d8 100644 --- a/src/test/java/com/tnt/member/application/SignUpServiceTest.java +++ b/src/test/java/com/tnt/member/application/SignUpServiceTest.java @@ -88,6 +88,7 @@ void save_trainee_success() { // given Member traineeMember = MemberFixture.getTraineeMemberWithId1(); List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + List ptGoalStrings = Arrays.asList("체중 감량", "근력 향상"); given(memberRepository.save(any(Member.class))).willReturn(traineeMember); given(traineeRepository.save(any(Trainee.class))).willReturn( @@ -97,7 +98,7 @@ void save_trainee_success() { traineeMember.getSocialType(), traineeMember.getSocialId(), traineeMember.getEmail(), traineeMember.getServiceAgreement(), traineeMember.getCollectionAgreement(), traineeMember.getAdvertisementAgreement(), traineeMember.getName(), traineeMember.getBirthday(), 180.0, - 75.0, "주의사항", ptGoals); + 75.0, "주의사항", ptGoalStrings); // when Long result = signUpService.signUp(request); diff --git a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java index c59a5d5d..100c58b4 100644 --- a/src/test/java/com/tnt/member/presentation/MemberControllerTest.java +++ b/src/test/java/com/tnt/member/presentation/MemberControllerTest.java @@ -124,7 +124,7 @@ void sign_up_trainer_success() throws Exception { @DisplayName("통합 테스트 - 트레이니 회원가입 성공") void sign_up_trainee_success() throws Exception { // given - List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + List ptGoals = Arrays.asList("체중 감량", "근력 향상"); SignUpRequest request = new SignUpRequest("fcm-token-test", TRAINEE, KAKAO, "12345", "test@kakao.com", true, true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", ptGoals); @@ -149,7 +149,7 @@ void sign_up_trainee_success() throws Exception { @DisplayName("통합 테스트 - 필수 필드 누락으로 회원가입 실패") void sign_up_missing_required_field_fail() throws Exception { // given - List ptGoals = Arrays.asList(WEIGHT_LOSS, STRENGTH_ENHANCE); + List ptGoals = Arrays.asList("체중 감량", "근력 향상"); SignUpRequest request = new SignUpRequest("", TRAINER, KAKAO, "12345", "test@kakao.com", true, true, true, "홍길동", LocalDate.of(1990, 1, 1), 175.0, 70.0, "테스트 주의사항", ptGoals); From e9151cad0fe076a1f010635116bf9810960e6af5 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 15:32:23 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++ src/main/resources/config | 2 +- .../V1__add_pt_goals_column_to_trainee.sql | 3 + .../migration/V2__migrate_pt_goal_data.java | 72 +++++++++++++++++++ .../db/migration/V3__drop_pt_goal_table.sql | 2 + 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/migration/V1__add_pt_goals_column_to_trainee.sql create mode 100644 src/main/resources/db/migration/V2__migrate_pt_goal_data.java create mode 100644 src/main/resources/db/migration/V3__drop_pt_goal_table.sql diff --git a/build.gradle b/build.gradle index f19b1469..dd50a9dc 100644 --- a/build.gradle +++ b/build.gradle @@ -215,4 +215,8 @@ dependencies { // 이미지 메타데이터 관련 라이브러리 implementation 'com.drewnoakes:metadata-extractor:2.19.0' implementation 'org.apache.commons:commons-imaging:1.0.0-alpha5' + + // Flyway + implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-mysql' } diff --git a/src/main/resources/config b/src/main/resources/config index 7372f14e..2a1ef66b 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit 7372f14e730625a190ef6e8f3a1f948ae3291194 +Subproject commit 2a1ef66b1afe41ea1acdbef9f0302cc35413c13d diff --git a/src/main/resources/db/migration/V1__add_pt_goals_column_to_trainee.sql b/src/main/resources/db/migration/V1__add_pt_goals_column_to_trainee.sql new file mode 100644 index 00000000..1f7e9a02 --- /dev/null +++ b/src/main/resources/db/migration/V1__add_pt_goals_column_to_trainee.sql @@ -0,0 +1,3 @@ +-- Add pt_goals column to trainee table +ALTER TABLE trainee +ADD COLUMN pt_goals TEXT NOT NULL DEFAULT '[]'; diff --git a/src/main/resources/db/migration/V2__migrate_pt_goal_data.java b/src/main/resources/db/migration/V2__migrate_pt_goal_data.java new file mode 100644 index 00000000..1a8671a6 --- /dev/null +++ b/src/main/resources/db/migration/V2__migrate_pt_goal_data.java @@ -0,0 +1,72 @@ +package db.migration; + +import static com.tnt.trainee.domain.PtGoal.BODY_PROFILE; +import static com.tnt.trainee.domain.PtGoal.FLEXIBILITY_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.HEALTH_MANAGE; +import static com.tnt.trainee.domain.PtGoal.POSTURE_CORRECTION; +import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; +import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tnt.trainee.domain.PtGoal; + +public class V2__migrate_pt_goal_data extends BaseJavaMigration { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void migrate(Context context) throws Exception { + // 1. pt_goal 테이블에서 모든 데이터 조회 + Map> traineeGoalsMap = new HashMap<>(); + + String selectSql = "SELECT trainee_id, content FROM pt_goal WHERE deleted_at IS NULL"; + try (PreparedStatement selectStmt = context.getConnection().prepareStatement(selectSql)) { + ResultSet rs = selectStmt.executeQuery(); + while (rs.next()) { + Long traineeId = rs.getLong("trainee_id"); + String content = rs.getString("content"); + + PtGoal ptGoal = mapContentToPtGoal(content); + if (ptGoal != null) { + traineeGoalsMap.computeIfAbsent(traineeId, k -> new ArrayList<>()).add(ptGoal); + } + } + } + + // 2. trainee 테이블 업데이트 + String updateSql = "UPDATE trainee SET pt_goals = ? WHERE id = ?"; + try (PreparedStatement updateStmt = context.getConnection().prepareStatement(updateSql)) { + for (Map.Entry> entry : traineeGoalsMap.entrySet()) { + Long traineeId = entry.getKey(); + List ptGoals = entry.getValue(); + + String jsonGoals = objectMapper.writeValueAsString(ptGoals); + updateStmt.setString(1, jsonGoals); + updateStmt.setLong(2, traineeId); + updateStmt.executeUpdate(); + } + } + } + + private PtGoal mapContentToPtGoal(String content) { + return switch (content.trim()) { + case "근력 향상" -> STRENGTH_ENHANCE; + case "체중 감량" -> WEIGHT_LOSS; + case "건강 관리" -> HEALTH_MANAGE; + case "자세 교정" -> POSTURE_CORRECTION; + case "바디프로필" -> BODY_PROFILE; + case "유연성향상" -> FLEXIBILITY_ENHANCE; + default -> null; + }; + } +} diff --git a/src/main/resources/db/migration/V3__drop_pt_goal_table.sql b/src/main/resources/db/migration/V3__drop_pt_goal_table.sql new file mode 100644 index 00000000..95d9d478 --- /dev/null +++ b/src/main/resources/db/migration/V3__drop_pt_goal_table.sql @@ -0,0 +1,2 @@ +-- Drop pt_goal table after data migration +DROP TABLE IF EXISTS pt_goal; From 6e49146242921e7cc9d76e8665f459e5a81d628b Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 16:00:28 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/V2__migrate_pt_goal_data.java | 72 ------------------- .../db/migration/V3__drop_pt_goal_table.sql | 2 - 2 files changed, 74 deletions(-) delete mode 100644 src/main/resources/db/migration/V2__migrate_pt_goal_data.java delete mode 100644 src/main/resources/db/migration/V3__drop_pt_goal_table.sql diff --git a/src/main/resources/db/migration/V2__migrate_pt_goal_data.java b/src/main/resources/db/migration/V2__migrate_pt_goal_data.java deleted file mode 100644 index 1a8671a6..00000000 --- a/src/main/resources/db/migration/V2__migrate_pt_goal_data.java +++ /dev/null @@ -1,72 +0,0 @@ -package db.migration; - -import static com.tnt.trainee.domain.PtGoal.BODY_PROFILE; -import static com.tnt.trainee.domain.PtGoal.FLEXIBILITY_ENHANCE; -import static com.tnt.trainee.domain.PtGoal.HEALTH_MANAGE; -import static com.tnt.trainee.domain.PtGoal.POSTURE_CORRECTION; -import static com.tnt.trainee.domain.PtGoal.STRENGTH_ENHANCE; -import static com.tnt.trainee.domain.PtGoal.WEIGHT_LOSS; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.flywaydb.core.api.migration.BaseJavaMigration; -import org.flywaydb.core.api.migration.Context; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.tnt.trainee.domain.PtGoal; - -public class V2__migrate_pt_goal_data extends BaseJavaMigration { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void migrate(Context context) throws Exception { - // 1. pt_goal 테이블에서 모든 데이터 조회 - Map> traineeGoalsMap = new HashMap<>(); - - String selectSql = "SELECT trainee_id, content FROM pt_goal WHERE deleted_at IS NULL"; - try (PreparedStatement selectStmt = context.getConnection().prepareStatement(selectSql)) { - ResultSet rs = selectStmt.executeQuery(); - while (rs.next()) { - Long traineeId = rs.getLong("trainee_id"); - String content = rs.getString("content"); - - PtGoal ptGoal = mapContentToPtGoal(content); - if (ptGoal != null) { - traineeGoalsMap.computeIfAbsent(traineeId, k -> new ArrayList<>()).add(ptGoal); - } - } - } - - // 2. trainee 테이블 업데이트 - String updateSql = "UPDATE trainee SET pt_goals = ? WHERE id = ?"; - try (PreparedStatement updateStmt = context.getConnection().prepareStatement(updateSql)) { - for (Map.Entry> entry : traineeGoalsMap.entrySet()) { - Long traineeId = entry.getKey(); - List ptGoals = entry.getValue(); - - String jsonGoals = objectMapper.writeValueAsString(ptGoals); - updateStmt.setString(1, jsonGoals); - updateStmt.setLong(2, traineeId); - updateStmt.executeUpdate(); - } - } - } - - private PtGoal mapContentToPtGoal(String content) { - return switch (content.trim()) { - case "근력 향상" -> STRENGTH_ENHANCE; - case "체중 감량" -> WEIGHT_LOSS; - case "건강 관리" -> HEALTH_MANAGE; - case "자세 교정" -> POSTURE_CORRECTION; - case "바디프로필" -> BODY_PROFILE; - case "유연성향상" -> FLEXIBILITY_ENHANCE; - default -> null; - }; - } -} diff --git a/src/main/resources/db/migration/V3__drop_pt_goal_table.sql b/src/main/resources/db/migration/V3__drop_pt_goal_table.sql deleted file mode 100644 index 95d9d478..00000000 --- a/src/main/resources/db/migration/V3__drop_pt_goal_table.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Drop pt_goal table after data migration -DROP TABLE IF EXISTS pt_goal; From 7e4dd54b59848d2096ce96e3b754d503b4593ab3 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 16:12:05 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index dd50a9dc..b825c6b2 100644 --- a/build.gradle +++ b/build.gradle @@ -127,7 +127,10 @@ jacocoTestCoverageVerification { 'scouter.*', 'reactor.*', '*.*RepositoryImpl', - '*.*Converter' + '*.*Converter', + '*.tnt.MigrationRunner', + '*.tnt.PtGoalMigrationService', + '*.tnt.PtGoal' ] } } @@ -215,7 +218,7 @@ dependencies { // 이미지 메타데이터 관련 라이브러리 implementation 'com.drewnoakes:metadata-extractor:2.19.0' implementation 'org.apache.commons:commons-imaging:1.0.0-alpha5' - + // Flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' From 05cfe17714c67c65dedab08f4844aeee922bcb14 Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 16:20:08 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b825c6b2..a6701800 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,10 @@ jacocoTestReport { "**/*Config*", "**/*DynamicQuery*", "**/*error*", - "**/*Repository*" + "**/*Repository*", + "**/MigrationRunner*", + "**/PtGoalMigrationService*", + "**/com/tnt/PtGoal.class" ] + Qdomains) }) ) From c938d48fcb8d0eebea6d70fe68f8fd27eb82624c Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Sat, 25 Oct 2025 16:33:39 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[TNT-304]feat:=20pt=20=EB=AA=A9=EC=A0=81=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 +++++- build.gradle | 10 ++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a87a31f..faef3d1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,11 @@ jobs: run: chmod +x gradlew - name: Build + run: ./gradlew build --info --stacktrace + + - name: SonarCloud Analysis + continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build sonar --info --stacktrace + run: ./gradlew sonar --info --stacktrace diff --git a/build.gradle b/build.gradle index a6701800..e8556c1f 100644 --- a/build.gradle +++ b/build.gradle @@ -86,10 +86,7 @@ jacocoTestReport { "**/*Config*", "**/*DynamicQuery*", "**/*error*", - "**/*Repository*", - "**/MigrationRunner*", - "**/PtGoalMigrationService*", - "**/com/tnt/PtGoal.class" + "**/*Repository*" ] + Qdomains) }) ) @@ -130,10 +127,7 @@ jacocoTestCoverageVerification { 'scouter.*', 'reactor.*', '*.*RepositoryImpl', - '*.*Converter', - '*.tnt.MigrationRunner', - '*.tnt.PtGoalMigrationService', - '*.tnt.PtGoal' + '*.*Converter' ] } }