diff --git a/src/main/java/com/example/team4backend/domain/Event.java b/src/main/java/com/example/team4backend/domain/Event.java new file mode 100644 index 0000000..64b67c4 --- /dev/null +++ b/src/main/java/com/example/team4backend/domain/Event.java @@ -0,0 +1,43 @@ +package com.example.team4backend.domain; + +import com.example.team4backend.common.domain.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Entity +@Table(name = "events") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Event extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "target_person_id", nullable = false) + private TargetPerson targetPerson; + + @Column(nullable = false) + private LocalDate date; + + @Column(nullable = false, columnDefinition = "TEXT") + private String description; + + @Builder + public Event(TargetPerson targetPerson, LocalDate date, String description) { + this.targetPerson = targetPerson; + this.date = date; + this.description = description; + } + + public void update(LocalDate date, String description) { + this.date = date; + this.description = description; + } +} diff --git a/src/main/java/com/example/team4backend/domain/TargetPerson.java b/src/main/java/com/example/team4backend/domain/TargetPerson.java index 784c666..bbeb593 100644 --- a/src/main/java/com/example/team4backend/domain/TargetPerson.java +++ b/src/main/java/com/example/team4backend/domain/TargetPerson.java @@ -6,6 +6,8 @@ import java.time.Instant; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; @Entity @Table(name = "target_people") @@ -30,16 +32,12 @@ public class TargetPerson extends BaseTimeEntity { private String phoneNumber; private LocalDate birthday; - private String job; @Column(columnDefinition = "TEXT") private String interests; - @Column(columnDefinition = "TEXT") - private String events; - - @Column(columnDefinition = "TEXT") - private String memo; + @OneToMany(mappedBy = "targetPerson", cascade = CascadeType.ALL, orphanRemoval = true) + private List events = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "relationship_id", nullable = false) @@ -52,22 +50,26 @@ public class TargetPerson extends BaseTimeEntity { private Instant deletedAt; @Builder - public TargetPerson(User user, String name, Relationship relationship, ChatStyle chatStyle, Integer age, String phoneNumber, LocalDate birthday, String job, String interests, - String events, String memo) { + public TargetPerson(User user, String name, Relationship relationship, ChatStyle chatStyle, Integer age, String phoneNumber, LocalDate birthday, String interests) { this.user = user; this.name = name; this.age = age; this.phoneNumber = phoneNumber; this.birthday = birthday; - this.job = job; this.interests = interests; - this.events = events; - this.memo = memo; this.relationship = relationship; this.chatStyle = chatStyle; this.deletedAt = null; } + public void addEvent(Event event) { + this.events.add(event); + } + + public void clearEvents() { + this.events.clear(); + } + public void update( String name, Relationship relationship, @@ -75,10 +77,7 @@ public void update( Integer age, String phoneNumber, LocalDate birthday, - String job, - String interests, - String events, - String memo + String interests ) { this.name = name; this.relationship = relationship; @@ -86,9 +85,6 @@ public void update( this.age = age; this.phoneNumber = phoneNumber; this.birthday = birthday; - this.job = job; this.interests = interests; - this.events = events; - this.memo = memo; } } \ No newline at end of file diff --git a/src/main/java/com/example/team4backend/dto/EventRequest.java b/src/main/java/com/example/team4backend/dto/EventRequest.java new file mode 100644 index 0000000..60fb0d9 --- /dev/null +++ b/src/main/java/com/example/team4backend/dto/EventRequest.java @@ -0,0 +1,19 @@ +package com.example.team4backend.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + +@Schema(description = "이벤트 정보 요청") +public record EventRequest( + @Schema(description = "이벤트 날짜", example = "2025-01-10") + @NotNull(message = "이벤트 날짜는 필수입니다.") + LocalDate date, + + @Schema(description = "이벤트 설명", example = "결혼기념일") + @NotBlank(message = "이벤트 설명은 필수입니다.") + String description +) { +} diff --git a/src/main/java/com/example/team4backend/dto/EventResponse.java b/src/main/java/com/example/team4backend/dto/EventResponse.java new file mode 100644 index 0000000..2b07521 --- /dev/null +++ b/src/main/java/com/example/team4backend/dto/EventResponse.java @@ -0,0 +1,26 @@ +package com.example.team4backend.dto; + +import com.example.team4backend.domain.Event; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; + +@Schema(description = "이벤트 정보 응답") +public record EventResponse( + @Schema(description = "이벤트 ID", example = "1") + Long id, + + @Schema(description = "이벤트 날짜", example = "2025-01-10") + LocalDate date, + + @Schema(description = "이벤트 설명", example = "결혼기념일") + String description +) { + public static EventResponse from(Event event) { + return new EventResponse( + event.getId(), + event.getDate(), + event.getDescription() + ); + } +} diff --git a/src/main/java/com/example/team4backend/dto/TargetRequest.java b/src/main/java/com/example/team4backend/dto/TargetRequest.java index 288b17a..60b0aad 100644 --- a/src/main/java/com/example/team4backend/dto/TargetRequest.java +++ b/src/main/java/com/example/team4backend/dto/TargetRequest.java @@ -1,9 +1,11 @@ package com.example.team4backend.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; +import java.util.List; @Schema(description = "상대방 정보 등록 요청") public record TargetRequest( @@ -11,12 +13,12 @@ public record TargetRequest( @NotBlank(message = "이름은 필수입니다.") String name, - @Schema(description = "관계", example = "1") - @NotNull + @Schema(description = "관계 ID", example = "1") + @NotNull(message = "관계는 필수입니다.") Long relationshipId, - @Schema(description = "채팅 스타일", example = "1") - @NotNull + @Schema(description = "채팅 스타일 ID", example = "1") + @NotNull(message = "채팅 스타일은 필수입니다.") Long chatStyleId, @Schema(description = "나이", example = "25") @@ -28,16 +30,11 @@ public record TargetRequest( @Schema(description = "생일", example = "1999-12-17") LocalDate birthday, - @Schema(description = "직업", example = "개발자") - String job, - @Schema(description = "관심사 및 취미", example = "축구, 영화 감상, 맛집 탐방") String interests, - @Schema(description = "최근 이벤트", example = "최근에 새로운 프로젝트를 시작함") - String events, - - @Schema(description = "기타 메모", example = "말투가 다정하고 리액션이 좋음") - String memo + @Schema(description = "이벤트 목록") + @Valid + List events ) { } \ No newline at end of file diff --git a/src/main/java/com/example/team4backend/dto/TargetResponse.java b/src/main/java/com/example/team4backend/dto/TargetResponse.java index 4381a77..0448a38 100644 --- a/src/main/java/com/example/team4backend/dto/TargetResponse.java +++ b/src/main/java/com/example/team4backend/dto/TargetResponse.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; +import java.util.List; public record TargetResponse( @Schema(description = "이름", example = "홍길동") @@ -24,17 +25,11 @@ public record TargetResponse( @Schema(description = "생일", example = "1999-12-17") LocalDate birthday, - @Schema(description = "직업", example = "개발자") - String job, - @Schema(description = "관심사 및 취미", example = "축구, 영화 감상, 맛집 탐방") String interests, - @Schema(description = "최근 이벤트", example = "최근에 새로운 프로젝트를 시작함") - String events, - - @Schema(description = "기타 메모", example = "말투가 다정하고 리액션이 좋음") - String memo + @Schema(description = "이벤트 목록") + List events ){ public static TargetResponse from(TargetPerson target) { return new TargetResponse( @@ -44,10 +39,10 @@ public static TargetResponse from(TargetPerson target) { target.getAge(), target.getPhoneNumber(), target.getBirthday(), - target.getJob(), target.getInterests(), - target.getEvents(), - target.getMemo() + target.getEvents().stream() + .map(EventResponse::from) + .toList() ); } } diff --git a/src/main/java/com/example/team4backend/repository/EventRepository.java b/src/main/java/com/example/team4backend/repository/EventRepository.java new file mode 100644 index 0000000..6d3d39a --- /dev/null +++ b/src/main/java/com/example/team4backend/repository/EventRepository.java @@ -0,0 +1,11 @@ +package com.example.team4backend.repository; + +import com.example.team4backend.domain.Event; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface EventRepository extends JpaRepository { + List findByTargetPersonId(Long targetPersonId); + void deleteByTargetPersonId(Long targetPersonId); +} diff --git a/src/main/java/com/example/team4backend/service/TargetService.java b/src/main/java/com/example/team4backend/service/TargetService.java index c722136..2576080 100644 --- a/src/main/java/com/example/team4backend/service/TargetService.java +++ b/src/main/java/com/example/team4backend/service/TargetService.java @@ -2,6 +2,7 @@ import com.example.team4backend.common.error.ErrorCode; import com.example.team4backend.domain.ChatStyle; +import com.example.team4backend.domain.Event; import com.example.team4backend.domain.Relationship; import com.example.team4backend.domain.TargetPerson; import com.example.team4backend.domain.User; @@ -10,6 +11,7 @@ import com.example.team4backend.dto.TargetResponse; import com.example.team4backend.exception.BusinessException; import com.example.team4backend.repository.ChatStyleRepository; +import com.example.team4backend.repository.EventRepository; import com.example.team4backend.repository.RelationshipRepository; import com.example.team4backend.repository.TargetPersonRepository; import com.example.team4backend.repository.UserRepository; @@ -27,6 +29,7 @@ public class TargetService { private final UserRepository userRepository; private final RelationshipRepository relationshipRepository; private final ChatStyleRepository chatStyleRepository; + private final EventRepository eventRepository; @Transactional public Long addTarget(Long userId, TargetRequest dto) { @@ -47,17 +50,28 @@ public Long addTarget(Long userId, TargetRequest dto) { .age(dto.age()) .phoneNumber(dto.phoneNumber()) .birthday(dto.birthday()) - .job(dto.job()) .interests(dto.interests()) - .events(dto.events()) - .memo(dto.memo()) .build(); + TargetPerson savedTarget = targetPersonRepository.save(target); + + // 이벤트 저장 + if (dto.events() != null && !dto.events().isEmpty()) { + dto.events().forEach(eventDto -> { + Event event = Event.builder() + .targetPerson(savedTarget) + .date(eventDto.date()) + .description(eventDto.description()) + .build(); + savedTarget.addEvent(event); + }); + } + if (!user.isOnboarded()) { user.completeOnboarding(); } - return targetPersonRepository.save(target).getId(); + return savedTarget.getId(); } @Transactional(readOnly = true) @@ -108,11 +122,21 @@ public void updateTarget(Long userId, Long targetId, TargetRequest dto) { dto.age(), dto.phoneNumber(), dto.birthday(), - dto.job(), - dto.interests(), - dto.events(), - dto.memo() - ); + dto.interests() + ); + + // 기존 이벤트 삭제 후 새로 추가 + target.clearEvents(); + if (dto.events() != null && !dto.events().isEmpty()) { + dto.events().forEach(eventDto -> { + Event event = Event.builder() + .targetPerson(target) + .date(eventDto.date()) + .description(eventDto.description()) + .build(); + target.addEvent(event); + }); + } } @Transactional diff --git a/src/test/java/com/example/team4backend/domain/TargetPersonTest.java b/src/test/java/com/example/team4backend/domain/TargetPersonTest.java index 680b1de..b912377 100644 --- a/src/test/java/com/example/team4backend/domain/TargetPersonTest.java +++ b/src/test/java/com/example/team4backend/domain/TargetPersonTest.java @@ -44,15 +44,12 @@ void createTargetPerson_success() { TargetPerson targetPerson = TargetPerson.builder() .user(user) .name("홍길동") - .relationship(relationship) // Enum 대신 Entity 사용 - .chatStyle(chatStyle) // 대화 형식 엔티티 추가 + .relationship(relationship) + .chatStyle(chatStyle) .age(29) .phoneNumber("01029050166") .birthday(birthday) - .job("개발자") .interests("운동, 음악") - .events("생일") - .memo("고등학교 친구") .build(); // then @@ -64,10 +61,8 @@ void createTargetPerson_success() { assertThat(targetPerson.getAge()).isEqualTo(29); assertThat(targetPerson.getPhoneNumber()).isEqualTo("01029050166"); assertThat(targetPerson.getBirthday()).isEqualTo(birthday); - assertThat(targetPerson.getJob()).isEqualTo("개발자"); assertThat(targetPerson.getInterests()).isEqualTo("운동, 음악"); - assertThat(targetPerson.getEvents()).isEqualTo("생일"); - assertThat(targetPerson.getMemo()).isEqualTo("고등학교 친구"); + assertThat(targetPerson.getEvents()).isEmpty(); assertThat(targetPerson.getDeletedAt()).isNull(); } } @@ -92,10 +87,7 @@ void updateTargetPerson_success() { .age(29) .phoneNumber("01029050166") .birthday(LocalDate.of(1995, 5, 10)) - .job("개발자") .interests("운동") - .events("생일") - .memo("친구") .build(); Relationship newRel = createRelationship("FAMILY", "가족"); @@ -110,10 +102,7 @@ void updateTargetPerson_success() { 35, "01012345678", newBirthday, - "기획자", - "독서", - "결혼식", - "가족" + "독서" ); // then @@ -123,10 +112,81 @@ void updateTargetPerson_success() { assertThat(targetPerson.getAge()).isEqualTo(35); assertThat(targetPerson.getPhoneNumber()).isEqualTo("01012345678"); assertThat(targetPerson.getBirthday()).isEqualTo(newBirthday); - assertThat(targetPerson.getJob()).isEqualTo("기획자"); assertThat(targetPerson.getInterests()).isEqualTo("독서"); - assertThat(targetPerson.getEvents()).isEqualTo("결혼식"); - assertThat(targetPerson.getMemo()).isEqualTo("가족"); + } + + @Test + @DisplayName("이벤트 추가가 정상적으로 동작한다") + void addEvent_success() { + // given + User user = createUser(); + Relationship relationship = createRelationship("FRIEND", "친구"); + ChatStyle chatStyle = createChatStyle("편한 반말", "반말 말투"); + + TargetPerson targetPerson = TargetPerson.builder() + .user(user) + .name("홍길동") + .relationship(relationship) + .chatStyle(chatStyle) + .age(29) + .phoneNumber("01029050166") + .birthday(LocalDate.of(1995, 5, 10)) + .interests("운동") + .build(); + + Event event = Event.builder() + .targetPerson(targetPerson) + .date(LocalDate.of(2025, 1, 10)) + .description("결혼기념일") + .build(); + + // when + targetPerson.addEvent(event); + + // then + assertThat(targetPerson.getEvents()).hasSize(1); + assertThat(targetPerson.getEvents().get(0).getDescription()).isEqualTo("결혼기념일"); + } + + @Test + @DisplayName("이벤트 전체 삭제가 정상적으로 동작한다") + void clearEvents_success() { + // given + User user = createUser(); + Relationship relationship = createRelationship("FRIEND", "친구"); + ChatStyle chatStyle = createChatStyle("편한 반말", "반말 말투"); + + TargetPerson targetPerson = TargetPerson.builder() + .user(user) + .name("홍길동") + .relationship(relationship) + .chatStyle(chatStyle) + .age(29) + .phoneNumber("01029050166") + .birthday(LocalDate.of(1995, 5, 10)) + .interests("운동") + .build(); + + Event event1 = Event.builder() + .targetPerson(targetPerson) + .date(LocalDate.of(2025, 1, 10)) + .description("결혼기념일") + .build(); + + Event event2 = Event.builder() + .targetPerson(targetPerson) + .date(LocalDate.of(2025, 2, 14)) + .description("생일") + .build(); + + targetPerson.addEvent(event1); + targetPerson.addEvent(event2); + + // when + targetPerson.clearEvents(); + + // then + assertThat(targetPerson.getEvents()).isEmpty(); } } } \ No newline at end of file diff --git a/src/test/java/com/example/team4backend/service/TargetServiceTest.java b/src/test/java/com/example/team4backend/service/TargetServiceTest.java index 0020376..198876e 100644 --- a/src/test/java/com/example/team4backend/service/TargetServiceTest.java +++ b/src/test/java/com/example/team4backend/service/TargetServiceTest.java @@ -5,9 +5,11 @@ import com.example.team4backend.domain.Relationship; import com.example.team4backend.domain.TargetPerson; import com.example.team4backend.domain.User; +import com.example.team4backend.dto.EventRequest; import com.example.team4backend.dto.TargetRequest; import com.example.team4backend.dto.TargetResponse; import com.example.team4backend.repository.ChatStyleRepository; +import com.example.team4backend.repository.EventRepository; import com.example.team4backend.repository.RelationshipRepository; import com.example.team4backend.repository.TargetPersonRepository; import com.example.team4backend.repository.UserRepository; @@ -19,6 +21,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDate; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.*; @@ -31,6 +34,7 @@ class TargetServiceTest { @Mock UserRepository userRepository; @Mock RelationshipRepository relationshipRepository; @Mock ChatStyleRepository chatStyleRepository; + @Mock EventRepository eventRepository; @InjectMocks TargetService targetService; @@ -61,15 +65,16 @@ private ChatStyle chatStyle(Long id, String name) { private TargetRequest req(Long relId, Long chatStyleId) { return new TargetRequest( "홍길동", - relId, // ID 기반으로 변경 - chatStyleId, // 추가 + relId, + chatStyleId, 29, "01029050166", LocalDate.of(1995, 5, 10), - "개발자", "운동, 음악", - "생일", - "메모" + List.of( + new EventRequest(LocalDate.of(2025, 1, 10), "결혼기념일"), + new EventRequest(LocalDate.of(2025, 2, 14), "생일") + ) ); } @@ -77,15 +82,12 @@ private TargetPerson target(Long targetId, User owner, Relationship rel, ChatSty TargetPerson t = TargetPerson.builder() .user(owner) .name("홍길동") - .relationship(rel) // 엔티티 객체 주입 - .chatStyle(chat) // 엔티티 객체 주입 + .relationship(rel) + .chatStyle(chat) .age(29) .phoneNumber("01029050166") .birthday(LocalDate.of(1995, 5, 10)) - .job("개발자") .interests("운동") - .events("생일") - .memo("친구") .build(); TestReflection.setField(t, "id", targetId); return t; @@ -166,7 +168,8 @@ void updateTarget_success() { TargetRequest dto = new TargetRequest( "김철수", 2L, 2L, 35, "01012345678", - LocalDate.of(1990, 1, 1), "기획자", "독서", "결혼식", "메모" + LocalDate.of(1990, 1, 1), "독서", + List.of(new EventRequest(LocalDate.of(2025, 3, 1), "결혼식")) ); // when