-
Notifications
You must be signed in to change notification settings - Fork 0
Signup profile policy refresh + admin login response update #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/product
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.comatching.user.domain.auth.dto; | ||
|
|
||
| public record NicknameAvailabilityResponse( | ||
| boolean available | ||
| ) { | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,13 +9,10 @@ | |
| import java.time.LocalDate; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import com.comatching.common.domain.enums.ContactFrequency; | ||
| import com.comatching.common.domain.enums.Gender; | ||
| import com.comatching.common.domain.enums.HobbyCategory; | ||
| import com.comatching.common.domain.enums.ProfileTagCategory; | ||
| import com.comatching.common.domain.enums.SocialAccountType; | ||
| import com.comatching.common.exception.BusinessException; | ||
| import com.comatching.user.global.exception.UserErrorCode; | ||
|
|
@@ -161,7 +158,7 @@ public void update( | |
| } | ||
|
|
||
| public void addHobbies(List<ProfileHobby> newHobbies) { | ||
| if (newHobbies == null || newHobbies.isEmpty() || newHobbies.size() > 10 || newHobbies.size() < 1) { | ||
| if (newHobbies == null || newHobbies.isEmpty() || newHobbies.size() > 10 || newHobbies.size() < 2) { | ||
| throw new BusinessException(UserErrorCode.INVALID_HOBBY_COUNT); | ||
| } | ||
|
|
||
|
|
@@ -179,17 +176,8 @@ public List<HobbyCategory> getHobbyCategories() { | |
| } | ||
|
|
||
| public void addTags(List<ProfileTag> newTags) { | ||
| if (newTags != null) { | ||
| Map<ProfileTagCategory, Long> countByCategory = newTags.stream() | ||
| .collect(Collectors.groupingBy( | ||
| t -> t.getTag().getGroup().getCategory(), | ||
| Collectors.counting() | ||
| )); | ||
| countByCategory.forEach((cat, count) -> { | ||
| if (count > 3) { | ||
| throw new BusinessException(UserErrorCode.TAG_LIMIT_PER_CATEGORY_EXCEEDED); | ||
| } | ||
| }); | ||
| if (newTags != null && newTags.size() > 5) { | ||
| throw new BusinessException(UserErrorCode.TAG_LIMIT_PER_CATEGORY_EXCEEDED); | ||
| } | ||
|
Comment on lines
+179
to
181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로필 태그의 최대 개수 정책이 '카테고리별' 제한에서 '전체' 제한으로 변경되었습니다. 하지만 여기서 사용된 |
||
| this.tags.clear(); | ||
| if (newTags != null) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| package com.comatching.user.domain.member.service; | ||
|
|
||
| import java.util.List; | ||
| import java.util.concurrent.ThreadLocalRandom; | ||
| import java.util.Locale; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
@@ -15,8 +17,8 @@ | |
| import com.comatching.common.dto.member.ProfileResponse; | ||
| import com.comatching.common.dto.member.ProfileTagDto; | ||
| import com.comatching.common.exception.BusinessException; | ||
| import com.comatching.common.service.S3Service; | ||
| import com.comatching.user.domain.event.UserEventPublisher; | ||
| import com.comatching.user.domain.member.component.RandomNicknameGenerator; | ||
| import com.comatching.user.domain.member.dto.ProfileUpdateRequest; | ||
| import com.comatching.user.domain.member.entity.Member; | ||
| import com.comatching.user.domain.member.entity.Profile; | ||
|
|
@@ -34,11 +36,18 @@ | |
| @Transactional | ||
| public class ProfileServiceImpl implements ProfileCreateService, ProfileManageService { | ||
|
|
||
| private static final String DEFAULT_IMAGE_VALUE = "default"; | ||
| private static final String DEFAULT_IMAGE_PREFIX = "default_"; | ||
| private static final String DEFAULT_IMAGE_EXTENSION = ".png"; | ||
| private static final Set<String> DEFAULT_IMAGE_ANIMALS = Set.of( | ||
| "dog", "cat", "dinosaur", "otter", "bear", "fox", "penguin", "wolf", "rabbit", "snake", "horse", "frog" | ||
| ); | ||
|
|
||
| private final MemberRepository memberRepository; | ||
| private final ProfileRepository profileRepository; | ||
| private final UserEventPublisher eventPublisher; | ||
| private final ProfileImageProperties profileImageProperties; | ||
| private final RandomNicknameGenerator nicknameGenerator; | ||
| private final S3Service s3Service; | ||
|
|
||
| @Override | ||
| public ProfileResponse createProfile(Long memberId, ProfileCreateRequest request) { | ||
|
|
@@ -70,6 +79,13 @@ public ProfileResponse getProfile(Long memberId) { | |
| return toProfileResponse(profile); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional(readOnly = true) | ||
| public boolean isNicknameAvailable(String nickname) { | ||
| String normalizedNickname = normalizeNickname(nickname); | ||
| return !profileRepository.existsByNickname(normalizedNickname); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional(readOnly = true) | ||
| public List<ProfileResponse> getProfilesByIds(List<Long> memberIds) { | ||
|
|
@@ -87,11 +103,14 @@ public ProfileResponse updateProfile(Long memberId, ProfileUpdateRequest request | |
| Profile profile = profileRepository.findByMemberId(memberId) | ||
| .orElseThrow(() -> new BusinessException(UserErrorCode.PROFILE_NOT_EXISTS)); | ||
|
|
||
| String normalizedNickname = normalizeNicknameForUpdate(request.nickname(), profile.getNickname(), memberId); | ||
| String profileImageUrl = resolveProfileImageUrlForUpdate(request.profileImageUrl()); | ||
|
|
||
| profile.update( | ||
| request.nickname(), | ||
| normalizedNickname, | ||
| request.intro(), | ||
| request.mbti(), | ||
| request.profileImageUrl(), | ||
| profileImageUrl, | ||
| request.gender(), | ||
| request.birthDate(), | ||
| request.socialType(), | ||
|
|
@@ -139,13 +158,10 @@ private void publishMatchingEvent(Profile profile) { | |
|
|
||
| private Profile saveProfile(ProfileCreateRequest request, Member member) { | ||
|
|
||
| String finalNickname = normalizeNickname(request.nickname()); | ||
| validateNicknameDuplicateOnCreate(finalNickname); | ||
| String finalProfileImageUrl = resolveProfileImageUrl(request.profileImageKey()); | ||
|
|
||
| String finalNickname = request.nickname(); | ||
| if (!StringUtils.hasText(finalNickname)) { | ||
| finalNickname = nicknameGenerator.generate(); | ||
| } | ||
|
|
||
| Profile profile = Profile.builder() | ||
| .member(member) | ||
| .nickname(finalNickname) | ||
|
|
@@ -167,20 +183,72 @@ private Profile saveProfile(ProfileCreateRequest request, Member member) { | |
| return profileRepository.save(profile); | ||
| } | ||
|
|
||
| private String resolveProfileImageUrl(String inputImageKey) { | ||
| if (StringUtils.hasText(inputImageKey)) { | ||
| return profileImageProperties.baseUrl() + inputImageKey; | ||
| private String normalizeNickname(String nickname) { | ||
| if (!StringUtils.hasText(nickname)) { | ||
| throw new BusinessException(UserErrorCode.INVALID_NICKNAME); | ||
| } | ||
|
|
||
| List<String> defaults = profileImageProperties.filenames(); | ||
| if (defaults == null || defaults.isEmpty()) { | ||
| return nickname.trim(); | ||
| } | ||
|
|
||
| private String normalizeNicknameForUpdate(String nickname, String currentNickname, Long memberId) { | ||
| if (nickname == null) { | ||
| return null; | ||
| } | ||
|
|
||
| int randomIndex = ThreadLocalRandom.current().nextInt(defaults.size()); | ||
| String selectedFilename = defaults.get(randomIndex); | ||
| String normalizedNickname = normalizeNickname(nickname); | ||
| if (!Objects.equals(normalizedNickname, currentNickname) | ||
| && profileRepository.existsByNicknameAndMemberIdNot(normalizedNickname, memberId)) { | ||
| throw new BusinessException(UserErrorCode.DUPLICATE_NICKNAME); | ||
| } | ||
|
|
||
| return normalizedNickname; | ||
| } | ||
|
|
||
| private void validateNicknameDuplicateOnCreate(String nickname) { | ||
| if (profileRepository.existsByNickname(nickname)) { | ||
| throw new BusinessException(UserErrorCode.DUPLICATE_NICKNAME); | ||
| } | ||
| } | ||
|
|
||
| return profileImageProperties.baseUrl() + selectedFilename; | ||
| private String resolveProfileImageUrlForUpdate(String profileImageValue) { | ||
| if (profileImageValue == null) { | ||
| return null; | ||
| } | ||
| return resolveProfileImageUrl(profileImageValue); | ||
| } | ||
|
|
||
| private String resolveProfileImageUrl(String profileImageValue) { | ||
| if (!StringUtils.hasText(profileImageValue)) { | ||
| return buildDefaultProfileImageUrl(DEFAULT_IMAGE_VALUE + DEFAULT_IMAGE_EXTENSION); | ||
| } | ||
|
|
||
| String normalizedValue = profileImageValue.trim(); | ||
| String loweredValue = normalizedValue.toLowerCase(Locale.ROOT); | ||
|
|
||
| if (DEFAULT_IMAGE_VALUE.equals(loweredValue)) { | ||
| return buildDefaultProfileImageUrl(DEFAULT_IMAGE_VALUE + DEFAULT_IMAGE_EXTENSION); | ||
| } | ||
|
|
||
| if (loweredValue.startsWith(DEFAULT_IMAGE_PREFIX)) { | ||
| String animalName = loweredValue.substring(DEFAULT_IMAGE_PREFIX.length()); | ||
| if (DEFAULT_IMAGE_ANIMALS.contains(animalName)) { | ||
| return buildDefaultProfileImageUrl(animalName + DEFAULT_IMAGE_EXTENSION); | ||
| } | ||
| } | ||
|
Comment on lines
+233
to
+238
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if (loweredValue.startsWith(DEFAULT_IMAGE_PREFIX)) {
String animalName = loweredValue.substring(DEFAULT_IMAGE_PREFIX.length());
if (DEFAULT_IMAGE_ANIMALS.contains(animalName)) {
return buildDefaultProfileImageUrl(animalName + DEFAULT_IMAGE_EXTENSION);
} else {
// 정의되지 않은 동물 이름이 입력된 경우, 기본 이미지로 fallback 합니다.
return buildDefaultProfileImageUrl(DEFAULT_IMAGE_VALUE + DEFAULT_IMAGE_EXTENSION);
}
}References
|
||
|
|
||
| if (normalizedValue.startsWith("http://") || normalizedValue.startsWith("https://")) { | ||
| return normalizedValue; | ||
| } | ||
|
|
||
| return s3Service.getFileUrl(normalizedValue); | ||
| } | ||
|
|
||
| private String buildDefaultProfileImageUrl(String filename) { | ||
| if (!StringUtils.hasText(profileImageProperties.baseUrl())) { | ||
| return null; | ||
| } | ||
| return profileImageProperties.baseUrl() + filename; | ||
| } | ||
|
|
||
| private static List<ProfileHobby> getProfileHobbies(List<HobbyDto> hobbies) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
accessToken쿠키의domain설정을 주석 처리한 것은 로컬 개발 환경을 지원하기 위한 좋은 변경입니다. 하지만refreshToken쿠키(32행)에는 여전히.domain("comatching.site")설정이 남아있어 설정이 일관되지 않습니다. 이로 인해 로컬 환경에서refreshToken쿠키가 정상적으로 설정되지 않을 수 있습니다. 두 쿠키의domain설정을 일관되게 관리하는 것을 권장합니다.