Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;

@Configuration
public class ApplicationConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
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 {
private final BigDecimal latitude;
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);
}
}
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,49 @@

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 {

// 요청 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();
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand All @@ -40,4 +35,9 @@ public TrainerAddress save(TrainerAddress address) {
);
}

@Override
public TrainerAddressEntity saveForEntityReference(TrainerAddress address) {
return jpaRepository.save(TrainerAddressMapper.toEntity(address));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiResponse<Void>> createProfile(
@RequestBody @Valid TrainerProfileRequest request
) {
Long userId = 4L; // TODO: 로그인 사용자 임시
/**
* 트레이너 프로필 등록 API
* @param request 트레이너 프로필 요청 DTO
* @return 등록 성공 여부
*/
@PostMapping
public ResponseEntity<ApiResponse<Void>> 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<ApiResponse<Void>> 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("트레이너 프로필이 성공적으로 등록되었습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 = "이름 공개 선택 여부는 필수입니다.")
Expand Down
Loading