From 049d8ccc29a675ea54b21fca3e57176284bfda5d Mon Sep 17 00:00:00 2001 From: seongminQa Date: Mon, 26 May 2025 00:01:40 +0900 Subject: [PATCH 01/13] =?UTF-8?q?chore=20:=20#47=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 | 3 +++ .../java/com/demo/pteam/global/config/ApplicationConfig.java | 5 ++++- .../java/com/demo/pteam/security/config/SecurityConfig.java | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ff85221a..c63d56a4 100644 --- a/build.gradle +++ b/build.gradle @@ -63,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..6fc22fd1 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,7 @@ package com.demo.pteam.global.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -8,6 +9,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(); From 2b9ec82ff12ae1724412274cb262992923220c0c Mon Sep 17 00:00:00 2001 From: seongminQa Date: Mon, 26 May 2025 04:09:00 +0900 Subject: [PATCH 02/13] =?UTF-8?q?refactor=20:=20#47=20`LocalTime`=20?= =?UTF-8?q?=EC=97=AD=EC=A7=81=EB=A0=AC=ED=99=94=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20@JsonFormat=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainer/profile/controller/dto/TrainerProfileRequest.java | 4 ++++ 1 file changed, 4 insertions(+) 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 = "이름 공개 선택 여부는 필수입니다.") From 3256f518254a810dda28dca5d128578f93b94067 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 00:18:08 +0900 Subject: [PATCH 03/13] =?UTF-8?q?refactor=20:=20#47=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=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=20=EB=B0=8F=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/domain/TrainerProfile.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 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..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 @@ -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,31 +34,17 @@ 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, - LocalTime contactStartTime, LocalTime contactEndTime, Boolean isNamePublic) { - return new TrainerProfile(null, userId, addressId, profileImg, intro, credit, - contactStartTime, contactEndTime, isNamePublic); - } - - public boolean isNameVisible() { - return this.isNamePublic; - } - - public boolean isContactTimePairValid() { - return (contactStartTime == null && contactEndTime == null) || - (contactStartTime != null && contactEndTime != null); - } - - public boolean hasContactTime() { - return contactStartTime != null && contactEndTime != null; + public String getDisplayName() { + return isNamePublic ? name : nickname; } - public boolean isValidContatTimeRange() { - return hasContactTime() && !contactStartTime.isAfter(contactEndTime); + public boolean isInvalidContactTimePair() { + return !(contactStartTime == null && contactEndTime == null) && + !(contactStartTime != null && contactEndTime != null); } - public boolean isProfileComplete() { - return userId != null && isNamePublic != null; + public boolean isInvalidContactTimeRange() { + return contactStartTime != null && contactEndTime != null && contactStartTime.isAfter(contactEndTime); } } From 18861863f91149a1ad517ae1848d92fe1e02f704 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:06:58 +0900 Subject: [PATCH 04/13] =?UTF-8?q?refactor=20:=20#47=20=EC=A2=8C=ED=91=9C?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A3=BC=EC=86=8C=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8=20=EC=88=98=EC=A0=95=20-=20Coordinates=20?= =?UTF-8?q?=EB=82=B4=EC=9D=98=20=ED=8A=B8=EB=A0=88=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EC=9C=84=EB=8F=84/=EA=B2=BD=EB=8F=84=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EC=83=81=ED=83=9C=EB=A5=BC=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20TrainerAddress=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20-=20equal?= =?UTF-8?q?s,=20hashCode=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B0=84=20=EB=B9=84=EA=B5=90=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainer/address/domain/Coordinates.java | 40 ++++++++++++------ .../address/domain/TrainerAddress.java | 41 +++++++++++++------ 2 files changed, 57 insertions(+), 24 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..92495f5d 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,10 +1,9 @@ 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; +import java.util.Objects; @Getter public class Coordinates { @@ -12,17 +11,34 @@ 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; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Coordinates that = (Coordinates) o; + + return latitude.compareTo(that.latitude) == 0 && + longitude.compareTo(that.longitude) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(latitude, longitude); + } } 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..99bb62da 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,18 +1,18 @@ 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.util.Objects; + @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) { @@ -24,17 +24,34 @@ 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; + 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) { return this.roadAddress.equals(kakaoRoadAddress); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrainerAddress that = (TrainerAddress) o; + + return Objects.equals(this.roadAddress, that.roadAddress) && + Objects.equals(this.detailAddress, that.detailAddress) && + this.coordinates.equals(that.coordinates); + } + + @Override + public int hashCode() { + return Objects.hash(roadAddress, detailAddress, coordinates); + } } From b635d88874b3e16a5029eddecfead152db83cc17 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:08:23 +0900 Subject: [PATCH 05/13] =?UTF-8?q?refactor=20:=20#47=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=A3=BC=EC=86=8C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?-=20=EC=A3=BC=EC=86=8C=20=EC=A2=8C=ED=91=9C=20=EC=A0=95?= =?UTF-8?q?=ED=95=A9=EC=84=B1,=20=EC=9C=A0=ED=9A=A8=EC=84=B1,=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=8B=A4=ED=8C=A8=20=EB=93=B1=EC=9D=98=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=83=81=ED=83=9C=20=EC=A0=95=EC=9D=98=20(?= =?UTF-8?q?=EC=95=84=EC=A7=81=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EC=83=81=ED=83=9C.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/TrainerAddressErrorCode.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 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..34abb49f 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 @@ -7,22 +7,24 @@ @Getter public enum TrainerAddressErrorCode implements ErrorCode { - // 주소 - COORDINATES_NULL(HttpStatus.BAD_REQUEST, "L_001", "위도, 경도 값은 null일 수 없습니다."), - INVALID_LATITUDE(HttpStatus.BAD_REQUEST, "L_002", "위도 값은 -90 ~ 90 사이여야 합니다."), - 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", "도로명 주소를 찾을 수 없습니다."); + // 주소 + COORDINATES_NULL(HttpStatus.BAD_REQUEST, "L_001", "위도, 경도 값은 null일 수 없습니다."), + INVALID_LATITUDE(HttpStatus.BAD_REQUEST, "L_002", "위도 값은 -90 ~ 90 사이여야 합니다."), + 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", "도로명 주소를 찾을 수 없습니다."); +// ADDRESS_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "L_007", "주소 저장에 실패했습니다."), +// ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "L_008", "등록되어 있는 트레이너 주소가 없습니다."); - private final HttpStatus status; - private final String code; - private final String message; + private final HttpStatus status; + private final String code; + private final String message; - TrainerAddressErrorCode(HttpStatus status, String code, String message) { - this.status = status; - this.code = code; - this.message = message; - } + TrainerAddressErrorCode(HttpStatus status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } } From 648c24eb3a86b204a0414b143f619e82e748d63d Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:10:30 +0900 Subject: [PATCH 06/13] =?UTF-8?q?refactor=20:=20#47=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8-=EC=97=94=ED=8B=B0=ED=8B=B0-DTO?= =?UTF-8?q?=20=EA=B0=84=20=EB=B3=80=ED=99=98=20Mapper=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../address/mapper/TrainerAddressMapper.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 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 18978aec..cb71e0d0 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 { @@ -9,11 +10,41 @@ 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 ); } -} + // 엔티티 -> 도메인 변환 + public static TrainerAddress toDomain(TrainerAddressEntity entity) { + Coordinates coordinates = new Coordinates(entity.getLatitude(), entity.getLongitude()); + return new TrainerAddress( + entity.getId(), + entity.getNumberAddress(), + entity.getRoadAddress(), + entity.getDetailAddress(), + entity.getPostalCode(), + coordinates + ); + } + + // 도메인 -> 엔티티 변환 + 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(); + } + +} \ No newline at end of file From cca5bf8c2eaa44673b8904d006b0bdb990c65032 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:11:51 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feat:=20#47=20saveForEntityReference()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?= =?UTF-8?q?=EC=98=81=EC=86=8D=EC=84=B1=20=EC=BB=A8=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TrainerAddressRepository.java | 3 ++- .../TrainerAddressRepositoryImpl.java | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) 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..b66e441a 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 @@ -1,8 +1,9 @@ package com.demo.pteam.trainer.address.repository; import com.demo.pteam.trainer.address.domain.TrainerAddress; -import java.util.Optional; +import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; public interface TrainerAddressRepository { TrainerAddress save(TrainerAddress address); + TrainerAddressEntity saveForEntityReference(TrainerAddress address); } \ 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..e6431349 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,10 +2,13 @@ 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; +import java.util.Optional; + @Repository @RequiredArgsConstructor public class TrainerAddressRepositoryImpl implements TrainerAddressRepository { @@ -14,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( @@ -40,4 +35,9 @@ public TrainerAddress save(TrainerAddress address) { ); } + @Override + public TrainerAddressEntity saveForEntityReference(TrainerAddress address) { + return jpaRepository.save(TrainerAddressMapper.toEntity(address)); + } + } \ No newline at end of file From b582b27e7f538154b6dcd4133d54c971463bc569 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:12:54 +0900 Subject: [PATCH 08/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20-=20updateXxx()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/TrainerProfileEntity.java | 106 +++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) 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..d7cd70d7 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 @@ -17,45 +17,71 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class TrainerProfileEntity extends SoftDeletableEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private AccountEntity trainer; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "address_id") - private TrainerAddressEntity address; - - @Lob - private String profileImg; - - @Lob - private String intro; - - @Column(columnDefinition = "INT DEFAULT 0") - private Integer credit; - - private LocalTime contactStartTime; - private LocalTime contactEndTime; - - @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT TRUE") - private Boolean isNamePublic; - - @Builder - public TrainerProfileEntity(AccountEntity trainer, TrainerAddressEntity address, String profileImg, - String intro, Integer credit, LocalTime contactStartTime, - LocalTime contactEndTime, Boolean isNamePublic) { - this.trainer = trainer; - this.address = address; - this.profileImg = profileImg; - this.intro = intro; - this.credit = credit != null ? credit : 0; - this.contactStartTime = contactStartTime; - this.contactEndTime = contactEndTime; - this.isNamePublic = isNamePublic != null ? isNamePublic : true; - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false, unique = true) + private AccountEntity trainer; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "address_id") + private TrainerAddressEntity address; + + @Lob + private String profileImg; + + @Lob + private String intro; + + @Column(columnDefinition = "INT DEFAULT 0") + private Integer credit; + + private LocalTime contactStartTime; + private LocalTime contactEndTime; + + @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT TRUE") + private Boolean isNamePublic; + + @Builder + public TrainerProfileEntity(AccountEntity trainer, TrainerAddressEntity address, String profileImg, + String intro, Integer credit, LocalTime contactStartTime, + LocalTime contactEndTime, Boolean isNamePublic) { + this.trainer = trainer; + this.address = address; + this.profileImg = profileImg; + this.intro = intro; + this.credit = credit != null ? credit : 0; + this.contactStartTime = contactStartTime; + this.contactEndTime = contactEndTime; + this.isNamePublic = isNamePublic != null ? isNamePublic : true; + } + + // 수정 관련 메서드 + public void updateIntro(String intro) { + this.intro = intro; + } + + public void updateProfileImg(String profileImg) { + this.profileImg = profileImg; + } + + public void updateCredit(Integer credit) { + this.credit = credit; + } + + public void updateContactTime(LocalTime start, LocalTime end) { + this.contactStartTime = start; + this.contactEndTime = end; + } + + public void updateNamePublic(Boolean isPublic) { + this.isNamePublic = isPublic; + } + + public void updateAddress(TrainerAddressEntity address) { + this.address = address; + } } From 3f14c1b5a6def4a4b6ac7e5e7f7375c7ad26ac66 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:13:54 +0900 Subject: [PATCH 09/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20JPA=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B0=8F=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=EC=BF=BC=EB=A6=AC=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20TrainerProfileJPARepository=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20-=20fetch=20join=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=ED=95=9C=20findDetailedProfileEntityByUserId()=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TrainerProfileJPARepository.java | 2 +- .../TrainerProfileJPARepositoryCustom.java | 9 +++++ ...TrainerProfileJPARepositoryCustomImpl.java | 34 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) 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/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() + ); + } +} From ceb81e82f4d84b7f6901e9a4902a2a23ef3ae244 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:14:26 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20Mapper=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20-=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=E2=86=94=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=E2=86=94=20=EC=9A=94=EC=B2=AD=20DTO=20=EA=B0=84=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/mapper/TrainerProfileMapper.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) 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..355ad2c6 --- /dev/null +++ b/src/main/java/com/demo/pteam/trainer/profile/mapper/TrainerProfileMapper.java @@ -0,0 +1,64 @@ +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.controller.dto.TrainerProfileRequest; +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(); + } + + // 프로필 엔티티 -> 프로필 도메인 + 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 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() + ); + } + +} From 8a36ceb0d634cda3af6b9181fd4cddfc0b3f124f Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:15:38 +0900 Subject: [PATCH 11/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20Repository=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?-=20JPA=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TrainerProfileRepository.java | 4 +++ .../TrainerProfileRepositoryImpl.java | 34 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) 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 b10e7d1d..4f7c214e 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,33 @@ 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); - } + @Override + public Optional findEntityByUserId(Long userId) { + return trainerProfileJPARepository.findDetailedProfileEntityByUserId(userId); + } } \ No newline at end of file From b4b2e97491f5af579d22cac2548c026e0f58ef13 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:16:10 +0900 Subject: [PATCH 12/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?API=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TrainerProfileController.java | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) 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..350027c0 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 @@ -6,25 +6,43 @@ 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 + * @param request 트레이너 프로필 요청 DTO + * @return 수정 성공 여부 + */ + @PutMapping + public ResponseEntity> updateProfile( + @RequestBody @Valid TrainerProfileRequest request + ) { + Long userId = 3L; // TODO: 로그인 사용자 임시 + + trainerProfileService.updateProfile(request, userId); + return ResponseEntity.ok(ApiResponse.success("트레이너 프로필이 성공적으로 수정되었습니다.")); + } - trainerProfileService.createProfile(request, userId); - return ResponseEntity.status(201).body(ApiResponse.created("트레이너 프로필이 성공적으로 등록되었습니다.")); - } } \ No newline at end of file From bfef2b2dd1d90e73abe686b84e6518d25757a097 Mon Sep 17 00:00:00 2001 From: seongminQa Date: Wed, 28 May 2025 02:17:24 +0900 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20#47=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20-=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C=20?= =?UTF-8?q?=EC=A2=8C=ED=91=9C=20=EC=A0=95=ED=95=A9=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=20-=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EC=97=AC=EB=B6=80=20=ED=8C=90=EB=8B=A8=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=95=ED=95=A9=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/TrainerProfileService.java | 113 +++++++++++++++--- 1 file changed, 94 insertions(+), 19 deletions(-) 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..8760675b 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,31 +3,54 @@ 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; import com.demo.pteam.trainer.address.repository.TrainerAddressRepository; +import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity; import com.demo.pteam.trainer.profile.controller.dto.TrainerProfileRequest; 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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @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()); + 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() @@ -43,37 +66,89 @@ 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); - - TrainerProfile profile = TrainerProfile.of( - userId, - savedAddress.getId(), - request.getProfileImg(), - request.getIntro(), - request.getCredit(), - request.getContactStartTime(), - request.getContactEndTime(), - request.getIsNamePublic() - ); + TrainerAddress savedAddress = trainerAddressRepository.save(completedAddress); - if (!profile.isProfileComplete()) { - throw new ApiException(TrainerProfileErrorCode.PROFILE_INCOMPLETE); - } + // name, nickname 임시 + TrainerProfile profile = TrainerProfileMapper.toDomain(request, userId, savedAddress.getId()); - 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); } trainerProfileRepository.save(profile); } + /** + * 트레이너 프로필 수정 + * @param request 트레이너 프로필 요청 DTO + * @param userId 로그인 사용자 id + */ + public void updateProfile(@Valid TrainerProfileRequest request, Long userId) { + TrainerProfileEntity entity = trainerProfileRepository.findEntityByUserId(userId) + .orElseThrow(() -> new ApiException(TrainerProfileErrorCode.PROFILE_NOT_FOUND)); + + // DB에 저장되어 있는 주소와 요청 주소가 같은지 확인 + TrainerAddress currentAddress = TrainerAddressMapper.toDomain(entity.getAddress()); // 기존 주소 + TrainerAddress newAddress = TrainerAddressMapper.toDomain(request.getAddress()); // 요청 주소 + + TrainerAddressEntity newAddressEntity = entity.getAddress(); + + boolean isAddressChanged = !newAddress.equals(currentAddress); + + if (isAddressChanged) { + KakaoGeoResponse response = kakaoMapService.requestCoordToAddress( + newAddress.getCoordinates().getLatitude(), + newAddress.getCoordinates().getLongitude() + ); + + KakaoGeoResponse.Document document = response.getDocuments().get(0); + + if (document.getRoadAddress() == null) { + throw new ApiException(TrainerAddressErrorCode.ROAD_ADDRESS_NOT_FOUND); + } + + if (!newAddress.matchesRoadAddress(document.getRoadAddress().getAddressName())) { + throw new ApiException(TrainerAddressErrorCode.ADDRESS_COORDINATE_MISMATCH); + } + + TrainerAddress completedAddress = newAddress.withCompletedAddress( + document.getAddress().getAddressName(), + document.getRoadAddress().getZoneNo() + ); + + newAddressEntity = trainerAddressRepository.saveForEntityReference(completedAddress); // 영속 상태 반환 + } + + // 나머지 트레이너 프로필 값 업데이트 + entity.updateIntro(request.getIntro()); + entity.updateProfileImg(request.getProfileImg()); + entity.updateCredit(request.getCredit()); + entity.updateContactTime(request.getContactStartTime(), request.getContactEndTime()); + entity.updateNamePublic(request.getIsNamePublic()); + + if (isAddressChanged) { + entity.updateAddress(newAddressEntity); + } + + TrainerProfile domain = TrainerProfileMapper.toDomain(entity); + + if (domain.isInvalidContactTimePair()) { + throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_PAIR); + } + + if (domain.isInvalidContactTimeRange()) { + throw new ApiException(TrainerProfileErrorCode.INVALID_CONTACT_TIME_RANGE); + } + + } + }