From 2a5c138443d491215bb31e1b42556dec6e9e6471 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 21 May 2025 21:55:42 +0900 Subject: [PATCH 01/12] =?UTF-8?q?refactor=20:=20#43=20TrainerProfileMapper?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/mapper/TrainerProfileMapper.java | 28 +++++++++++++++++ .../TrainerProfileRepositoryImpl.java | 30 ++++++++----------- 2 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java diff --git a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java new file mode 100644 index 00000000..9f3ed299 --- /dev/null +++ b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java @@ -0,0 +1,28 @@ +package com.demo.pteam.trainer.profile.mapper; + + +import com.demo.pteam.authentication.repository.entity.AccountEntity; +import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; +import com.demo.pteam.trainer.profile.domain.TrainerProfile; +import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; + +public class TrainerProfileMapper { + + // 프로필 도메인 -> 프로필 엔티티 변환 + public static TrainerProfileEntity toEntity( + TrainerProfile profile, + AccountEntity trainer, + TrainerAddressEntity address) { + + return TrainerProfileEntity.builder() + .trainer(trainer) + .address(address) + .profileImg(profile.getProfileImg()) + .intro(profile.getIntro()) + .credit(profile.getCredit()) + .contactStartTime(profile.getContactStartTime()) + .contactEndTime(profile.getContactEndTime()) + .isNamePublic(profile.getIsNamePublic()) + .build(); + } +} diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java index b10e7d1d..c08c47af 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java @@ -3,35 +3,29 @@ import com.demo.pteam.authentication.repository.entity.AccountEntity; import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; import com.demo.pteam.trainer.profile.domain.TrainerProfile; +import com.demo.pteam.trainer.profile.mapper.TrainerProfileMapper; import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.Optional; + @RequiredArgsConstructor @Repository public class TrainerProfileRepositoryImpl implements TrainerProfileRepository { - private final TrainerProfileJPARepository trainerProfileJPARepository; - private final EntityManager em; + private final TrainerProfileJPARepository trainerProfileJPARepository; + private final EntityManager em; - @Override - public void save(TrainerProfile profile) { + @Override + public void save(TrainerProfile profile) { - AccountEntity trainer = em.getReference(AccountEntity.class, profile.getUserId()); - TrainerAddressEntity address = em.getReference(TrainerAddressEntity.class, profile.getAddressId()); + AccountEntity trainer = em.getReference(AccountEntity.class, profile.getUserId()); + TrainerAddressEntity address = em.getReference(TrainerAddressEntity.class, profile.getAddressId()); - TrainerProfileEntity entity = TrainerProfileEntity.builder() - .trainer(trainer) - .address(address) - .profileImg(profile.getProfileImg()) - .intro(profile.getIntro()) - .credit(profile.getCredit()) - .contactStartTime(profile.getContactStartTime()) - .contactEndTime(profile.getContactEndTime()) - .isNamePublic(profile.getIsNamePublic()) - .build(); + TrainerProfileEntity entity = TrainerProfileMapper.toEntity(profile, trainer, address); + trainerProfileJPARepository.save(entity); + } - trainerProfileJPARepository.save(entity); - } } \ No newline at end of file From 0dc4e00372079c7c35c77e6bdb82cf197299ca05 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:11:56 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat=20:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20DTO=20=EC=9E=91=EC=84=B1=20(=EC=9E=90=EA=B2=A9?= =?UTF-8?q?=EC=A6=9D,=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=20X)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/TrainerProfileResponse.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java index fa6be623..ce9ec108 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java +++ b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java @@ -1,5 +1,39 @@ package com.demo.pteam.trainer.profile.controller.dto; +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class TrainerProfileResponse { + private Long profileId; + private String displayName; + private String intro; + private Integer credit; + private LocalTime contactStartTime; + private LocalTime contactEndTime; + private String profileImg; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + private Address address; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Address { + private String roadAddress; + private String detailAddress; + private String postalCode; + private BigDecimal latitude; + private BigDecimal longitude; + } } + From 1f6c218bb104839550f7d59276b60fbb4ebcaba2 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:14:39 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat=20:=20#43=20JPAQueryFactory=20bean?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 ++++++- src/main/java/com/demo/pteam/global/config/JpaConfig.java | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index ff85221a..3a562fbe 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,12 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + //MapStruct implementation 'org.mapstruct:mapstruct:1.6.3' annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' @@ -76,5 +82,4 @@ dependencies { tasks.named('test') { useJUnitPlatform() - jvmArgs "-javaagent:${configurations.testRuntimeClasspath.find { it.name.contains('byte-buddy-agent') }.absolutePath}" } diff --git a/src/main/java/com/demo/pteam/global/config/JpaConfig.java b/src/main/java/com/demo/pteam/global/config/JpaConfig.java index 73b3d94f..c89d2f70 100644 --- a/src/main/java/com/demo/pteam/global/config/JpaConfig.java +++ b/src/main/java/com/demo/pteam/global/config/JpaConfig.java @@ -7,8 +7,8 @@ @Configuration public class JpaConfig { - @Bean - public JPAQueryFactory jpaQueryFactory(EntityManager em) { - return new JPAQueryFactory(em); - } + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager em) { + return new JPAQueryFactory(em); + } } From 611b75974bb7789a3030ba344d4e7504aff181f0 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:16:06 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat=20:=20#43=20=EC=A3=BC=EC=86=8C,=20?= =?UTF-8?q?=ED=8A=B8=EB=A0=88=EC=9D=B4=EB=84=88=20ErrorCode=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 --- .../trainer/address/exception/TrainerAddressErrorCode.java | 3 ++- .../trainer/profile/exception/TrainerProfileErrorCode.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/address/exception/TrainerAddressErrorCode.java b/src/main/java/com/demo/pteam/trainer/address/exception/TrainerAddressErrorCode.java index 416402e1..9a2dec81 100644 --- a/src/main/java/com/demo/pteam/trainer/address/exception/TrainerAddressErrorCode.java +++ b/src/main/java/com/demo/pteam/trainer/address/exception/TrainerAddressErrorCode.java @@ -13,7 +13,8 @@ public enum TrainerAddressErrorCode implements ErrorCode { INVALID_LONGITUDE(HttpStatus.BAD_REQUEST, "L_003", "경도 값은 -180 ~ 180 사이여야 합니다."), ADDRESS_COORDINATE_MISMATCH(HttpStatus.BAD_REQUEST, "L_004", "위도/경도와 도로명 주소가 일치하지 않습니다."), KAKAO_API_EMPTY_RESPONSE(HttpStatus.BAD_GATEWAY, "L_005", "카카오 지도 API 응답이 비어있습니다."), - ROAD_ADDRESS_NOT_FOUND(HttpStatus.BAD_REQUEST, "L_006", "도로명 주소를 찾을 수 없습니다."); + ROAD_ADDRESS_NOT_FOUND(HttpStatus.BAD_REQUEST, "L_006", "도로명 주소를 찾을 수 없습니다."), + ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "L_007", "등록되어 있는 트레이너 주소가 없습니다.");; private final HttpStatus status; private final String code; diff --git a/src/main/java/com/demo/pteam/trainer/profile/exception/TrainerProfileErrorCode.java b/src/main/java/com/demo/pteam/trainer/profile/exception/TrainerProfileErrorCode.java index fc1608d8..d420cb35 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/exception/TrainerProfileErrorCode.java +++ b/src/main/java/com/demo/pteam/trainer/profile/exception/TrainerProfileErrorCode.java @@ -10,7 +10,8 @@ public enum TrainerProfileErrorCode implements ErrorCode { PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "P_001", "존재하지 않는 트레이너 프로필입니다."), PROFILE_INCOMPLETE(HttpStatus.BAD_REQUEST, "P_002", "User ID와 이름 공개 여부(isNamePublic)는 필수입니다."), INVALID_CONTACT_TIME_PAIR(HttpStatus.BAD_REQUEST, "P_003", "연락 시작 시간과 종료 시간은 모두 입력하거나 모두 생략해야 합니다."), - INVALID_CONTACT_TIME_RANGE(HttpStatus.BAD_REQUEST, "P_004", "연락 시작 시간이 종료 시간보다 늦을 수 없습니다."); + INVALID_CONTACT_TIME_RANGE(HttpStatus.BAD_REQUEST, "P_004", "연락 시작 시간이 종료 시간보다 늦을 수 없습니다."), + TRAINER_ROLE_REQUIRED(HttpStatus.FORBIDDEN, "P_005", "트레이너 권한이 필요합니다."); private final HttpStatus status; private final String code; From 4f9841dbef25f35e0a694052cc2b88aa2f347fae Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:18:55 +0900 Subject: [PATCH 05/12] =?UTF-8?q?chore=20:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=EC=9D=98=20use?= =?UTF-8?q?r=5Fid=20Unique=20=EC=A0=9C=EC=95=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V202505241925__add_trainer_user_unique_constraint.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/db/migration/V202505241925__add_trainer_user_unique_constraint.sql diff --git a/src/main/resources/db/migration/V202505241925__add_trainer_user_unique_constraint.sql b/src/main/resources/db/migration/V202505241925__add_trainer_user_unique_constraint.sql new file mode 100644 index 00000000..8a994a19 --- /dev/null +++ b/src/main/resources/db/migration/V202505241925__add_trainer_user_unique_constraint.sql @@ -0,0 +1 @@ +ALTER TABLE trainer_profile ADD CONSTRAINT uq_trainer_user UNIQUE (user_id); \ No newline at end of file From 838af1e09b43140ef77fb368226dde5b0668a1d8 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:19:17 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor=20:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8,=20=EC=97=94=ED=8B=B0=ED=8B=B0=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 --- .../profile/domain/TrainerProfile.java | 19 +++++++++++-------- .../entity/TrainerProfileEntity.java | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java index e93d89f3..eb4c6aee 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java +++ b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java @@ -1,9 +1,6 @@ package com.demo.pteam.trainer.profile.domain; -import com.demo.pteam.global.exception.ApiException; -import com.demo.pteam.trainer.profile.exception.TrainerProfileErrorCode; import lombok.Getter; - import java.time.LocalTime; @Getter @@ -11,6 +8,8 @@ public class TrainerProfile { private final Long id; private final Long userId; + private final String name; + private final String nickname; private final Long addressId; private final String profileImg; private final String intro; @@ -19,10 +18,13 @@ public class TrainerProfile { private final LocalTime contactEndTime; private final Boolean isNamePublic; - public TrainerProfile(Long id, Long userId, Long addressId, String profileImg, String intro, Integer credit, + public TrainerProfile(Long id, Long userId, String name, String nickname, Long addressId, + String profileImg, String intro, Integer credit, LocalTime contactStartTime, LocalTime contactEndTime, Boolean isNamePublic) { this.id = id; this.userId = userId; + this.name = name; + this.nickname = nickname; this.addressId = addressId; this.profileImg = profileImg; this.intro = intro; @@ -32,14 +34,15 @@ public TrainerProfile(Long id, Long userId, Long addressId, String profileImg, S this.isNamePublic = isNamePublic; } - public static TrainerProfile of(Long userId, Long addressId, String profileImg, String intro, Integer credit, + public static TrainerProfile of(Long userId, String name, String nickname, Long addressId, String profileImg, + String intro, Integer credit, LocalTime contactStartTime, LocalTime contactEndTime, Boolean isNamePublic) { - return new TrainerProfile(null, userId, addressId, profileImg, intro, credit, + return new TrainerProfile(null, userId, name, nickname, addressId, profileImg, intro, credit, contactStartTime, contactEndTime, isNamePublic); } - public boolean isNameVisible() { - return this.isNamePublic; + public String getDisplayName() { + return isNamePublic ? name : nickname; } public boolean isContactTimePairValid() { diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/entity/TrainerProfileEntity.java b/src/main/java/com/demo/pteam/trainer/profile/repository/entity/TrainerProfileEntity.java index 1c38ec08..a5427e38 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/repository/entity/TrainerProfileEntity.java +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/entity/TrainerProfileEntity.java @@ -22,7 +22,7 @@ public class TrainerProfileEntity extends SoftDeletableEntity { private Long id; @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) + @JoinColumn(name = "user_id", nullable = false, unique = true) private AccountEntity trainer; @OneToOne(fetch = FetchType.LAZY) From 4d5e8082cf8489e33f487892f4e20d76b3aa0123 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:21:04 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat=20:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=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 --- .../repository/TrainerAddressRepository.java | 1 + .../TrainerAddressRepositoryImpl.java | 15 +++++++ .../controller/TrainerProfileController.java | 42 +++++++++++------ .../profile/mapper/TrainerProfileMapper.java | 45 ++++++++++++++++++- .../TrainerProfileJPARepository.java | 2 +- .../TrainerProfileJPARepositoryCustom.java | 9 ++++ ...TrainerProfileJPARepositoryCustomImpl.java | 34 ++++++++++++++ .../repository/TrainerProfileRepository.java | 4 ++ .../TrainerProfileRepositoryImpl.java | 5 +++ .../service/TrainerProfileService.java | 36 ++++++++++++++- 10 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustom.java create mode 100644 src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustomImpl.java diff --git a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepository.java b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepository.java index e08013fd..8c3f639a 100644 --- a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepository.java +++ b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepository.java @@ -5,4 +5,5 @@ public interface TrainerAddressRepository { TrainerAddress save(TrainerAddress address); + Optional findById(Long addressId); } \ No newline at end of file diff --git a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java index 8cb3502a..293c548a 100644 --- a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java +++ b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java @@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository @RequiredArgsConstructor public class TrainerAddressRepositoryImpl implements TrainerAddressRepository { @@ -40,4 +42,17 @@ public TrainerAddress save(TrainerAddress address) { ); } + @Override + public Optional findById(Long addressId) { + return jpaRepository.findById(addressId) + .map(entity -> new TrainerAddress( + entity.getId(), + entity.getNumberAddress(), + entity.getRoadAddress(), + entity.getDetailAddress(), + entity.getPostalCode(), + new Coordinates(entity.getLatitude(), entity.getLongitude()) + )); + } + } \ No newline at end of file diff --git a/src/main/java/com/demo/pteam/trainer/profile/controller/TrainerProfileController.java b/src/main/java/com/demo/pteam/trainer/profile/controller/TrainerProfileController.java index 6e4335b3..6018127e 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/controller/TrainerProfileController.java +++ b/src/main/java/com/demo/pteam/trainer/profile/controller/TrainerProfileController.java @@ -2,29 +2,45 @@ import com.demo.pteam.global.response.ApiResponse; import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileRequest; +import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileResponse; import com.demo.pteam.trainer.profile.service.TrainerProfileService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -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.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api/trainers/me/profile") public class TrainerProfileController { - private final TrainerProfileService trainerProfileService; + private final TrainerProfileService trainerProfileService; - @PostMapping - public ResponseEntity> createProfile( - @RequestBody @Valid TrainerProfileRequest request - ) { - Long userId = 4L; // TODO: 로그인 사용자 임시 + /** + * 트레이너 프로필 등록 API + * @param request 트레이너 프로필 요청 DTO + * @return 등록 성공 여부 + */ + @PostMapping + public ResponseEntity> createProfile( + @RequestBody @Valid TrainerProfileRequest request + ) { + Long userId = 4L; // TODO: 로그인 사용자 임시 + + trainerProfileService.createProfile(request, userId); + return ResponseEntity.status(201).body(ApiResponse.created("트레이너 프로필이 성공적으로 등록되었습니다.")); + } + + /** + * 트레이너 프로필 조회 API (사용자 본인) + * @return 트레이너 프로필 응답 DTO + */ + @GetMapping + public ResponseEntity> getProfile() { + Long userId = 4L; // TODO: 로그인 사용자 임시 + + TrainerProfileResponse response = trainerProfileService.getProfile(userId); + return ResponseEntity.ok(ApiResponse.success(response)); + } - trainerProfileService.createProfile(request, userId); - return ResponseEntity.status(201).body(ApiResponse.created("트레이너 프로필이 성공적으로 등록되었습니다.")); - } } \ No newline at end of file diff --git a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java index 9f3ed299..a41a5e2b 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java +++ b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java @@ -2,13 +2,17 @@ import com.demo.pteam.authentication.repository.entity.AccountEntity; +import com.demo.pteam.trainer.address.domain.TrainerAddress; import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; +import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileResponse; import com.demo.pteam.trainer.profile.domain.TrainerProfile; import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; +import java.time.LocalDateTime; + public class TrainerProfileMapper { - // 프로필 도메인 -> 프로필 엔티티 변환 + // 프로필 도메인 -> 프로필 엔티티 public static TrainerProfileEntity toEntity( TrainerProfile profile, AccountEntity trainer, @@ -25,4 +29,43 @@ public static TrainerProfileEntity toEntity( .isNamePublic(profile.getIsNamePublic()) .build(); } + + // 프로필 엔티티 -> 프로필 도메인 + public static TrainerProfile toDomain(TrainerProfileEntity entity) { + return new TrainerProfile( + entity.getId(), + entity.getTrainer().getId(), + entity.getTrainer().getName(), + entity.getTrainer().getNickname(), + entity.getAddress().getId(), + entity.getProfileImg(), + entity.getIntro(), + entity.getCredit(), + entity.getContactStartTime(), + entity.getContactEndTime(), + entity.getIsNamePublic() + ); + } + + // 프로필 도메인 -> 프로필 응답 DTO + public static TrainerProfileResponse toResponse(TrainerProfile profile, TrainerAddress address, LocalDateTime createdAt, LocalDateTime updatedAt) { + return TrainerProfileResponse.builder() + .profileId(profile.getId()) + .displayName(profile.getDisplayName()) + .intro(profile.getIntro()) + .credit(profile.getCredit()) + .contactStartTime(profile.getContactStartTime()) + .contactEndTime(profile.getContactEndTime()) + .profileImg(profile.getProfileImg()) + .createdAt(createdAt) + .updatedAt(updatedAt) + .address(TrainerProfileResponse.Address.builder() + .roadAddress(address.getRoadAddress()) + .detailAddress(address.getDetailAddress()) + .postalCode(address.getPostalCode()) + .latitude(address.getCoordinates().getLatitude()) + .longitude(address.getCoordinates().getLongitude()) + .build()) + .build(); + } } diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepository.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepository.java index 803f92b7..982db1b1 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepository.java +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepository.java @@ -3,5 +3,5 @@ import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface TrainerProfileJPARepository extends JpaRepository { +public interface TrainerProfileJPARepository extends JpaRepository, TrainerProfileJPARepositoryCustom { } diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustom.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustom.java new file mode 100644 index 00000000..08506074 --- /dev/null +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustom.java @@ -0,0 +1,9 @@ +package com.demo.pteam.trainer.profile.repository; + +import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; + +import java.util.Optional; + +public interface TrainerProfileJPARepositoryCustom { + Optional findDetailedProfileEntityByUserId(Long userId); +} diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustomImpl.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustomImpl.java new file mode 100644 index 00000000..36ae8193 --- /dev/null +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileJPARepositoryCustomImpl.java @@ -0,0 +1,34 @@ +package com.demo.pteam.trainer.profile.repository; + +import com.demo.pteam.authentication.repository.entity.QAccountEntity; +import com.demo.pteam.trainer.address.repository.entity.QTrainerAddressEntity; +import com.demo.pteam.trainer.profile.repository.entity.QTrainerProfileEntity; +import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class TrainerProfileJPARepositoryCustomImpl implements TrainerProfileJPARepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public Optional findDetailedProfileEntityByUserId(Long userId) { + QTrainerProfileEntity profile = QTrainerProfileEntity.trainerProfileEntity; + QTrainerAddressEntity address = QTrainerAddressEntity.trainerAddressEntity; + QAccountEntity account = QAccountEntity.accountEntity; + + return Optional.ofNullable( + queryFactory + .select(profile) + .from(profile) + .leftJoin(profile.address, address).fetchJoin() + .leftJoin(profile.trainer, account).fetchJoin() + .where(profile.trainer.id.eq(userId)) + .fetchOne() + ); + } +} diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepository.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepository.java index ed65e752..34931e63 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepository.java +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepository.java @@ -1,7 +1,11 @@ package com.demo.pteam.trainer.profile.repository; import com.demo.pteam.trainer.profile.domain.TrainerProfile; +import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; + +import java.util.Optional; public interface TrainerProfileRepository { void save(TrainerProfile trainerProfile); + Optional findEntityByUserId(Long userId); } diff --git a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java index c08c47af..46b34ec4 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java +++ b/src/main/java/com/demo/pteam/trainer/profile/repository/TrainerProfileRepositoryImpl.java @@ -28,4 +28,9 @@ public void save(TrainerProfile profile) { trainerProfileJPARepository.save(entity); } + @Override + public Optional findEntityByUserId(Long userId) { + return trainerProfileJPARepository.findDetailedProfileEntityByUserId(userId); + } + } \ No newline at end of file diff --git a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java index a597d894..ba3ec6cc 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java +++ b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java @@ -8,23 +8,34 @@ import com.demo.pteam.trainer.address.mapper.TrainerAddressMapper; import com.demo.pteam.trainer.address.repository.TrainerAddressRepository; import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileRequest; +import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileResponse; import com.demo.pteam.trainer.profile.domain.TrainerProfile; import com.demo.pteam.trainer.profile.exception.TrainerProfileErrorCode; +import com.demo.pteam.trainer.profile.mapper.TrainerProfileMapper; import com.demo.pteam.trainer.profile.repository.TrainerProfileRepository; +import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor +@Transactional public class TrainerProfileService { private final TrainerProfileRepository trainerProfileRepository; private final TrainerAddressRepository trainerAddressRepository; private final KakaoMapService kakaoMapService; - @Transactional + /** + * 트레이너 프로필 등록 + * @param request 트레이너 프로필 DTO + * @param userId 로그인 사용자 id + */ public void createProfile(TrainerProfileRequest request, Long userId) { + // TODO: '회원'이 아닌 '트레이너' 확인 여부 TrainerAddress newAddress = TrainerAddressMapper.toDomain(request.getAddress()); @@ -50,8 +61,11 @@ public void createProfile(TrainerProfileRequest request, Long userId) { TrainerAddress savedAddress = trainerAddressRepository.save(newAddress); + // name, nickname 임시 TrainerProfile profile = TrainerProfile.of( userId, + null, + null, savedAddress.getId(), request.getProfileImg(), request.getIntro(), @@ -76,4 +90,24 @@ public void createProfile(TrainerProfileRequest request, Long userId) { trainerProfileRepository.save(profile); } + /** + * 트레이너 프로필 조회 API (사용자 본인) + * @return 트레이너 프로필 응답 DTO + */ + @Transactional(readOnly = true) + public TrainerProfileResponse getProfile(Long userId) { + // TODO: '회원'이 아닌 '트레이너' 확인 여부 + + // 로그인한 사용자의 프로필이 있는지 여부 + TrainerProfileEntity entity = trainerProfileRepository.findEntityByUserId(userId) + .orElseThrow(() -> new ApiException(TrainerProfileErrorCode.PROFILE_NOT_FOUND)); + + TrainerProfile profile = TrainerProfileMapper.toDomain(entity); + + TrainerAddress address = trainerAddressRepository.findById(profile.getAddressId()) + .orElseThrow(() -> new ApiException(TrainerAddressErrorCode.ADDRESS_NOT_FOUND)); + + return TrainerProfileMapper.toResponse(profile, address, entity.getCreatedAt(), entity.getUpdatedAt()); + } + } From 53db3b121dee75ce64a0bf35a326467712f10680 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sat, 24 May 2025 21:23:40 +0900 Subject: [PATCH 08/12] =?UTF-8?q?chore=20:=20#43=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/demo/pteam/trainer/address/domain/TrainerAddress.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java index 8e8e9a79..dd9031ab 100644 --- a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java +++ b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java @@ -1,7 +1,5 @@ package com.demo.pteam.trainer.address.domain; -import com.demo.pteam.global.exception.ApiException; -import com.demo.pteam.trainer.address.exception.TrainerAddressErrorCode; import lombok.Getter; @Getter From 33472d7ead3258557ae3b981b22774fa1c686f3e Mon Sep 17 00:00:00 2001 From: seongminQa Date: Sun, 25 May 2025 21:28:00 +0900 Subject: [PATCH 09/12] =?UTF-8?q?chore=20:=20#43=20jackson-datatype-jsr310?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20ObjectMapper=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95=20+?= =?UTF-8?q?=20("/api/trainers/**"=20=EC=9E=84=EC=8B=9C=20=ED=97=88?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 +++------ .../com/demo/pteam/global/config/ApplicationConfig.java | 6 +++++- .../com/demo/pteam/security/config/SecurityConfig.java | 1 + .../profile/controller/dto/TrainerProfileRequest.java | 4 ++++ .../profile/controller/dto/TrainerProfileResponse.java | 9 +++++++++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 3a562fbe..fbcfb71d 100644 --- a/build.gradle +++ b/build.gradle @@ -48,12 +48,6 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - // QueryDSL - implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" - //MapStruct implementation 'org.mapstruct:mapstruct:1.6.3' annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' @@ -69,6 +63,9 @@ dependencies { annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // 날짜/시간 타입(JSON 직렬화 지원) + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' diff --git a/src/main/java/com/demo/pteam/global/config/ApplicationConfig.java b/src/main/java/com/demo/pteam/global/config/ApplicationConfig.java index 93e06297..ed9ac1b0 100644 --- a/src/main/java/com/demo/pteam/global/config/ApplicationConfig.java +++ b/src/main/java/com/demo/pteam/global/config/ApplicationConfig.java @@ -1,6 +1,8 @@ package com.demo.pteam.global.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -8,6 +10,8 @@ public class ApplicationConfig { @Bean public ObjectMapper objectMapper() { - return new ObjectMapper(); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper; } } diff --git a/src/main/java/com/demo/pteam/security/config/SecurityConfig.java b/src/main/java/com/demo/pteam/security/config/SecurityConfig.java index bd0deff2..8831020e 100644 --- a/src/main/java/com/demo/pteam/security/config/SecurityConfig.java +++ b/src/main/java/com/demo/pteam/security/config/SecurityConfig.java @@ -46,6 +46,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auths/login").permitAll() + .requestMatchers("/api/trainers/**").permitAll() // 임시 .anyRequest().authenticated()); return http.build(); diff --git a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileRequest.java b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileRequest.java index e4c0f696..f805183b 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileRequest.java +++ b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileRequest.java @@ -1,5 +1,6 @@ package com.demo.pteam.trainer.profile.controller.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -23,7 +24,10 @@ public class TrainerProfileRequest { @PositiveOrZero(message = "크레딧은 0 이상이어야 합니다.") private Integer credit; + @JsonFormat(pattern = "HH:mm") private LocalTime contactStartTime; + + @JsonFormat(pattern = "HH:mm") private LocalTime contactEndTime; @NotNull(message = "이름 공개 선택 여부는 필수입니다.") diff --git a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java index ce9ec108..07555bab 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java +++ b/src/main/java/com/demo/pteam/trainer/profile/controller/dto/TrainerProfileResponse.java @@ -1,5 +1,6 @@ package com.demo.pteam.trainer.profile.controller.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; import java.math.BigDecimal; @@ -15,10 +16,18 @@ public class TrainerProfileResponse { private String displayName; private String intro; private Integer credit; + + @JsonFormat(pattern = "HH:mm") private LocalTime contactStartTime; + + @JsonFormat(pattern = "HH:mm") private LocalTime contactEndTime; private String profileImg; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime updatedAt; private Address address; From 0e789ea7a2864ead999eedc09285fed995a88341 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Mon, 26 May 2025 13:40:04 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=A3=BC=EC=86=8C,=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=98=20=EC=A0=95=EC=A0=81=20=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=98=EA=B3=A0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainer/address/domain/TrainerAddress.java | 4 ---- .../address/mapper/TrainerAddressMapper.java | 6 +++++- .../trainer/profile/domain/TrainerProfile.java | 7 ------- .../profile/mapper/TrainerProfileMapper.java | 18 ++++++++++++++++++ .../profile/service/TrainerProfileService.java | 13 +------------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java index dd9031ab..0d90cbe1 100644 --- a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java +++ b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java @@ -22,10 +22,6 @@ public TrainerAddress(Long id, String numberAddress, String roadAddress, String this.coordinates = coordinates; } - public static TrainerAddress from(String roadAddress, String detailAddress, Coordinates coordinates) { - return new TrainerAddress(null, null, roadAddress, detailAddress, null, coordinates); - } - public void completeAddress(String numberAddress, String postalCode) { this.numberAddress = numberAddress; this.postalCode = postalCode; diff --git a/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java b/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java index 18978aec..3b303b6a 100644 --- a/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java +++ b/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java @@ -9,9 +9,13 @@ public class TrainerAddressMapper { // 요청 DTO -> 도메인 변환 public static TrainerAddress toDomain(TrainerProfileRequest.Address dto) { Coordinates coordinates = new Coordinates(dto.getLatitude(), dto.getLongitude()); - return TrainerAddress.from( + + return new TrainerAddress( + null, + null, dto.getRoadAddress(), dto.getDetailAddress(), + null, coordinates ); } diff --git a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java index eb4c6aee..cb2893c3 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java +++ b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java @@ -34,13 +34,6 @@ public TrainerProfile(Long id, Long userId, String name, String nickname, Long a this.isNamePublic = isNamePublic; } - public static TrainerProfile of(Long userId, String name, String nickname, Long addressId, String profileImg, - String intro, Integer credit, - LocalTime contactStartTime, LocalTime contactEndTime, Boolean isNamePublic) { - return new TrainerProfile(null, userId, name, nickname, addressId, profileImg, intro, credit, - contactStartTime, contactEndTime, isNamePublic); - } - public String getDisplayName() { return isNamePublic ? name : nickname; } diff --git a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java index a41a5e2b..da2892a0 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java +++ b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java @@ -4,6 +4,7 @@ import com.demo.pteam.authentication.repository.entity.AccountEntity; import com.demo.pteam.trainer.address.domain.TrainerAddress; import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; +import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileRequest; import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileResponse; import com.demo.pteam.trainer.profile.domain.TrainerProfile; import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; @@ -68,4 +69,21 @@ public static TrainerProfileResponse toResponse(TrainerProfile profile, TrainerA .build()) .build(); } + + // 요청 DTO -> 도메인 + public static TrainerProfile toDomain(TrainerProfileRequest dto, Long userId, Long addressId) { + return new TrainerProfile( + null, + userId, + null, + null, + addressId, + dto.getProfileImg(), + dto.getIntro(), + dto.getCredit(), + dto.getContactStartTime(), + dto.getContactEndTime(), + dto.getIsNamePublic() + ); + } } diff --git a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java index ba3ec6cc..20eba9b8 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java +++ b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java @@ -62,18 +62,7 @@ public void createProfile(TrainerProfileRequest request, Long userId) { TrainerAddress savedAddress = trainerAddressRepository.save(newAddress); // name, nickname 임시 - TrainerProfile profile = TrainerProfile.of( - userId, - null, - null, - savedAddress.getId(), - request.getProfileImg(), - request.getIntro(), - request.getCredit(), - request.getContactStartTime(), - request.getContactEndTime(), - request.getIsNamePublic() - ); + TrainerProfile profile = TrainerProfileMapper.toDomain(request, userId, savedAddress.getId()); if (!profile.isProfileComplete()) { throw new ApiException(TrainerProfileErrorCode.PROFILE_INCOMPLETE); From b9bee1d261719b62f31ba4d3b9f7850f5206b0a3 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Mon, 26 May 2025 16:18:43 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor=20:=20#43=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=A3=BC=EC=86=8C,=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainer/address/domain/Coordinates.java | 26 +++++------ .../address/domain/TrainerAddress.java | 19 +++++--- .../profile/domain/TrainerProfile.java | 18 +++----- .../profile/mapper/TrainerProfileMapper.java | 2 +- .../service/TrainerProfileService.java | 44 ++++++++++++++----- 5 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/address/domain/Coordinates.java b/src/main/java/com/demo/pteam/trainer/address/domain/Coordinates.java index 67cc679a..a9cb9272 100644 --- a/src/main/java/com/demo/pteam/trainer/address/domain/Coordinates.java +++ b/src/main/java/com/demo/pteam/trainer/address/domain/Coordinates.java @@ -1,9 +1,6 @@ package com.demo.pteam.trainer.address.domain; -import com.demo.pteam.global.exception.ApiException; -import com.demo.pteam.trainer.address.exception.TrainerAddressErrorCode; import lombok.Getter; - import java.math.BigDecimal; @Getter @@ -12,17 +9,20 @@ public class Coordinates { private final BigDecimal longitude; public Coordinates(BigDecimal latitude, BigDecimal longitude) { - if (latitude == null || longitude == null) { - throw new ApiException(TrainerAddressErrorCode.COORDINATES_NULL); - } - if (latitude.abs().compareTo(BigDecimal.valueOf(90)) > 0) { - throw new ApiException(TrainerAddressErrorCode.INVALID_LATITUDE); - } - if (longitude.abs().compareTo(BigDecimal.valueOf(180)) > 0) { - throw new ApiException(TrainerAddressErrorCode.INVALID_LONGITUDE); - } - this.latitude = latitude; this.longitude = longitude; } + + public boolean isNull() { + return latitude == null || longitude == null; + } + + public boolean isInvalidLatitude() { + return latitude != null && latitude.abs().compareTo(BigDecimal.valueOf(90)) > 0; + } + + public boolean isInvalidLongitude() { + return longitude != null && longitude.abs().compareTo(BigDecimal.valueOf(180)) > 0; + } + } diff --git a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java index 0d90cbe1..8623449c 100644 --- a/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java +++ b/src/main/java/com/demo/pteam/trainer/address/domain/TrainerAddress.java @@ -4,13 +4,12 @@ @Getter public class TrainerAddress { - private final Long id; - private String numberAddress; + private final String numberAddress; private final String roadAddress; private final String detailAddress; - private String postalCode; - private Coordinates coordinates; + private final String postalCode; + private final Coordinates coordinates; public TrainerAddress(Long id, String numberAddress, String roadAddress, String detailAddress, String postalCode, Coordinates coordinates) { @@ -22,9 +21,15 @@ public TrainerAddress(Long id, String numberAddress, String roadAddress, String this.coordinates = coordinates; } - public void completeAddress(String numberAddress, String postalCode) { - this.numberAddress = numberAddress; - this.postalCode = postalCode; + public TrainerAddress withCompletedAddress(String numberAddress, String postalCode) { + return new TrainerAddress( + this.id, + numberAddress, + this.roadAddress, + this.detailAddress, + postalCode, + this.coordinates + ); } public boolean matchesRoadAddress(String kakaoRoadAddress) { diff --git a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java index cb2893c3..761ebb75 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java +++ b/src/main/java/com/demo/pteam/trainer/profile/domain/TrainerProfile.java @@ -38,21 +38,13 @@ public String getDisplayName() { return isNamePublic ? name : nickname; } - public boolean isContactTimePairValid() { - return (contactStartTime == null && contactEndTime == null) || - (contactStartTime != null && contactEndTime != null); + public boolean isInvalidContactTimePair() { + return !(contactStartTime == null && contactEndTime == null) && + !(contactStartTime != null && contactEndTime != null); } - public boolean hasContactTime() { - return contactStartTime != null && contactEndTime != null; - } - - public boolean isValidContatTimeRange() { - return hasContactTime() && !contactStartTime.isAfter(contactEndTime); - } - - public boolean isProfileComplete() { - return userId != null && isNamePublic != null; + public boolean isInvalidContactTimeRange() { + return contactStartTime != null && contactEndTime != null && contactStartTime.isAfter(contactEndTime); } } diff --git a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java index da2892a0..8a77fd08 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java +++ b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java @@ -70,7 +70,7 @@ public static TrainerProfileResponse toResponse(TrainerProfile profile, TrainerA .build(); } - // 요청 DTO -> 도메인 + // 프로필 요청 DTO -> 프로필 도메인 public static TrainerProfile toDomain(TrainerProfileRequest dto, Long userId, Long addressId) { return new TrainerProfile( null, diff --git a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java index 20eba9b8..2a3c36de 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java +++ b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java @@ -3,6 +3,7 @@ import com.demo.pteam.external.kakao.dto.KakaoGeoResponse; import com.demo.pteam.external.kakao.service.KakaoMapService; import com.demo.pteam.global.exception.ApiException; +import com.demo.pteam.trainer.address.domain.Coordinates; import com.demo.pteam.trainer.address.domain.TrainerAddress; import com.demo.pteam.trainer.address.exception.TrainerAddressErrorCode; import com.demo.pteam.trainer.address.mapper.TrainerAddressMapper; @@ -15,14 +16,14 @@ import com.demo.pteam.trainer.profile.repository.TrainerProfileRepository; import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor @Transactional +@Slf4j public class TrainerProfileService { private final TrainerProfileRepository trainerProfileRepository; @@ -36,9 +37,33 @@ public class TrainerProfileService { */ public void createProfile(TrainerProfileRequest request, Long userId) { // TODO: '회원'이 아닌 '트레이너' 확인 여부 + log.info("📌 profileImg = {}", request.getProfileImg()); + log.info("📌 intro = {}", request.getIntro()); + log.info("📌 credit = {}", request.getCredit()); + log.info("📌 contactStartTime = {}", request.getContactStartTime()); + log.info("📌 contactEndTime = {}", request.getContactEndTime()); + log.info("📌 isNamePublic = {}", request.getIsNamePublic()); + + TrainerProfileRequest.Address addr = request.getAddress(); + log.info("📍 address.roadAddress = {}", addr.getRoadAddress()); + log.info("📍 address.detailAddress = {}", addr.getDetailAddress()); + log.info("📍 address.latitude = {}", addr.getLatitude()); + log.info("📍 address.longitude = {}", addr.getLongitude()); TrainerAddress newAddress = TrainerAddressMapper.toDomain(request.getAddress()); + Coordinates coordinates = newAddress.getCoordinates(); + + if (coordinates.isNull()) { + throw new ApiException(TrainerAddressErrorCode.COORDINATES_NULL); + } + if (coordinates.isInvalidLatitude()) { + throw new ApiException(TrainerAddressErrorCode.INVALID_LATITUDE); + } + if (coordinates.isInvalidLongitude()) { + throw new ApiException(TrainerAddressErrorCode.INVALID_LONGITUDE); + } + KakaoGeoResponse response = kakaoMapService.requestCoordToAddress( newAddress.getCoordinates().getLatitude(), newAddress.getCoordinates().getLongitude() @@ -54,25 +79,20 @@ public void createProfile(TrainerProfileRequest request, Long userId) { throw new ApiException(TrainerAddressErrorCode.ADDRESS_COORDINATE_MISMATCH); } - newAddress.completeAddress( + TrainerAddress completedAddress = newAddress.withCompletedAddress( document.getAddress().getAddressName(), document.getRoadAddress().getZoneNo() ); - TrainerAddress savedAddress = trainerAddressRepository.save(newAddress); + TrainerAddress savedAddress = trainerAddressRepository.save(completedAddress); - // name, nickname 임시 + // name, nickname 임시 null TrainerProfile profile = TrainerProfileMapper.toDomain(request, userId, savedAddress.getId()); - if (!profile.isProfileComplete()) { - throw new ApiException(TrainerProfileErrorCode.PROFILE_INCOMPLETE); - } - - if (!profile.isContactTimePairValid()) { + if (profile.isInvalidContactTimePair()) { throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_PAIR); } - - if (!profile.isValidContatTimeRange()) { + if (profile.isInvalidContactTimeRange()) { throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_RANGE); } From 2754bf6fb3c4b4e6aa46f6a30200e0952d9a051c Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 03:20:12 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor=20:=20#43=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../address/mapper/TrainerAddressMapper.java | 15 +++++++++++++++ .../repository/TrainerAddressRepositoryImpl.java | 11 ++--------- .../profile/service/TrainerProfileService.java | 15 +-------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java b/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java index 3b303b6a..526eea86 100644 --- a/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java +++ b/src/main/java/com/demo/pteam/trainer/address/mapper/TrainerAddressMapper.java @@ -2,6 +2,7 @@ import com.demo.pteam.trainer.address.domain.Coordinates; import com.demo.pteam.trainer.address.domain.TrainerAddress; +import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileRequest; public class TrainerAddressMapper { @@ -20,4 +21,18 @@ public static TrainerAddress toDomain(TrainerProfileRequest.Address dto) { ); } + // 도메인 -> 엔티티 변환 + public static TrainerAddressEntity toEntity(TrainerAddress address) { + Coordinates coordinates = address.getCoordinates(); + + return TrainerAddressEntity.builder() + .numberAddress(address.getNumberAddress()) + .roadAddress(address.getRoadAddress()) + .detailAddress(address.getDetailAddress()) + .postalCode(address.getPostalCode()) + .latitude(coordinates.getLatitude()) + .longitude(coordinates.getLongitude()) + .build(); + } + } diff --git a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java index 293c548a..30dba25c 100644 --- a/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java +++ b/src/main/java/com/demo/pteam/trainer/address/repository/TrainerAddressRepositoryImpl.java @@ -2,6 +2,7 @@ import com.demo.pteam.trainer.address.domain.Coordinates; import com.demo.pteam.trainer.address.domain.TrainerAddress; +import com.demo.pteam.trainer.address.mapper.TrainerAddressMapper; import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -16,15 +17,7 @@ public class TrainerAddressRepositoryImpl implements TrainerAddressRepository { @Override public TrainerAddress save(TrainerAddress address) { - TrainerAddressEntity entity = TrainerAddressEntity.builder() - .numberAddress(address.getNumberAddress()) - .roadAddress(address.getRoadAddress()) - .detailAddress(address.getDetailAddress()) - .postalCode(address.getPostalCode()) - .latitude(address.getCoordinates().getLatitude()) - .longitude(address.getCoordinates().getLongitude()) - .build(); - + TrainerAddressEntity entity = TrainerAddressMapper.toEntity(address); TrainerAddressEntity saved = jpaRepository.save(entity); Coordinates coordinates = new Coordinates( diff --git a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java index 2a3c36de..936d2c2e 100644 --- a/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java +++ b/src/main/java/com/demo/pteam/trainer/profile/service/TrainerProfileService.java @@ -16,14 +16,12 @@ import com.demo.pteam.trainer.profile.repository.TrainerProfileRepository; import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional -@Slf4j public class TrainerProfileService { private final TrainerProfileRepository trainerProfileRepository; @@ -37,18 +35,6 @@ public class TrainerProfileService { */ public void createProfile(TrainerProfileRequest request, Long userId) { // TODO: '회원'이 아닌 '트레이너' 확인 여부 - log.info("📌 profileImg = {}", request.getProfileImg()); - log.info("📌 intro = {}", request.getIntro()); - log.info("📌 credit = {}", request.getCredit()); - log.info("📌 contactStartTime = {}", request.getContactStartTime()); - log.info("📌 contactEndTime = {}", request.getContactEndTime()); - log.info("📌 isNamePublic = {}", request.getIsNamePublic()); - - TrainerProfileRequest.Address addr = request.getAddress(); - log.info("📍 address.roadAddress = {}", addr.getRoadAddress()); - log.info("📍 address.detailAddress = {}", addr.getDetailAddress()); - log.info("📍 address.latitude = {}", addr.getLatitude()); - log.info("📍 address.longitude = {}", addr.getLongitude()); TrainerAddress newAddress = TrainerAddressMapper.toDomain(request.getAddress()); @@ -92,6 +78,7 @@ public void createProfile(TrainerProfileRequest request, Long userId) { if (profile.isInvalidContactTimePair()) { throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_PAIR); } + if (profile.isInvalidContactTimeRange()) { throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_RANGE); }