diff --git a/src/docs/asciidoc/auth.adoc b/src/docs/asciidoc/auth.adoc index cdd8024b..6835b9ac 100644 --- a/src/docs/asciidoc/auth.adoc +++ b/src/docs/asciidoc/auth.adoc @@ -5,7 +5,7 @@ :sectlinks: :sectnums: -== Member +== Auth === 로그인 diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index a1bcf005..580247dd 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -5,7 +5,7 @@ :sectlinks: :sectnums: -== Info +== Member === 닉네임 중복 확인 (GET /api/members/nickname/existence) @@ -18,11 +18,10 @@ include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/http- include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/http-response.adoc[] -=== 회원 정보 초기화 (POST /api/members/{memberId}) +=== 회원 정보 초기화 (POST /api/members) ==== 요청 include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/request-headers.adoc[] -include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/path-parameters.adoc[] include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/request-fields.adoc[] include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/http-request.adoc[] @@ -30,11 +29,20 @@ include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/http- include::{snippets}/member-controller-web-mvc-test/회원_정보_초기화/http-response.adoc[] -=== 회원 정보 수정 (PATCH /api/members/{memberId}) +=== 회원 정보 조회(GET /api/members) +==== 요청 +include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/request-headers.adoc[] +include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/http-request.adoc[] + +==== 응답 +include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/response-body.adoc[] +include::{snippets}/member-controller-web-mvc-test/회원_정보_조회/http-response.adoc[] + + +=== 회원 정보 수정 (PATCH /api/members) ==== 요청 include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/request-headers.adoc[] -include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/path-parameters.adoc[] include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/request-fields.adoc[] include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/http-request.adoc[] @@ -42,11 +50,10 @@ include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/http-req include::{snippets}/member-controller-web-mvc-test/회원_정보_수정/http-response.adoc[] -=== 회원 삭제 (DELETE /api/members/{memberId}) +=== 회원 삭제 (DELETE /api/members) ==== 요청 include::{snippets}/member-controller-web-mvc-test/회원_삭제/request-headers.adoc[] -include::{snippets}/member-controller-web-mvc-test/회원_삭제/path-parameters.adoc[] include::{snippets}/member-controller-web-mvc-test/회원_삭제/http-request.adoc[] ==== 응답 diff --git a/src/main/java/com/atwoz/member/application/member/MemberQueryService.java b/src/main/java/com/atwoz/member/application/member/MemberQueryService.java new file mode 100644 index 00000000..23e16b2f --- /dev/null +++ b/src/main/java/com/atwoz/member/application/member/MemberQueryService.java @@ -0,0 +1,26 @@ +package com.atwoz.member.application.member; + +import com.atwoz.member.domain.member.MemberRepository; +import com.atwoz.member.exception.exceptions.member.MemberNicknameAlreadyExistedException; +import com.atwoz.member.infrastructure.member.dto.MemberResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class MemberQueryService { + + private final MemberRepository memberRepository; + + public void checkMemberExists(final String nickname) { + if (memberRepository.existsByNickname(nickname)) { + throw new MemberNicknameAlreadyExistedException(); + } + } + + public MemberResponse findMember(final Long memberId) { + return memberRepository.findMemberWithId(memberId); + } +} diff --git a/src/main/java/com/atwoz/member/application/member/MemberService.java b/src/main/java/com/atwoz/member/application/member/MemberService.java index 1095750d..80bfe3a1 100644 --- a/src/main/java/com/atwoz/member/application/member/MemberService.java +++ b/src/main/java/com/atwoz/member/application/member/MemberService.java @@ -1,7 +1,6 @@ package com.atwoz.member.application.member; import com.atwoz.member.application.member.dto.MemberInitializeRequest; -import com.atwoz.member.application.member.dto.MemberNicknameRequest; import com.atwoz.member.application.member.dto.MemberUpdateRequest; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; @@ -30,17 +29,6 @@ public void create(final String phoneNumber) { memberRepository.save(Member.createWithOAuth(phoneNumber)); } - @Transactional(readOnly = true) - public void checkMemberExists(final MemberNicknameRequest memberNicknameRequest) { - validateNicknameIsUnique(memberNicknameRequest.nickname()); - } - - private void validateNicknameIsUnique(final String nickname) { - if (memberRepository.existsByNickname(nickname)) { - throw new MemberNicknameAlreadyExistedException(); - } - } - public void initializeMember(final Long memberId, final MemberInitializeRequest memberInitializeRequest) { Member foundMember = findMemberById(memberId); MemberProfileDto memberProfileDto = MemberProfileDto.createWith( @@ -50,6 +38,12 @@ public void initializeMember(final Long memberId, final MemberInitializeRequest foundMember.initializeWith(memberInitializeRequest.nickname(), foundRecommenderId, memberProfileDto); } + private void validateNicknameIsUnique(final String nickname) { + if (memberRepository.existsByNickname(nickname)) { + throw new MemberNicknameAlreadyExistedException(); + } + } + private Member findMemberById(final Long memberId) { return memberRepository.findById(memberId) .orElseThrow(MemberNotFoundException::new); diff --git a/src/main/java/com/atwoz/member/application/member/dto/MemberNicknameRequest.java b/src/main/java/com/atwoz/member/application/member/dto/MemberNicknameRequest.java deleted file mode 100644 index 169f4c24..00000000 --- a/src/main/java/com/atwoz/member/application/member/dto/MemberNicknameRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.atwoz.member.application.member.dto; - -import jakarta.validation.constraints.NotBlank; - -public record MemberNicknameRequest( - @NotBlank(message = "닉네임을 입력해주세요.") - String nickname -){ -} diff --git a/src/main/java/com/atwoz/member/domain/member/MemberRepository.java b/src/main/java/com/atwoz/member/domain/member/MemberRepository.java index 71fb086b..5a389aa3 100644 --- a/src/main/java/com/atwoz/member/domain/member/MemberRepository.java +++ b/src/main/java/com/atwoz/member/domain/member/MemberRepository.java @@ -1,5 +1,6 @@ package com.atwoz.member.domain.member; +import com.atwoz.member.infrastructure.member.dto.MemberResponse; import java.util.Optional; public interface MemberRepository { @@ -10,6 +11,8 @@ public interface MemberRepository { Optional findByNickname(String nickname); + MemberResponse findMemberWithId(Long id); + Member save(Member member); boolean existsByPhoneNumber(String phoneNumber); diff --git a/src/main/java/com/atwoz/member/domain/member/profile/vo/MemberHobbies.java b/src/main/java/com/atwoz/member/domain/member/profile/MemberHobbies.java similarity index 57% rename from src/main/java/com/atwoz/member/domain/member/profile/vo/MemberHobbies.java rename to src/main/java/com/atwoz/member/domain/member/profile/MemberHobbies.java index 80f54945..11200d43 100644 --- a/src/main/java/com/atwoz/member/domain/member/profile/vo/MemberHobbies.java +++ b/src/main/java/com/atwoz/member/domain/member/profile/MemberHobbies.java @@ -1,46 +1,47 @@ -package com.atwoz.member.domain.member.profile.vo; +package com.atwoz.member.domain.member.profile; +import com.atwoz.member.domain.member.profile.vo.Hobby; import com.atwoz.member.exception.exceptions.member.profile.HobbyDuplicateException; import com.atwoz.member.exception.exceptions.member.profile.HobbySizeException; import com.atwoz.member.exception.exceptions.member.profile.InvalidHobbyException; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embeddable; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@Embeddable +@EqualsAndHashCode(of = "id") @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PUBLIC) +@Entity public class MemberHobbies { private static final int MIN_HOBBY_SIZE = 1; private static final int MAX_HOBBY_SIZE = 3; - @ElementCollection - @CollectionTable(name = "HOBBIES", joinColumns = @JoinColumn(name = "profile_id")) - @Enumerated(EnumType.STRING) - private Set hobbies = new HashSet<>(); + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - public MemberHobbies changeWith(final List hobbyCodes) { - validateHobbyCodes(hobbyCodes); - Set uniqueHobbies = convertToUniqueHobbies(hobbyCodes); - - if (isSameAsCurrentValue(uniqueHobbies)) { - return new MemberHobbies(this.hobbies); - } + @JoinColumn(name = "member_hobbies_id") + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true, fetch = FetchType.LAZY) + private Set hobbies = new HashSet<>(); - return new MemberHobbies(uniqueHobbies); + public void change(final List hobbyCodes) { + validateHobbyCodes(hobbyCodes); + changeHobbies(hobbyCodes); } private void validateHobbyCodes(final List hobbyCodes) { @@ -76,13 +77,16 @@ private boolean hasInvalidHobbyCode(final List hobbyCodes) { .anyMatch(hobbyCode -> !Hobby.isValidCode(hobbyCode)); } - private Set convertToUniqueHobbies(final List hobbyCodes) { - return hobbyCodes.stream() - .map(Hobby::findByCode) - .collect(Collectors.toSet()); + private void changeHobbies(final List hobbyCodes) { + hobbies.removeIf(memberHobby -> !memberHobby.hasMatchingHobbyCodeOf(hobbyCodes)); + hobbyCodes.stream() + .map(MemberHobby::createWith) + .filter(memberHobby -> !isAlreadyExist(memberHobby)) + .forEach(memberHobby -> hobbies.add(memberHobby)); } - private boolean isSameAsCurrentValue(final Set uniqueHobbies) { - return uniqueHobbies.equals(this.hobbies); + private boolean isAlreadyExist(final MemberHobby memberHobby) { + return hobbies.stream() + .anyMatch(memberHobby::isSame); } } diff --git a/src/main/java/com/atwoz/member/domain/member/profile/MemberHobby.java b/src/main/java/com/atwoz/member/domain/member/profile/MemberHobby.java new file mode 100644 index 00000000..a2312010 --- /dev/null +++ b/src/main/java/com/atwoz/member/domain/member/profile/MemberHobby.java @@ -0,0 +1,44 @@ +package com.atwoz.member.domain.member.profile; + +import com.atwoz.member.domain.member.profile.vo.Hobby; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class MemberHobby { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + private Hobby hobby; + + public static MemberHobby createWith(final String hobbyCode) { + return MemberHobby.builder() + .hobby(Hobby.findByCode(hobbyCode)) + .build(); + } + + public boolean hasMatchingHobbyCodeOf(final List hobbyCodes) { + return hobbyCodes.contains(hobby.getCode()); + } + + public boolean isSame(final MemberHobby memberHobby) { + return this.hobby.equals(memberHobby.hobby); + } +} diff --git a/src/main/java/com/atwoz/member/domain/member/profile/MemberStyle.java b/src/main/java/com/atwoz/member/domain/member/profile/MemberStyle.java new file mode 100644 index 00000000..161665ef --- /dev/null +++ b/src/main/java/com/atwoz/member/domain/member/profile/MemberStyle.java @@ -0,0 +1,44 @@ +package com.atwoz.member.domain.member.profile; + +import com.atwoz.member.domain.member.profile.vo.Style; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class MemberStyle { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + private Style style; + + public static MemberStyle createWith(final String styleCode) { + return MemberStyle.builder() + .style(Style.findByCode(styleCode)) + .build(); + } + + public boolean hasMatchingStyleCodeOf(final List styleCodes) { + return styleCodes.contains(style.getCode()); + } + + public boolean isSame(final MemberStyle memberStyle) { + return this.style.equals(memberStyle.style); + } +} diff --git a/src/main/java/com/atwoz/member/domain/member/profile/vo/MemberStyles.java b/src/main/java/com/atwoz/member/domain/member/profile/MemberStyles.java similarity index 57% rename from src/main/java/com/atwoz/member/domain/member/profile/vo/MemberStyles.java rename to src/main/java/com/atwoz/member/domain/member/profile/MemberStyles.java index 15782815..f5c43458 100644 --- a/src/main/java/com/atwoz/member/domain/member/profile/vo/MemberStyles.java +++ b/src/main/java/com/atwoz/member/domain/member/profile/MemberStyles.java @@ -1,46 +1,47 @@ -package com.atwoz.member.domain.member.profile.vo; +package com.atwoz.member.domain.member.profile; +import com.atwoz.member.domain.member.profile.vo.Style; import com.atwoz.member.exception.exceptions.member.profile.InvalidStyleException; import com.atwoz.member.exception.exceptions.member.profile.StyleDuplicateException; import com.atwoz.member.exception.exceptions.member.profile.StyleSizeException; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embeddable; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@Embeddable +@EqualsAndHashCode(of = "id") @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PUBLIC) +@Entity public class MemberStyles { private static final int MIN_STYLE_SIZE = 1; private static final int MAX_STYLE_SIZE = 3; - @ElementCollection - @CollectionTable(name = "STYLES", joinColumns = @JoinColumn(name = "profile_id")) - @Enumerated(EnumType.STRING) - private Set