diff --git a/src/main/java/com/zelusik/eatery/domain/member/api/MemberController.java b/src/main/java/com/zelusik/eatery/domain/member/api/MemberController.java index 9fbf42ca..344d3100 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/api/MemberController.java +++ b/src/main/java/com/zelusik/eatery/domain/member/api/MemberController.java @@ -136,9 +136,14 @@ public SliceResponse searchMembersByKeywordV1_1( summary = "내 정보 수정", description = "

Latest version: v1.1" + "

내 정보를 수정합니다." + - "

프로필 이미지는 수정하고자 하는 경우에만 요청해야 하고, 수정하지 않는 경우 요청 데이터에서 제외해야합니다.", + "

프로필 이미지는 수정하고자 하는 경우에만 요청 데이터에 포함하고, 수정하지 않는 경우 요청 데이터에서 제외해야합니다.", security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "409", description = "이미 사용중인 닉네임인 경우"), + @ApiResponse(responseCode = "422", description = "유효하지 않은 닉네임인 경우.", content = @Content) + }) @PutMapping(value = "/v1/members", headers = API_MINOR_VERSION_HEADER_NAME + "=1", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public MemberResponse updateMemberV1_1( @AuthenticationPrincipal UserPrincipal userPrincipal, diff --git a/src/main/java/com/zelusik/eatery/domain/member/dto/MemberDto.java b/src/main/java/com/zelusik/eatery/domain/member/dto/MemberDto.java index 6baf705e..78ee1786 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/dto/MemberDto.java +++ b/src/main/java/com/zelusik/eatery/domain/member/dto/MemberDto.java @@ -60,7 +60,7 @@ public static MemberDto from(Member entity) { } public Member toEntity() { - return Member.of( + return Member.create( this.getProfileImageUrl(), this.getProfileThumbnailImageUrl(), this.getSocialUid(), @@ -72,4 +72,8 @@ public Member toEntity() { this.getGender() ); } + + public void setNickname(String nickname) { + this.nickname = nickname; + } } diff --git a/src/main/java/com/zelusik/eatery/domain/member/dto/request/MemberUpdateRequest.java b/src/main/java/com/zelusik/eatery/domain/member/dto/request/MemberUpdateRequest.java index c1ada102..1f055494 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/dto/request/MemberUpdateRequest.java +++ b/src/main/java/com/zelusik/eatery/domain/member/dto/request/MemberUpdateRequest.java @@ -33,6 +33,5 @@ public class MemberUpdateRequest { private Gender gender; @Schema(description = "변경하고자 하는 프로필 이미지") - @NotNull private MultipartFile profileImage; } diff --git a/src/main/java/com/zelusik/eatery/domain/member/entity/Member.java b/src/main/java/com/zelusik/eatery/domain/member/entity/Member.java index b5930bc0..77d95d10 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/entity/Member.java +++ b/src/main/java/com/zelusik/eatery/domain/member/entity/Member.java @@ -1,19 +1,20 @@ package com.zelusik.eatery.domain.member.entity; +import com.zelusik.eatery.domain.favorite_food_category.entity.FavoriteFoodCategory; import com.zelusik.eatery.domain.member.constant.Gender; import com.zelusik.eatery.domain.member.constant.LoginType; import com.zelusik.eatery.domain.member.constant.RoleType; import com.zelusik.eatery.domain.member.converter.RoleTypesConverter; import com.zelusik.eatery.global.common.entity.BaseTimeEntity; -import com.zelusik.eatery.domain.favorite_food_category.entity.FavoriteFoodCategory; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.LinkedList; @@ -35,70 +36,52 @@ public class Member extends BaseTimeEntity { @Column(name = "member_id", nullable = false) private Long id; - @Setter(AccessLevel.PRIVATE) - @Column(nullable = false) + @NotBlank private String profileImageUrl; - @Setter(AccessLevel.PRIVATE) - @Column(nullable = false) + @NotBlank private String profileThumbnailImageUrl; - @Column(nullable = false, unique = true) + @NotBlank + @Column(unique = true) private String socialUid; - @Column(nullable = false) + @NotNull @Enumerated(EnumType.STRING) private LoginType loginType; - @Column(nullable = false) + @NotEmpty @Convert(converter = RoleTypesConverter.class) private Set roleTypes; private String email; - @Setter(AccessLevel.PRIVATE) - @Column(nullable = false, length = 15) - private String nickname; + @NotNull + @Embedded + private MemberNickname nickname; - @Setter(AccessLevel.PRIVATE) private LocalDate birthDay; private Integer ageRange; - @Setter(AccessLevel.PRIVATE) + @NotNull @Enumerated(EnumType.STRING) - @Column(nullable = false) private Gender gender; - @OneToMany(mappedBy = "member") - private List favoriteFoodCategories = new LinkedList<>(); - - @Setter(AccessLevel.PRIVATE) private LocalDateTime deletedAt; - public static Member of( - @NonNull String profileImageUrl, - @NonNull String profileThumbnailImageUrl, - @NonNull String socialUid, - @NonNull LoginType loginType, - @NonNull Set roleTypes, - @Nullable String email, - @NonNull String nickname, - @Nullable Integer ageRange, - @Nullable Gender gender - ) { - return of(null, profileImageUrl, profileThumbnailImageUrl, socialUid, loginType, roleTypes, email, nickname, null, ageRange, gender, null, null, null); - } + @OneToMany(mappedBy = "member") + private List favoriteFoodCategories = new LinkedList<>(); - public static Member of( + public Member( @Nullable Long id, - @NonNull String profileImageUrl, - @NonNull String profileThumbnailImageUrl, - @NonNull String socialUid, - @NonNull LoginType loginType, - @NonNull Set roleTypes, + @NotNull String profileImageUrl, + @NotNull String profileThumbnailImageUrl, + @NotNull String socialUid, + @NotNull LoginType loginType, + @NotNull Set roleTypes, @Nullable String email, - @NonNull String nickname, + @NotNull String nickname, @Nullable LocalDate birthDay, @Nullable Integer ageRange, @Nullable Gender gender, @@ -106,10 +89,6 @@ public static Member of( @Nullable LocalDateTime updatedAt, @Nullable LocalDateTime deletedAt ) { - return new Member(id, profileImageUrl, profileThumbnailImageUrl, socialUid, loginType, roleTypes, email, nickname, birthDay, ageRange, gender, createdAt, updatedAt, deletedAt); - } - - private Member(@Nullable Long id, @NonNull String profileImageUrl, @NonNull String profileThumbnailImageUrl, @NonNull String socialUid, @NonNull LoginType loginType, @NonNull Set roleTypes, @Nullable String email, @NonNull String nickname, @Nullable LocalDate birthDay, @Nullable Integer ageRange, @Nullable Gender gender, @Nullable LocalDateTime createdAt, @Nullable LocalDateTime updatedAt, @Nullable LocalDateTime deletedAt) { super(createdAt, updatedAt); this.id = id; this.profileImageUrl = profileImageUrl; @@ -118,32 +97,50 @@ private Member(@Nullable Long id, @NonNull String profileImageUrl, @NonNull Stri this.loginType = loginType; this.roleTypes = roleTypes; this.email = email; - this.nickname = nickname; + this.nickname = new MemberNickname(nickname); this.birthDay = birthDay; this.ageRange = ageRange; this.gender = gender; this.deletedAt = deletedAt; } + public static Member create( + @NotNull String profileImageUrl, + @NotNull String profileThumbnailImageUrl, + @NotNull String socialUid, + @NotNull LoginType loginType, + @NotNull Set roleTypes, + @Nullable String email, + @NotNull String nickname, + @Nullable Integer ageRange, + @Nullable Gender gender + ) { + return new Member(null, profileImageUrl, profileThumbnailImageUrl, socialUid, loginType, roleTypes, email, nickname, null, ageRange, gender, null, null, null); + } + + public String getNickname() { + return nickname.getNickname(); + } + public void update(String profileImageUrl, String profileThumbnailImageUrl, String nickname, LocalDate birthDay, Gender gender) { - this.setProfileImageUrl(profileImageUrl); - this.setProfileThumbnailImageUrl(profileThumbnailImageUrl); - this.setNickname(nickname); - this.setBirthDay(birthDay); - this.setGender(gender); + this.profileImageUrl = profileImageUrl; + this.profileThumbnailImageUrl = profileThumbnailImageUrl; + this.nickname = new MemberNickname(nickname); + this.birthDay = birthDay; + this.gender = gender; } public void update(String nickname, LocalDate birthDay, Gender gender) { - this.setNickname(nickname); - this.setBirthDay(birthDay); - this.setGender(gender); + this.nickname = new MemberNickname(nickname); + this.birthDay = birthDay; + this.gender = gender; } public void rejoin() { - this.setDeletedAt(null); + this.deletedAt = null; } public void softDelete() { - this.setDeletedAt(LocalDateTime.now()); + this.deletedAt = LocalDateTime.now(); } } diff --git a/src/main/java/com/zelusik/eatery/domain/member/entity/MemberNickname.java b/src/main/java/com/zelusik/eatery/domain/member/entity/MemberNickname.java new file mode 100644 index 00000000..913e7b9b --- /dev/null +++ b/src/main/java/com/zelusik/eatery/domain/member/entity/MemberNickname.java @@ -0,0 +1,54 @@ +package com.zelusik.eatery.domain.member.entity; + +import com.zelusik.eatery.domain.member.exception.InvalidNicknameException; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.validation.constraints.NotBlank; +import java.util.Objects; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class MemberNickname { + + + private static final int MEMBER_NICKNAME_MIN_LEN = 2; + private static final int MEMBER_NICKNAME_MAX_LEN = 15; + + @NotBlank + @Column(nullable = false, length = 15) + String nickname; + + public MemberNickname(String nickname) { + validateNickname(nickname); + this.nickname = nickname; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof String that) { + return this.getNickname().equals(that); + } + if (o instanceof MemberNickname that) { + return this.getNickname().equals(that.getNickname()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(getNickname()); + } + + private static void validateNickname(String nickname) { + int nicknameLength = nickname.length(); + if (nicknameLength < MEMBER_NICKNAME_MIN_LEN || nicknameLength > MEMBER_NICKNAME_MAX_LEN) { + throw new InvalidNicknameException(String.format("%d글자 이상, %d글자 이하의 닉네임을 입력해주세요.", MEMBER_NICKNAME_MIN_LEN, MEMBER_NICKNAME_MAX_LEN)); + } + } +} diff --git a/src/main/java/com/zelusik/eatery/domain/member/exception/InvalidNicknameException.java b/src/main/java/com/zelusik/eatery/domain/member/exception/InvalidNicknameException.java new file mode 100644 index 00000000..d8b558a7 --- /dev/null +++ b/src/main/java/com/zelusik/eatery/domain/member/exception/InvalidNicknameException.java @@ -0,0 +1,11 @@ +package com.zelusik.eatery.domain.member.exception; + +import com.zelusik.eatery.global.common.exception.UnprocessableEntityException; +import com.zelusik.eatery.global.exception.constant.CustomExceptionType; + +public class InvalidNicknameException extends UnprocessableEntityException { + + public InvalidNicknameException(String optionalMessage) { + super(CustomExceptionType.INVALID_NICKNAME, optionalMessage); + } +} diff --git a/src/main/java/com/zelusik/eatery/domain/member/exception/NicknameDuplicationException.java b/src/main/java/com/zelusik/eatery/domain/member/exception/NicknameDuplicationException.java new file mode 100644 index 00000000..731a230a --- /dev/null +++ b/src/main/java/com/zelusik/eatery/domain/member/exception/NicknameDuplicationException.java @@ -0,0 +1,11 @@ +package com.zelusik.eatery.domain.member.exception; + +import com.zelusik.eatery.global.common.exception.ConflictException; +import com.zelusik.eatery.global.exception.constant.CustomExceptionType; + +public class NicknameDuplicationException extends ConflictException { + + public NicknameDuplicationException() { + super(CustomExceptionType.NICKNAME_DUPLICATION); + } +} diff --git a/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepository.java b/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepository.java index 3f7fc941..c1a8e056 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepository.java @@ -7,6 +7,8 @@ public interface MemberRepository extends JpaRepository, MemberRepositoryQCustom { + boolean existsByNickname(String nickname); + Optional findByIdAndDeletedAtNull(Long memberId); Optional findBySocialUid(String socialUid); diff --git a/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepositoryQCustomImpl.java b/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepositoryQCustomImpl.java index e8bb7363..173ad13a 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepositoryQCustomImpl.java +++ b/src/main/java/com/zelusik/eatery/domain/member/repository/MemberRepositoryQCustomImpl.java @@ -3,12 +3,12 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.zelusik.eatery.domain.member.entity.QMember; -import com.zelusik.eatery.global.common.constant.FoodCategoryValue; -import com.zelusik.eatery.domain.review.constant.ReviewKeywordValue; -import com.zelusik.eatery.domain.member.entity.Member; import com.zelusik.eatery.domain.member.dto.MemberWithProfileInfoDto; +import com.zelusik.eatery.domain.member.entity.Member; +import com.zelusik.eatery.domain.member.entity.QMember; import com.zelusik.eatery.domain.member.exception.MemberNotFoundByIdException; +import com.zelusik.eatery.domain.review.constant.ReviewKeywordValue; +import com.zelusik.eatery.global.common.constant.FoodCategoryValue; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -120,7 +120,7 @@ private FoodCategoryValue getMostEatenFoodCategory(long memberId) { @Override public Slice searchByKeyword(String searchKeyword, Pageable pageable) { List content = queryFactory.selectFrom(member) - .where(isMemberNotDeleted(), member.nickname.containsIgnoreCase(searchKeyword)) + .where(isMemberNotDeleted(), member.nickname.nickname.containsIgnoreCase(searchKeyword)) .offset(pageable.getOffset()) .limit(pageable.getPageSize() + 1) .fetch(); diff --git a/src/main/java/com/zelusik/eatery/domain/member/service/MemberCommandService.java b/src/main/java/com/zelusik/eatery/domain/member/service/MemberCommandService.java index ca1616db..d9c8e2ea 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/service/MemberCommandService.java +++ b/src/main/java/com/zelusik/eatery/domain/member/service/MemberCommandService.java @@ -6,6 +6,7 @@ import com.zelusik.eatery.domain.member.dto.request.MemberUpdateRequest; import com.zelusik.eatery.domain.member.entity.Member; import com.zelusik.eatery.domain.member.exception.MemberNotFoundException; +import com.zelusik.eatery.domain.member.exception.NicknameDuplicationException; import com.zelusik.eatery.domain.member.repository.MemberRepository; import com.zelusik.eatery.domain.member_deletion_survey.constant.MemberDeletionSurveyType; import com.zelusik.eatery.domain.member_deletion_survey.dto.MemberDeletionSurveyDto; @@ -16,6 +17,7 @@ import com.zelusik.eatery.domain.profile_image.service.ProfileImageQueryService; import com.zelusik.eatery.domain.terms_info.service.TermsInfoCommandService; import com.zelusik.eatery.global.common.constant.FoodCategoryValue; +import com.zelusik.eatery.global.util.NicknameGenerator; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; @@ -47,6 +49,9 @@ public class MemberCommandService { */ @Transactional public MemberDto save(MemberDto memberDto) { + if (isNicknameExists(memberDto.getNickname())) { + memberDto.setNickname(generateUniqueNickname()); + } return MemberDto.from(memberRepository.save(memberDto.toEntity())); } @@ -75,6 +80,8 @@ public void rejoin(Long memberId) { public MemberDto update(Long memberId, MemberUpdateRequest updateRequest) { Member member = memberQueryService.getById(memberId); + validateNicknameDuplication(updateRequest.getNickname()); + MultipartFile profileImageForUpdate = updateRequest.getProfileImage(); if (profileImageForUpdate == null) { member.update( @@ -147,4 +154,39 @@ public MemberDeletionSurveyDto delete(Long memberId, MemberDeletionSurveyType su memberDeletionSurveyRepository.save(deletionSurvey); return MemberDeletionSurveyDto.from(deletionSurvey); } + + /** + * 이미 존재하는 닉네임인지 확인한다. + * + * @param nickname 존재 여부를 확인할 닉네임 + * @return 닉네임 존재 여부 + */ + private boolean isNicknameExists(String nickname) { + return memberQueryService.existsByNickname(nickname); + } + + /** + * 기존에 사용중이지 않은, 새로운 닉네임을 랜덤하게 생성한다. + * + * @return 생성된 unique nickname + */ + private String generateUniqueNickname() { + String nicknameRandomlyGenerated = NicknameGenerator.generateRandomNickname(); + while (isNicknameExists(nicknameRandomlyGenerated)) { + nicknameRandomlyGenerated = NicknameGenerator.generateRandomNickname(); + } + return nicknameRandomlyGenerated; + } + + /** + * 중복되지 않은 닉네임임을 검증한다. + * + * @param nickname 중복을 검증하고자 하는 닉네임 + * @throws NicknameDuplicationException 이미 사용중인 닉네임일 경우 + */ + private void validateNicknameDuplication(String nickname) { + if (isNicknameExists(nickname)) { + throw new NicknameDuplicationException(); + } + } } diff --git a/src/main/java/com/zelusik/eatery/domain/member/service/MemberQueryService.java b/src/main/java/com/zelusik/eatery/domain/member/service/MemberQueryService.java index 2d45ecda..8a24e896 100644 --- a/src/main/java/com/zelusik/eatery/domain/member/service/MemberQueryService.java +++ b/src/main/java/com/zelusik/eatery/domain/member/service/MemberQueryService.java @@ -99,4 +99,14 @@ public Slice searchDtosByKeyword(String searchKeyword, Pageable pagea public MemberWithProfileInfoDto getMemberProfileInfoById(long memberId) { return memberRepository.getMemberProfileInfoById(memberId); } + + /** + * 이미 존재하는 닉네임인지 확인한다. + * + * @param nickname 존재 여부를 확인할 닉네임 + * @return 닉네임 존재 여부 + */ + public boolean existsByNickname(String nickname) { + return memberRepository.existsByNickname(nickname); + } } diff --git a/src/main/java/com/zelusik/eatery/global/common/exception/UnprocessableEntityException.java b/src/main/java/com/zelusik/eatery/global/common/exception/UnprocessableEntityException.java new file mode 100644 index 00000000..9665b7f0 --- /dev/null +++ b/src/main/java/com/zelusik/eatery/global/common/exception/UnprocessableEntityException.java @@ -0,0 +1,23 @@ +package com.zelusik.eatery.global.common.exception; + +import com.zelusik.eatery.global.exception.constant.CustomExceptionType; +import org.springframework.http.HttpStatus; + +public abstract class UnprocessableEntityException extends CustomException { + + public UnprocessableEntityException(CustomExceptionType exceptionType) { + super(HttpStatus.UNPROCESSABLE_ENTITY, exceptionType); + } + + public UnprocessableEntityException(CustomExceptionType exceptionType, String optionalMessage) { + super(HttpStatus.UNPROCESSABLE_ENTITY, exceptionType, optionalMessage); + } + + public UnprocessableEntityException(CustomExceptionType exceptionType, Throwable cause) { + super(HttpStatus.UNPROCESSABLE_ENTITY, exceptionType, cause); + } + + public UnprocessableEntityException(CustomExceptionType exceptionType, String optionalMessage, Throwable cause) { + super(HttpStatus.UNPROCESSABLE_ENTITY, exceptionType, optionalMessage, cause); + } +} diff --git a/src/main/java/com/zelusik/eatery/global/exception/constant/CustomExceptionType.java b/src/main/java/com/zelusik/eatery/global/exception/constant/CustomExceptionType.java index 69177ac0..725151a1 100644 --- a/src/main/java/com/zelusik/eatery/global/exception/constant/CustomExceptionType.java +++ b/src/main/java/com/zelusik/eatery/global/exception/constant/CustomExceptionType.java @@ -56,6 +56,8 @@ public enum CustomExceptionType { * 회원({@link Member}) 관련 예외 */ MEMBER_NOT_FOUND(2000, "회원을 찾을 수 없습니다."), + INVALID_NICKNAME(2001, "유효하지 않은 닉네임입니다."), + NICKNAME_DUPLICATION(2002, "이미 사용중인 닉네임입니다."), /** * 장소({@link Place}) 관련 예외 diff --git a/src/main/java/com/zelusik/eatery/global/util/NicknameGenerator.java b/src/main/java/com/zelusik/eatery/global/util/NicknameGenerator.java index bc7cab8f..db6f9dc3 100644 --- a/src/main/java/com/zelusik/eatery/global/util/NicknameGenerator.java +++ b/src/main/java/com/zelusik/eatery/global/util/NicknameGenerator.java @@ -1,13 +1,20 @@ package com.zelusik.eatery.global.util; +import java.util.concurrent.ThreadLocalRandom; + public class NicknameGenerator { + private static final int MIN_NUM_FOR_RANDOM_NICKNAME = 1000; + private static final int MAX_NUM_FOR_RANDOM_NICKNAME = 10000; + private static final String[] FLAVOR_LIST = {"달달한", "바삭한", "매콤한", "쫄깃한", "아삭한", "촉촉한", "고소한", "알싸한", "얼얼한", "얼큰한", "진한", "싱거운", "짭잘한", "짭조름한", "담백한", "느끼한", "새콤한", "신선한", "달콤한", "구수한", "삼삼한", "밍밍한", "칼칼한"}; private static final String[] FOOD_LIST = {"햄버거", "감자튀김", "감자탕", "닭발구이", "피자", "치킨", "비빔밥", "떡볶이", "갈비", "곱창전골", "삼겹살", "호떡", "만두", "물회", "연어", "타코야끼", "쌀국수", "냉면", "샤브샤브", "제육볶음", "쫄면", "붕어빵", "마라탕", "마라샹궈", "김치찌개", "된장찌개", "카레", "돈까스", "짬뽕", "짜장면", "유린기", "초밥", "딱새우", "칼국수", "새우튀김", "어묵우동", "육회", "가라아게", "콘스프", "간장새우", "파스타", "볶음밥", "소세지", "리조또", "깐풍기", "칠리새우", "크림새우", "군만두", "꿔바로우", "분짜", "스프링롤", "팟타이", "똠양꿍", "닭강정", "떡갈비", "핫도그", "샐러드", "닭갈비", "계란말이", "탄두리"}; public static String generateRandomNickname() { - int flavorIdx = (int) (Math.random() * FLAVOR_LIST.length); - int foodIdx = (int) (Math.random() * FOOD_LIST.length); - return FLAVOR_LIST[flavorIdx] + " " + FOOD_LIST[foodIdx]; + ThreadLocalRandom random = ThreadLocalRandom.current(); + int flavorIdx = random.nextInt(FLAVOR_LIST.length); + int foodIdx = random.nextInt(FOOD_LIST.length); + int randNum = random.nextInt(MIN_NUM_FOR_RANDOM_NICKNAME, MAX_NUM_FOR_RANDOM_NICKNAME); + return String.format("%s %s %d", FLAVOR_LIST[flavorIdx], FOOD_LIST[foodIdx], randNum); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4f2e76b3..85e56b5c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ -eatery.app.version=2.1.0 +eatery.app.version=2.1.1 kakao.rest-api.key=${KAKAO_REST_API_KEY} open_ai.api.key=${OPEN_AI_API_KEY} diff --git a/src/test/java/com/zelusik/eatery/integration/domain/member/repository/MemberRepositoryTest.java b/src/test/java/com/zelusik/eatery/integration/domain/member/repository/MemberRepositoryTest.java index 9e34c9ec..3d405a65 100644 --- a/src/test/java/com/zelusik/eatery/integration/domain/member/repository/MemberRepositoryTest.java +++ b/src/test/java/com/zelusik/eatery/integration/domain/member/repository/MemberRepositoryTest.java @@ -125,7 +125,7 @@ void given_whenGetMemberProfileInfoWithNotExistentMemberId_thenReturnMemberProfi } private Member createNewMember(String socialUid, String nickname) { - return Member.of( + return Member.create( "https://default-profile-image", "https://defualt-profile-thumbnail-image", socialUid, diff --git a/src/test/java/com/zelusik/eatery/integration/domain/place/repository/PlaceRepositoryTest.java b/src/test/java/com/zelusik/eatery/integration/domain/place/repository/PlaceRepositoryTest.java index 499c8703..65647221 100644 --- a/src/test/java/com/zelusik/eatery/integration/domain/place/repository/PlaceRepositoryTest.java +++ b/src/test/java/com/zelusik/eatery/integration/domain/place/repository/PlaceRepositoryTest.java @@ -317,7 +317,7 @@ void given_whenGetFilteringKeywordsForAddressOrderByCountDesc_thenReturnFilterin } private Member createNewMember(String socialUid) { - return Member.of( + return Member.create( "https://default-profile-image", "https://defualt-profile-thumbnail-image", socialUid, diff --git a/src/test/java/com/zelusik/eatery/integration/domain/recommended_review/repository/RecommendedReviewRepositoryTest.java b/src/test/java/com/zelusik/eatery/integration/domain/recommended_review/repository/RecommendedReviewRepositoryTest.java index 60d97dad..7ea47ec6 100644 --- a/src/test/java/com/zelusik/eatery/integration/domain/recommended_review/repository/RecommendedReviewRepositoryTest.java +++ b/src/test/java/com/zelusik/eatery/integration/domain/recommended_review/repository/RecommendedReviewRepositoryTest.java @@ -109,7 +109,7 @@ void given_whenFindingAllRecommendedReviewDtosWithPlaceMarkedStatusByNotExistent } private Member createNewMember(String socialId, Set roleTypes, String nickname) { - return Member.of( + return Member.create( "https://default-profile-image", "https://defualt-profile-thumbnail-image", socialId, diff --git a/src/test/java/com/zelusik/eatery/integration/domain/review/repository/ReviewRepositoryTest.java b/src/test/java/com/zelusik/eatery/integration/domain/review/repository/ReviewRepositoryTest.java index c6cee5bc..9ff7a1d3 100644 --- a/src/test/java/com/zelusik/eatery/integration/domain/review/repository/ReviewRepositoryTest.java +++ b/src/test/java/com/zelusik/eatery/integration/domain/review/repository/ReviewRepositoryTest.java @@ -316,7 +316,7 @@ private Member createNewMember(String socialUid) { } private Member createNewMember(String socialUid, Set roleTypes) { - return Member.of( + return Member.create( "https://default-profile-image", "https://defualt-profile-thumbnail-image", socialUid, diff --git a/src/test/java/com/zelusik/eatery/unit/domain/bookmark/service/BookmarkCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/bookmark/service/BookmarkCommandServiceTest.java index ac977b12..3245f6e6 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/bookmark/service/BookmarkCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/bookmark/service/BookmarkCommandServiceTest.java @@ -147,7 +147,7 @@ private void verifyEveryMocksShouldHaveNoMoreInteractions() { } private Member createMember(long memberId) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberCommandServiceTest.java index 466f16ac..c7b5a091 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberCommandServiceTest.java @@ -9,6 +9,7 @@ import com.zelusik.eatery.domain.member.dto.request.MemberUpdateRequest; import com.zelusik.eatery.domain.member.entity.Member; import com.zelusik.eatery.domain.member.exception.MemberNotFoundException; +import com.zelusik.eatery.domain.member.exception.NicknameDuplicationException; import com.zelusik.eatery.domain.member.repository.MemberRepository; import com.zelusik.eatery.domain.member.service.MemberCommandService; import com.zelusik.eatery.domain.member.service.MemberQueryService; @@ -117,6 +118,7 @@ void givenMemberUpdateInfo_whenUpdatingMemberInfo_thenUpdate() { MemberUpdateRequest memberUpdateInfo = new MemberUpdateRequest("update", LocalDate.of(2020, 1, 1), Gender.ETC, createMockMultipartFile()); ProfileImage oldProfileImage = createProfileImage(member, 2L); given(memberQueryService.getById(memberId)).willReturn(member); + given(memberQueryService.existsByNickname(memberUpdateInfo.getNickname())).willReturn(false); given(profileImageQueryService.findByMember(member)).willReturn(Optional.of(oldProfileImage)); willDoNothing().given(profileImageCommandService).softDelete(oldProfileImage); given(profileImageCommandService.upload(any(Member.class), eq(memberUpdateInfo.getProfileImage()))).willReturn(createProfileImage(member, 10L)); @@ -126,6 +128,7 @@ void givenMemberUpdateInfo_whenUpdatingMemberInfo_thenUpdate() { // then then(memberQueryService).should().getById(memberId); + then(memberQueryService).should().existsByNickname(memberUpdateInfo.getNickname()); then(profileImageQueryService).should().findByMember(member); then(profileImageCommandService).should().softDelete(oldProfileImage); then(profileImageCommandService).should().upload(any(Member.class), eq(memberUpdateInfo.getProfileImage())); @@ -135,6 +138,26 @@ void givenMemberUpdateInfo_whenUpdatingMemberInfo_thenUpdate() { assertThat(updatedMemberDto.getGender()).isEqualTo(memberUpdateInfo.getGender()); } + @DisplayName("수정할 회원 정보 중 이미 사용중인 닉네임이 주어지고, 회원 정보를 수정하면, 예외가 발생한다.") + @Test + void givenNicknameThatAlreadyExists_whenUpdatingMemberInfo_thenThrowNicknameDuplicationException() { + // given + long memberId = 1L; + Member member = createMember(memberId); + MemberUpdateRequest memberUpdateInfo = new MemberUpdateRequest("update", LocalDate.of(2020, 1, 1), Gender.ETC, createMockMultipartFile()); + given(memberQueryService.getById(memberId)).willReturn(member); + given(memberQueryService.existsByNickname(memberUpdateInfo.getNickname())).willReturn(true); + + // when + Throwable t = catchThrowable(() -> sut.update(memberId, memberUpdateInfo)); + + // then + then(memberQueryService).should().getById(memberId); + then(memberQueryService).should().existsByNickname(memberUpdateInfo.getNickname()); + verifyEveryMocksShouldHaveNoMoreInteractions(); + assertThat(t).isInstanceOf(NicknameDuplicationException.class); + } + @DisplayName("선호 음식 카테고리 목록이 주어지고, 이를 업데이트하면, 선호 음식 카테고리를 수정한다.") @Test void givenFavoriteFoodCategories_whenUpdatingFavoriteFoodCategories_thenUpdate() { @@ -214,7 +237,7 @@ private void verifyEveryMocksShouldHaveNoMoreInteractions() { } private Member createDeletedMember(long memberId) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", @@ -237,7 +260,7 @@ private Member createMember(long memberId) { } private Member createMember(long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", @@ -292,7 +315,7 @@ private MemberDto createNewMemberDto(String socialUid, Set roleTypes) LoginType.KAKAO, roleTypes, "email", - "nickname" + socialUid, + "name" + socialUid, null, null ); diff --git a/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberQueryServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberQueryServiceTest.java index 13d6b415..4ff7b1da 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberQueryServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/member/service/MemberQueryServiceTest.java @@ -161,7 +161,7 @@ private Member createMember(long memberId) { } private Member createMember(long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/menu_keyword/api/MenuKeywordControllerTest.java b/src/test/java/com/zelusik/eatery/unit/domain/menu_keyword/api/MenuKeywordControllerTest.java index f71e8e10..576cbc4c 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/menu_keyword/api/MenuKeywordControllerTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/menu_keyword/api/MenuKeywordControllerTest.java @@ -1,18 +1,18 @@ package com.zelusik.eatery.unit.domain.menu_keyword.api; import com.zelusik.eatery.config.TestSecurityConfig; -import com.zelusik.eatery.global.common.constant.EateryConstants; -import com.zelusik.eatery.global.common.constant.FoodCategoryValue; import com.zelusik.eatery.domain.member.constant.Gender; import com.zelusik.eatery.domain.member.constant.LoginType; import com.zelusik.eatery.domain.member.constant.RoleType; -import com.zelusik.eatery.domain.menu_keyword.api.MenuKeywordController; -import com.zelusik.eatery.domain.place.entity.PlaceCategory; -import com.zelusik.eatery.global.common.dto.ListDto; import com.zelusik.eatery.domain.member.dto.MemberDto; +import com.zelusik.eatery.domain.menu_keyword.api.MenuKeywordController; import com.zelusik.eatery.domain.menu_keyword.dto.response.MenuKeywordResponse; -import com.zelusik.eatery.global.auth.UserPrincipal; import com.zelusik.eatery.domain.menu_keyword.service.MenuKeywordQueryService; +import com.zelusik.eatery.domain.place.entity.PlaceCategory; +import com.zelusik.eatery.global.auth.UserPrincipal; +import com.zelusik.eatery.global.common.constant.EateryConstants; +import com.zelusik.eatery.global.common.constant.FoodCategoryValue; +import com.zelusik.eatery.global.common.dto.ListDto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,9 +29,9 @@ import java.util.List; import java.util.Set; -import static com.zelusik.eatery.global.common.constant.EateryConstants.API_MINOR_VERSION_HEADER_NAME; import static com.zelusik.eatery.domain.review_keyword.constant.MenuKeywordCategory.MENU_NAME; import static com.zelusik.eatery.domain.review_keyword.constant.MenuKeywordCategory.PLACE_CATEGORY; +import static com.zelusik.eatery.global.common.constant.EateryConstants.API_MINOR_VERSION_HEADER_NAME; import static org.hamcrest.Matchers.hasSize; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; diff --git a/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageCommandServiceTest.java index c4c10b59..ff35d491 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageCommandServiceTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.util.ReflectionUtils; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -75,7 +76,7 @@ void givenProfileImage_whenSoftDeleting_thenUpdateDeletedAt() { } private Member createMember(long memberId) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageQueryServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageQueryServiceTest.java index ee62b0a6..849e22e6 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageQueryServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/profile_image/service/ProfileImageQueryServiceTest.java @@ -52,7 +52,7 @@ void givenMember_whenFindingByMember_thenReturnOptionalProfileImage() { } private Member createMember(long memberId) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/recommended_review/service/RecommendedReviewCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/recommended_review/service/RecommendedReviewCommandServiceTest.java index e10b1894..6b62f811 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/recommended_review/service/RecommendedReviewCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/recommended_review/service/RecommendedReviewCommandServiceTest.java @@ -136,7 +136,7 @@ private Member createMember(Long memberId) { } private Member createMember(Long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewCommandServiceTest.java index 5494297e..99bc15ee 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewCommandServiceTest.java @@ -192,7 +192,7 @@ private Member createMember(Long memberId) { } private Member createMember(Long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewQueryServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewQueryServiceTest.java index b9e54db5..ba316389 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewQueryServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/review/service/ReviewQueryServiceTest.java @@ -166,7 +166,7 @@ private Member createMember(Long memberId) { } private Member createMember(Long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/review_image/service/ReviewImageCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/review_image/service/ReviewImageCommandServiceTest.java index fa227f03..48a02976 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/review_image/service/ReviewImageCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/review_image/service/ReviewImageCommandServiceTest.java @@ -123,7 +123,7 @@ private Member createMember(long memberId) { } private Member createMember(long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/domain/terms_info/service/TermsInfoCommandServiceTest.java b/src/test/java/com/zelusik/eatery/unit/domain/terms_info/service/TermsInfoCommandServiceTest.java index 2ead5fa2..14c88e9e 100644 --- a/src/test/java/com/zelusik/eatery/unit/domain/terms_info/service/TermsInfoCommandServiceTest.java +++ b/src/test/java/com/zelusik/eatery/unit/domain/terms_info/service/TermsInfoCommandServiceTest.java @@ -98,7 +98,7 @@ private Member createMember(long memberId) { } private Member createMember(long memberId, Set roleTypes) { - return Member.of( + return new Member( memberId, "profile image url", "profile thunmbnail image url", diff --git a/src/test/java/com/zelusik/eatery/unit/global/util/NicknameGeneratorTest.java b/src/test/java/com/zelusik/eatery/unit/global/util/NicknameGeneratorTest.java new file mode 100644 index 00000000..a59f727f --- /dev/null +++ b/src/test/java/com/zelusik/eatery/unit/global/util/NicknameGeneratorTest.java @@ -0,0 +1,5 @@ +package com.zelusik.eatery.unit.global.util; + +class NicknameGeneratorTest { + +} \ No newline at end of file