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,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;

import java.math.BigDecimal;
Expand All @@ -12,17 +10,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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
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,13 +24,15 @@ 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,39 @@
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("트레이너 프로필이 성공적으로 등록되었습니다."));
}
trainerProfileService.createProfile(request, userId);
return ResponseEntity.status(201).body(ApiResponse.created("트레이너 프로필이 성공적으로 등록되었습니다."));
}

/**
* 트레이너 프로필 삭제 API
* @return 삭제 성공 여부
*/
@DeleteMapping
public ResponseEntity<ApiResponse<Void>> deleteProfile() {
Long userId = 3L; // TODO: 로그인 사용자 임시

trainerProfileService.deleteProfile(userId);
return ResponseEntity.ok(ApiResponse.success("트레이너 프로필이 성공적으로 삭제되었습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
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;
Expand All @@ -11,6 +9,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;
Expand All @@ -19,10 +19,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;
Expand All @@ -32,31 +35,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 String getDisplayName() {
return isNamePublic ? name : nickname;
}

public boolean isNameVisible() {
return this.isNamePublic;
public boolean isInvalidContactTimePair() {
return !(contactStartTime == null && contactEndTime == null) &&
!(contactStartTime != null && contactEndTime != null);
}

public boolean isContactTimePairValid() {
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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
import com.demo.pteam.trainer.profile.repository.entity.TrainerProfileEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface TrainerProfileJPARepository extends JpaRepository<TrainerProfileEntity, Long> {
Optional<TrainerProfileEntity> findByTrainerId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import com.demo.pteam.trainer.profile.domain.TrainerProfile;

import java.util.Optional;

public interface TrainerProfileRepository {
void save(TrainerProfile trainerProfile);
Optional<TrainerProfile> findByUserId(Long userId);
void delete(Long profileId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,42 @@

import com.demo.pteam.authentication.repository.entity.AccountEntity;
import com.demo.pteam.trainer.address.repository.entity.TrainerAddressEntity;
import com.demo.pteam.trainer.profile.mapper.TrainerProfileMapper;
import com.demo.pteam.trainer.profile.domain.TrainerProfile;
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) {

AccountEntity trainer = em.getReference(AccountEntity.class, profile.getUserId());
TrainerAddressEntity address = em.getReference(TrainerAddressEntity.class, profile.getAddressId());

TrainerProfileEntity entity = TrainerProfileMapper.toEntity(profile, trainer, address);

@Override
public void save(TrainerProfile profile) {
trainerProfileJPARepository.save(entity);
}

AccountEntity trainer = em.getReference(AccountEntity.class, profile.getUserId());
TrainerAddressEntity address = em.getReference(TrainerAddressEntity.class, profile.getAddressId());
@Override
public Optional<TrainerProfile> findByUserId(Long userId) {
return trainerProfileJPARepository.findByTrainerId(userId)
.map(entity -> TrainerProfileMapper.toDomain(entity));
}

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();
@Override
public void delete(Long profileId) {
trainerProfileJPARepository.deleteById(profileId);
}

trainerProfileJPARepository.save(entity);
}
}
Loading