Skip to content

Commit

Permalink
Merge pull request #454 from tukcomCD2024/feat/friend_tag-B-#452
Browse files Browse the repository at this point in the history
feat : 친구 태그 검색 개선
  • Loading branch information
GaBaljaintheroom authored May 29, 2024
2 parents 8b3b09f + a907a97 commit 550cd0f
Show file tree
Hide file tree
Showing 24 changed files with 338 additions and 95 deletions.
3 changes: 0 additions & 3 deletions backend/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ dependencies {
//aop
implementation 'org.springframework.boot:spring-boot-starter-aop'

//jnanoid
implementation 'com.aventrix.jnanoid:jnanoid:2.0.0'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse;
Expand All @@ -20,6 +21,7 @@
import site.timecapsulearchive.core.infra.sms.data.response.SmsApiResponse;
import site.timecapsulearchive.core.infra.sms.manager.SmsApiManager;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
Expand Down Expand Up @@ -109,6 +111,14 @@ private Long updateToVerifiedMember(final Long memberId, final byte[] plain) {

memberTemporaryRepository.delete(memberTemporary);

boolean isDuplicateTag = memberRepository.checkTagDuplication(memberTemporary.getTag());
if (isDuplicateTag) {
log.warn("member tag duplicate - email:{}, tag:{}", memberTemporary.getEmail(),
memberTemporary.getTag());
memberTemporary.updateTagLowerCaseSocialType();
log.warn("member tag update - tag: {}", memberTemporary.getTag());
}

final Member verifiedMember = memberTemporary.toMember(hashEncryptionManager.encrypt(plain),
aesEncryptionManager.encryptWithPrefixIV(plain));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ ResponseEntity<ApiSpec<SearchFriendsResponse>> searchMembersByPhones(
);

@Operation(
summary = "찬구 검색",
description = "친구의 tag로 친구 검색을 한다.",
summary = "친구 검색",
description = """
친구의 tag로 친구 검색을 한다.
<br>
태그가 일치하면 일치하는 태그를 가진 사용자를 일치하지 않으면 가장 비슷한 태그를 가진 사용자를 반환한다.
""",
security = {@SecurityRequirement(name = "user_token")},
tags = {"friend"}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.web.bind.annotation.RestController;
import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag;
import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest;
import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest;
import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse;
Expand Down Expand Up @@ -127,10 +128,13 @@ public ResponseEntity<ApiSpec<SearchTagFriendSummaryResponse>> searchFriendByTag
@AuthenticationPrincipal Long memberId,
@RequestParam(value = "friend_tag") final String tag
) {
SearchFriendSummaryDtoByTag searchResult = friendQueryService.searchFriend(
memberId, tag);

return ResponseEntity.ok(
ApiSpec.success(
SuccessCode.SUCCESS,
friendQueryService.searchFriend(memberId, tag)
searchResult.toResponse()
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import static site.timecapsulearchive.core.domain.member.entity.QMember.member;
import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup;

import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberTemplate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.ZonedDateTime;
import java.util.List;
Expand All @@ -27,6 +31,11 @@
@RequiredArgsConstructor
public class MemberFriendQueryRepositoryImpl implements MemberFriendQueryRepository {

private static final double MATCH_THRESHOLD = 0;
private static final String MATCH_AGAINST_FUNCTION = "function('match_against', {0}, {1})";
private static final String FRIEND_INVITE_TO_ME_PATH = "friendInviteToMe";
private static final String FRIEND_INVITE_TO_FRIEND_PATH = "friendInviteToFriend";

private final JPAQueryFactory jpaQueryFactory;

public Slice<FriendSummaryDto> findFriendsSlice(
Expand Down Expand Up @@ -121,8 +130,8 @@ public List<SearchFriendSummaryDto> findFriendsByPhone(
final Long memberId,
final List<byte[]> hashes
) {
final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend");
final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe");
final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH);
final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH);

return jpaQueryFactory
.select(
Expand Down Expand Up @@ -157,8 +166,21 @@ public Optional<SearchFriendSummaryDtoByTag> findFriendsByTag(
final Long memberId,
final String tag
) {
final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend");
final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe");
if (tag != null && tag.isBlank()) {
return Optional.empty();
}

final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH);
final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH);

NumberTemplate<Double> tagFullTextSearchTemplate = Expressions.numberTemplate(Double.class,
MATCH_AGAINST_FUNCTION,
member.tag,
tag);

OrderSpecifier<Boolean> tagFullyMatchFirstOrder = new CaseBuilder().when(member.tag.eq(tag))
.then(Boolean.TRUE)
.otherwise(Boolean.FALSE).desc();

return Optional.ofNullable(jpaQueryFactory
.select(
Expand All @@ -179,9 +201,11 @@ public Optional<SearchFriendSummaryDtoByTag> findFriendsByTag(
.on(friendInviteToFriend.friend.id.eq(member.id)
.and(friendInviteToFriend.owner.id.eq(memberId)))
.leftJoin(friendInviteToMe)
.on(friendInviteToMe.friend.id.eq(memberId)
.and(friendInviteToMe.owner.id.eq(member.id)))
.where(member.tag.eq(tag))
.on(friendInviteToMe.owner.id.eq(member.id)
.and(friendInviteToMe.friend.id.eq(memberId)))
.where(tagFullTextSearchTemplate.gt(MATCH_THRESHOLD))
.orderBy(tagFullyMatchFirstOrder, tagFullTextSearchTemplate.desc())
.limit(1L)
.fetchOne()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag;
import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest;
import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse;
import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException;
import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository;
import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper;
Expand Down Expand Up @@ -53,12 +52,8 @@ public List<SearchFriendSummaryDto> findFriendsByPhone(
return memberFriendRepository.findFriendsByPhone(memberId, hashes);
}

public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final String tag) {
final SearchFriendSummaryDtoByTag friendSummaryDto = memberFriendRepository
.findFriendsByTag(memberId, tag).orElseThrow(FriendNotFoundException::new);

return friendSummaryDto.toResponse();
public SearchFriendSummaryDtoByTag searchFriend(final Long memberId, final String tag) {
return memberFriendRepository.findFriendsByTag(memberId, tag)
.orElseThrow(FriendNotFoundException::new);
}


}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package site.timecapsulearchive.core.domain.member.data.dto;

import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import site.timecapsulearchive.core.domain.member.entity.MemberTemporary;
import site.timecapsulearchive.core.domain.member.entity.SocialType;
import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil;
Expand All @@ -12,14 +11,14 @@ public record SignUpRequestDto(
SocialType socialType
) {

public MemberTemporary toMemberTemporary() {
public MemberTemporary toMemberTemporary(final String tag) {
return MemberTemporary.builder()
.authId(authId)
.nickname(MakeRandomNickNameUtil.makeRandomNickName())
.email(email)
.profileUrl(profileUrl)
.socialType(socialType)
.tag(NanoIdUtils.randomNanoId())
.tag(tag)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package site.timecapsulearchive.core.domain.member.data.mapper;

import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import java.time.ZoneId;
import java.util.List;
import java.util.UUID;
Expand All @@ -12,6 +11,7 @@
import site.timecapsulearchive.core.domain.member.entity.Member;
import site.timecapsulearchive.core.domain.member.entity.SocialType;
import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo;
import site.timecapsulearchive.core.global.util.TagGenerator;
import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil;
import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager;

Expand All @@ -33,7 +33,7 @@ public Member OAuthToEntity(
.email(oAuth2UserInfo.getEmail())
.profileUrl(oAuth2UserInfo.getImageUrl())
.socialType(socialType)
.tag(NanoIdUtils.randomNanoId())
.tag(TagGenerator.generate(oAuth2UserInfo.getEmail(), socialType))
.build();
}

Expand Down Expand Up @@ -64,13 +64,15 @@ public MemberNotificationSliceResponse notificationSliceToResponse(
}

public Member createMemberWithEmail(String email, String password) {
SocialType socialType = SocialType.EMAIL;

return Member.builder()
.email(email)
.password(password)
.authId(String.valueOf(UUID.randomUUID()))
.profileUrl("")
.socialType(SocialType.EMAIL)
.tag(NanoIdUtils.randomNanoId())
.socialType(socialType)
.tag(TagGenerator.generate(email, socialType))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup;
import site.timecapsulearchive.core.global.entity.BaseEntity;
import site.timecapsulearchive.core.global.util.NullCheck;
import site.timecapsulearchive.core.global.util.TagGenerator;

@Entity
@Getter
Expand Down Expand Up @@ -107,4 +108,7 @@ private Member(String profileUrl, String nickname, SocialType socialType, String
this.phone_hash = phone_hash;
}

public void updateTagLowerCaseSocialType() {
this.tag = TagGenerator.lowercase(email, socialType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import lombok.NoArgsConstructor;
import site.timecapsulearchive.core.global.entity.BaseEntity;
import site.timecapsulearchive.core.global.util.NullCheck;
import site.timecapsulearchive.core.global.util.TagGenerator;

@Entity
@Getter
Expand Down Expand Up @@ -73,4 +74,8 @@ public Member toMember(final byte[] phone_hash, final byte[] phone) {
.phone(phone)
.build();
}

public void updateTagLowerCaseSocialType() {
this.tag = TagGenerator.lowercase(email, socialType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ List<MemberNotificationDto> findMemberNotificationDtos(
Optional<Boolean> findIsAlarmByMemberId(final Long memberId);

List<Long> findMemberIdsByIds(List<Long> ids);

boolean checkTagDuplication(String tag);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class MemberQueryRepositoryImpl implements MemberQueryRepository {

private final JPAQueryFactory query;

@Override
public Boolean findIsVerifiedByAuthIdAndSocialType(
final String authId,
final SocialType socialType
Expand All @@ -40,6 +41,7 @@ public Boolean findIsVerifiedByAuthIdAndSocialType(
.fetchOne();
}

@Override
public Optional<VerifiedCheckDto> findVerifiedCheckDtoByAuthIdAndSocialType(
final String authId,
final SocialType socialType
Expand All @@ -59,6 +61,7 @@ public Optional<VerifiedCheckDto> findVerifiedCheckDtoByAuthIdAndSocialType(
);
}

@Override
public Optional<MemberDetailDto> findMemberDetailResponseDtoById(final Long memberId) {
return Optional.ofNullable(
query
Expand All @@ -85,6 +88,7 @@ private NumberExpression<Long> countDistinct(final NumberExpression<Long> expres
return Expressions.numberTemplate(Long.class, "COUNT(DISTINCT {0})", expression);
}

@Override
public Slice<MemberNotificationDto> findNotificationSliceByMemberId(
final Long memberId,
final int size,
Expand All @@ -101,6 +105,7 @@ public Slice<MemberNotificationDto> findNotificationSliceByMemberId(
return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext);
}

@Override
public List<MemberNotificationDto> findMemberNotificationDtos(
final Long memberId,
final int size,
Expand All @@ -126,6 +131,7 @@ public List<MemberNotificationDto> findMemberNotificationDtos(
.fetch();
}

@Override
public Optional<EmailVerifiedCheckDto> findEmailVerifiedCheckDtoByEmail(
final String email
) {
Expand All @@ -146,6 +152,7 @@ public Optional<EmailVerifiedCheckDto> findEmailVerifiedCheckDtoByEmail(
);
}

@Override
public Boolean checkEmailDuplication(final String email) {
final Integer count = query.selectOne()
.from(member)
Expand All @@ -155,6 +162,7 @@ public Boolean checkEmailDuplication(final String email) {
return count != null;
}

@Override
public Optional<Boolean> findIsAlarmByMemberId(final Long memberId) {
return Optional.ofNullable(
query.select(member.notificationEnabled)
Expand All @@ -164,11 +172,22 @@ public Optional<Boolean> findIsAlarmByMemberId(final Long memberId) {
);
}

@Override
public List<Long> findMemberIdsByIds(List<Long> ids) {
return query
.select(member.id)
.from(member)
.where(member.id.in(ids))
.fetch();
}

@Override
public boolean checkTagDuplication(String tag) {
final Integer count = query.selectOne()
.from(member)
.where(member.tag.eq(tag))
.fetchFirst();

return count != null;
}
}
Loading

0 comments on commit 550cd0f

Please sign in to comment.