diff --git a/be/src/main/java/yeonba/be/exception/JoinException.java b/be/src/main/java/yeonba/be/exception/JoinException.java
index 0faad4be..43d97cff 100644
--- a/be/src/main/java/yeonba/be/exception/JoinException.java
+++ b/be/src/main/java/yeonba/be/exception/JoinException.java
@@ -1,13 +1,13 @@
package yeonba.be.exception;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
import org.springframework.http.HttpStatus;
+@Getter
+@AllArgsConstructor
public enum JoinException implements BaseException {
- PASSWORD_CONFIRMATION_NOT_MATCH(
- HttpStatus.BAD_REQUEST,
- "비밀번호 확인 값이 비밀번호와 일치하지 않습니다."),
-
ALREADY_USED_NICKNAME(
HttpStatus.BAD_REQUEST,
"이미 사용 중인 닉네임입니다."),
@@ -18,22 +18,4 @@ public enum JoinException implements BaseException {
private final HttpStatus httpStatus;
private final String reason;
-
- JoinException(HttpStatus httpStatus, String reason) {
-
- this.httpStatus = httpStatus;
- this.reason = reason;
- }
-
- @Override
- public HttpStatus getHttpStatus() {
-
- return httpStatus;
- }
-
- @Override
- public String getReason() {
-
- return reason;
- }
}
diff --git a/be/src/main/java/yeonba/be/mypage/entity/Acquaintance.java b/be/src/main/java/yeonba/be/mypage/entity/Acquaintance.java
index ac9edd20..e9a1f32b 100644
--- a/be/src/main/java/yeonba/be/mypage/entity/Acquaintance.java
+++ b/be/src/main/java/yeonba/be/mypage/entity/Acquaintance.java
@@ -1,10 +1,12 @@
package yeonba.be.mypage.entity;
+import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
+import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -14,7 +16,7 @@
@Getter
@Entity
@EqualsAndHashCode(of = {"userId", "phoneNumber"})
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Acquaintance {
@@ -22,12 +24,16 @@ public class Acquaintance {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
+ @Column(nullable = false)
private long userId;
+
+ @Column(nullable = false)
private String name;
+
+ @Column(nullable = false)
private String phoneNumber;
public Acquaintance(long userId, String name, String phoneNumber) {
-
this.userId = userId;
this.name = name;
this.phoneNumber = phoneNumber;
diff --git a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java
index 395e41db..990400b8 100644
--- a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java
+++ b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java
@@ -1,9 +1,10 @@
package yeonba.be.mypage.service;
+import static yeonba.be.util.BoundsValidator.validateBounds;
+
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
@@ -35,10 +36,10 @@
import yeonba.be.user.entity.User;
import yeonba.be.user.entity.UserPreference;
import yeonba.be.user.entity.VocalRange;
-import yeonba.be.user.repository.block.BlockCommand;
-import yeonba.be.user.repository.block.BlockQuery;
import yeonba.be.user.repository.animal.AnimalQuery;
import yeonba.be.user.repository.area.AreaQuery;
+import yeonba.be.user.repository.block.BlockCommand;
+import yeonba.be.user.repository.block.BlockQuery;
import yeonba.be.user.repository.user.UserQuery;
import yeonba.be.user.repository.userpreference.UserPreferenceQuery;
import yeonba.be.user.repository.vocalrange.VocalRangeQuery;
@@ -172,18 +173,6 @@ private Area findAreaByName(List areas, String name) {
.orElseThrow(() -> new GeneralException(UserException.AREA_NOT_FOUND));
}
- private void validateBounds(Integer lowerBound, Integer upperBound) {
-
- if (Objects.isNull(lowerBound) || Objects.isNull(upperBound)) {
-
- return;
- }
-
- if (lowerBound > upperBound) {
- throw new GeneralException(UserException.LOWER_BOUND_LESS_THAN_OR_EQUAL_UPPER_BOUND);
- }
- }
-
public void updateProfilePhotos(List profilePhotos, MultipartFile realTimePhoto,
long userId) {
diff --git a/be/src/main/java/yeonba/be/user/controller/UserController.java b/be/src/main/java/yeonba/be/user/controller/UserController.java
index d867e423..3d47b36e 100644
--- a/be/src/main/java/yeonba/be/user/controller/UserController.java
+++ b/be/src/main/java/yeonba/be/user/controller/UserController.java
@@ -20,6 +20,7 @@
import yeonba.be.mypage.service.ReportService;
import yeonba.be.user.dto.request.UserQueryRequest;
import yeonba.be.user.dto.request.UserReportRequest;
+import yeonba.be.user.dto.request.UserSearchRequest;
import yeonba.be.user.dto.request.UserUpdateDeviceTokenRequest;
import yeonba.be.user.dto.response.UserProfileResponse;
import yeonba.be.user.dto.response.UserQueryPageResponse;
@@ -54,7 +55,7 @@ public ResponseEntity> getUsers(
@Operation(summary = "추천 이성 조회", description = "추천 이성을 조회할 수 있다.")
@ApiResponse(responseCode = "200", description = "추천 이성 정상 조회")
- @GetMapping("/users/recommend")
+ @PostMapping("/users/recommend")
public ResponseEntity> getRecommendUsers(
@RequestAttribute("userId") long userId) {
@@ -145,6 +146,20 @@ public ResponseEntity> block(
.body(new CustomResponse<>());
}
+ @Operation(summary = "이성 검색", description = "이성을 검색할 수 있습니다.")
+ @ApiResponse(responseCode = "200", description = "이성 검색 성공")
+ @PostMapping("/users/search")
+ public ResponseEntity> search(
+ @RequestAttribute("userId") long userId,
+ @Valid @RequestBody(required = false) UserSearchRequest request) {
+
+ UserQueryPageResponse response = userService.findUsersBySearchCondition(userId, request);
+
+ return ResponseEntity
+ .ok()
+ .body(new CustomResponse<>(response));
+ }
+
@Operation(summary = "device token 업데이트", description = "device token을 업데이트할 수 있습니다.")
@ApiResponse(responseCode = "200", description = "device token 업데이트 성공")
@PatchMapping("/users/device-token")
diff --git a/be/src/main/java/yeonba/be/user/dto/request/UserSearchRequest.java b/be/src/main/java/yeonba/be/user/dto/request/UserSearchRequest.java
new file mode 100644
index 00000000..f044b21a
--- /dev/null
+++ b/be/src/main/java/yeonba/be/user/dto/request/UserSearchRequest.java
@@ -0,0 +1,65 @@
+package yeonba.be.user.dto.request;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Range;
+
+@Getter
+@NoArgsConstructor
+public class UserSearchRequest {
+
+ @Schema(
+ type = "number",
+ description = "페이지 번호, 기본 첫 페이지(0)",
+ example = "0")
+ @PositiveOrZero(message = "페이지 번호는 0이상이어야 합니다.")
+ private Integer page;
+
+ @Schema(
+ type = "string",
+ description = "검색 지역",
+ example = "서울")
+ private String area;
+
+ @Schema(
+ type = "string",
+ description = "검색 음역대",
+ example = "고음")
+ private String vocalRange;
+
+ @Schema(
+ type = "number",
+ description = "검색 나이 하한",
+ example = "20")
+ @Range(min = 20, max = 40, message = "검색 나이는 20~40내 값만 가능합니다.")
+ private Integer ageLowerBound;
+
+ @Schema(
+ type = "number",
+ description = "검색 나이 상한",
+ example = "30")
+ @Range(min = 20, max = 40, message = "검색 나이는 20~40내 값만 가능합니다.")
+ private Integer ageUpperBound;
+
+ @Schema(
+ type = "number",
+ description = "검색 키 하한",
+ example = "160")
+ @Range(min = 130, max = 220, message = "검색 키는 130~220cm 내 값만 가능합니다.")
+ private Integer heightLowerBound;
+
+ @Schema(
+ type = "number",
+ description = "검색 키 상한",
+ example = "170")
+ @Range(min = 130, max = 220, message = "검색 키는 130~220cm 내 값만 가능합니다.")
+ private Integer heightUpperBound;
+
+ @Schema(
+ type = "boolean",
+ description = "검색 기준에 선호하는 동물상 포함 여부",
+ example = "true")
+ private Boolean includePreferredAnimal;
+}
diff --git a/be/src/main/java/yeonba/be/user/dto/response/UserQueryPageResponse.java b/be/src/main/java/yeonba/be/user/dto/response/UserQueryPageResponse.java
index c515089f..e678a83b 100644
--- a/be/src/main/java/yeonba/be/user/dto/response/UserQueryPageResponse.java
+++ b/be/src/main/java/yeonba/be/user/dto/response/UserQueryPageResponse.java
@@ -3,12 +3,13 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
+import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.domain.Page;
@Getter
-@AllArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class UserQueryPageResponse {
@Schema(
diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java
index a29e6a69..6dca8fb3 100644
--- a/be/src/main/java/yeonba/be/user/entity/User.java
+++ b/be/src/main/java/yeonba/be/user/entity/User.java
@@ -215,7 +215,6 @@ public void changeInactiveStatus(boolean inactiveStatus) {
}
public void updateProfilePhotos(List profilePhotos) {
-
this.profilePhotos = profilePhotos;
}
diff --git a/be/src/main/java/yeonba/be/user/entity/UserPreference.java b/be/src/main/java/yeonba/be/user/entity/UserPreference.java
index 3589515b..d78e34d9 100644
--- a/be/src/main/java/yeonba/be/user/entity/UserPreference.java
+++ b/be/src/main/java/yeonba/be/user/entity/UserPreference.java
@@ -54,10 +54,10 @@ public class UserPreference {
private Animal animal;
public UserPreference(
- int ageLowerBound,
- int ageUpperBound,
- int heightLowerBound,
- int heightUpperBound,
+ Integer ageLowerBound,
+ Integer ageUpperBound,
+ Integer heightLowerBound,
+ Integer heightUpperBound,
String mbti,
String bodyType,
User user,
diff --git a/be/src/main/java/yeonba/be/user/enums/Gender.java b/be/src/main/java/yeonba/be/user/enums/Gender.java
index 0b2e69f9..ce7df7c6 100644
--- a/be/src/main/java/yeonba/be/user/enums/Gender.java
+++ b/be/src/main/java/yeonba/be/user/enums/Gender.java
@@ -13,6 +13,7 @@ public enum Gender {
public final String genderString;
public final boolean genderBoolean;
+
public static Gender from(String genderString) {
if (StringUtils.equals(genderString, MALE.genderString)) {
diff --git a/be/src/main/java/yeonba/be/user/repository/user/UserQuery.java b/be/src/main/java/yeonba/be/user/repository/user/UserQuery.java
index 877da017..e2ce93eb 100644
--- a/be/src/main/java/yeonba/be/user/repository/user/UserQuery.java
+++ b/be/src/main/java/yeonba/be/user/repository/user/UserQuery.java
@@ -9,6 +9,7 @@
import org.springframework.stereotype.Component;
import yeonba.be.exception.GeneralException;
import yeonba.be.exception.UserException;
+import yeonba.be.user.dto.request.UserSearchRequest;
import yeonba.be.user.dto.response.UserQueryPageResponse;
import yeonba.be.user.dto.response.UserQueryResponse;
import yeonba.be.user.entity.User;
@@ -73,10 +74,22 @@ public List findByIds(List userIds) {
}
public UserQueryPageResponse findRecommendUsers(
- long userId, boolean userGender, PageRequest pageRequest, LocalDate recommendDay) {
+ User user, PageRequest pageRequest, LocalDate recommendDay) {
Page page = userRepository
- .findRecommendUsers(userId, userGender, pageRequest, recommendDay);
+ .findRecommendUsers(user, pageRequest, recommendDay);
+
+ return UserQueryPageResponse.from(page);
+ }
+
+ public UserQueryPageResponse findUsersBySearchCondition(
+ User user,
+ PageRequest pageRequest,
+ LocalDate searchDay,
+ UserSearchRequest request) {
+
+ Page page = userRepository
+ .findUsersBySearchCondition(user, pageRequest, searchDay, request);
return UserQueryPageResponse.from(page);
}
diff --git a/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryCustom.java b/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryCustom.java
index 30ca5753..cf3a1600 100644
--- a/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryCustom.java
+++ b/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryCustom.java
@@ -3,7 +3,9 @@
import java.time.LocalDate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
+import yeonba.be.user.dto.request.UserSearchRequest;
import yeonba.be.user.dto.response.UserQueryResponse;
+import yeonba.be.user.entity.User;
public interface UserRepositoryCustom {
@@ -14,8 +16,13 @@ public interface UserRepositoryCustom {
Page findArrowSendersBy(long receiverId, PageRequest pageRequest);
Page findRecommendUsers(
- long userId,
- boolean userGender,
+ User queryingUser,
PageRequest pageRequest,
LocalDate recommendDay);
-}
\ No newline at end of file
+
+ Page findUsersBySearchCondition(
+ User searchingUser,
+ PageRequest pageRequest,
+ LocalDate searchDay,
+ UserSearchRequest request);
+}
diff --git a/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryImpl.java b/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryImpl.java
index be1e39c8..061dac0a 100644
--- a/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryImpl.java
+++ b/be/src/main/java/yeonba/be/user/repository/user/UserRepositoryImpl.java
@@ -25,11 +25,16 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
+import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.util.StringUtils;
+import yeonba.be.arrow.enums.ArrowTransactionType;
+import yeonba.be.user.dto.request.UserSearchRequest;
import yeonba.be.user.dto.response.UserQueryResponse;
+import yeonba.be.user.entity.User;
import yeonba.be.user.entity.UserPreference;
@RequiredArgsConstructor
@@ -43,8 +48,7 @@ public Page findFavoritesBy(long userId, PageRequest pageRequ
int limit = pageRequest.getPageSize();
int offset = pageRequest.getPageNumber() * limit;
- List content = selectUserQueryResponse(
- Expressions.constant(true))
+ List content = selectUserQueryResponse(Expressions.constant(true))
.where(findFavoritesCondition(userId))
.limit(limit)
.offset(offset)
@@ -125,8 +129,7 @@ private BooleanExpression findArrowSendersCondition(long receiverId) {
@Override
public Page findRecommendUsers(
- long userId,
- boolean userGender,
+ User queryingUser,
PageRequest pageRequest,
LocalDate recommendDay) {
@@ -135,27 +138,26 @@ public Page findRecommendUsers(
// 추천 대상 사용자의 선호조건 조회
UserPreference preference = queryFactory.selectFrom(userPreference)
- .where(userPreference.user.id.eq(userId))
+ .where(userPreference.user.id.eq(queryingUser.getId()))
.fetchFirst();
List content = selectUserQueryResponse(
Expressions.constant(false))
- .where(recommendUserCondition(userId, userGender, preference, recommendDay))
+ .where(recommendUserCondition(queryingUser, preference, recommendDay))
.limit(limit)
.offset(offset)
.fetch();
JPAQuery countQuery = queryFactory.select(user.count())
.from(user)
- .where(recommendUserCondition(userId, userGender, preference, recommendDay));
+ .where(recommendUserCondition(queryingUser, preference, recommendDay));
return PageableExecutionUtils.getPage(content, pageRequest, countQuery::fetchOne);
}
/*
이성 추천시 배제되는 사용자
- - 자기 자신(조회하는 사용자)
- - 동성
+ - 같은 성별 사용자
- 추천(선호) 조건을 만족하지 않는 사용자
- 화살을 주고 받은 적이 있는 사용자
- 즐겨찾기한 사용자
@@ -165,14 +167,14 @@ public Page findRecommendUsers(
- 추천 일자에 검색된 적 있는 사용자
*/
private BooleanExpression recommendUserCondition(
- long userId,
- Boolean gender,
+ User queryingUser,
UserPreference preference,
LocalDate recommendDay) {
+ long userId = queryingUser.getId();
+
return Expressions.allOf(
- user.id.ne(userId),
- user.gender.ne(gender),
+ notSameGenderCondition(queryingUser.getGenderBoolean()),
isActiveAndNotDeletedUserCondition(),
isNotAcquaintanceCondition(userId),
isNotBlockedUserCondition(userId),
@@ -184,22 +186,115 @@ private BooleanExpression recommendUserCondition(
isNotUserSearchedOnDayCondition(userId, recommendDay));
}
- private JPQLQuery findOneArrowReceivedTransactionBy(long receiverId) {
+ @Override
+ public Page findUsersBySearchCondition(
+ User searchingUser,
+ PageRequest pageRequest,
+ LocalDate searchDay,
+ UserSearchRequest request) {
- return JPAExpressions.selectOne()
- .from(arrowTransaction)
- .where(
- arrowTransaction.receiver.id.eq(receiverId),
- arrowTransaction.sender.id.eq(user.id));
+ int limit = pageRequest.getPageSize();
+ int offset = pageRequest.getPageNumber() * limit;
+
+ BooleanExpression searchUserCondition =
+ searchUserCondition(searchingUser, request, searchDay);
+
+ List content = selectUserQueryResponse(
+ Expressions.as(findOneFavoriteBy(searchingUser.getId()).exists(), "isFavorite"))
+ .from(user)
+ .where(searchUserCondition)
+ .limit(limit)
+ .offset(offset)
+ .fetch();
+
+ JPAQuery countQuery = queryFactory.select(user.count())
+ .from(user)
+ .where(searchUserCondition);
+
+ return PageableExecutionUtils.getPage(content, pageRequest, countQuery::fetchOne);
+ }
+
+ /*
+ 이성 검색시 제외되는 사용자
+ - 같은 성별 사용자
+ - 지인
+ - 차단한 사용자
+ - 휴면, 삭제 상태 사용자
+ - 화실을 주고 받은 적 있는 사용자
+ - 검색일에 이미 검색된 적 있는 사용자
+ */
+ // TODO : 채팅 이력 있는 사용자 제외 조건 추가
+ private BooleanExpression searchUserCondition(
+ User searchingUser, UserSearchRequest request, LocalDate searchDay) {
+
+ long userId = searchingUser.getId();
+
+ return Expressions.allOf(
+ notSameGenderCondition(searchingUser.getGenderBoolean()),
+ isNotAcquaintanceCondition(userId),
+ isNotBlockedUserCondition(userId),
+ isActiveAndNotDeletedUserCondition(),
+ findOneArrowReceivedTransactionBy(userId).notExists(),
+ findOneArrowSentTransactionBy(userId).notExists(),
+ isNotUserRecommendedOnDayCondition(userId, searchDay),
+ isNotUserSearchedOnDayCondition(userId, searchDay),
+ searchCondition(userId, request)
+ );
+ }
+
+ private BooleanExpression searchCondition(long userId, UserSearchRequest request) {
+
+ if (Objects.isNull(request)) {
+
+ return null;
+ }
+
+ String area = request.getArea();
+ BooleanExpression userAreaEqualCondition =
+ StringUtils.hasText(area) ? user.area.name.eq(area) : null;
+
+ String vocalRange = request.getVocalRange();
+ BooleanExpression userVocalRangeEqualCondition =
+ StringUtils.hasText(vocalRange) ? user.vocalRange.classification.eq(vocalRange) : null;
+
+ BooleanExpression userAgeRangeCondition =
+ userAgeRangeCondition(request.getAgeLowerBound(), request.getAgeUpperBound());
+
+ BooleanExpression userHeightRangeCondition =
+ userHeightRangeCondition(request.getHeightLowerBound(), request.getHeightUpperBound());
+
+ BooleanExpression includePreferredAnimalCondition =
+ includePreferredAnimalCondition(userId, request.getIncludePreferredAnimal());
+
+ return Expressions.allOf(
+ userAreaEqualCondition,
+ userVocalRangeEqualCondition,
+ userAgeRangeCondition,
+ userHeightRangeCondition,
+ includePreferredAnimalCondition);
+ }
+
+ private BooleanExpression includePreferredAnimalCondition(
+ long userId, Boolean includePreferredAnimal) {
+
+ if (Objects.isNull(includePreferredAnimal) || !includePreferredAnimal) {
+
+ return null;
+ }
+
+ Long preferredAnimalId = queryFactory.select(userPreference.animal.id)
+ .from(userPreference)
+ .where(userPreference.user.id.eq(userId))
+ .fetchFirst();
+
+ return Objects.nonNull(preferredAnimalId) ? user.animal.id.eq(preferredAnimalId) : null;
}
private JPQLQuery findOneFavoriteBy(long userId) {
return JPAExpressions.selectOne()
.from(favorite)
- .where(
- favorite.user.id.eq(userId),
- favorite.favoriteUser.id.eq(user.id));
+ .where(favorite.user.id.eq(userId), favorite.favoriteUser.id.eq(user.id));
}
private JPQLQuery findOneArrowSentTransactionBy(long senderId) {
@@ -207,20 +302,33 @@ private JPQLQuery findOneArrowSentTransactionBy(long senderId) {
return JPAExpressions.selectOne()
.from(arrowTransaction)
.where(
+ arrowTransaction.type.eq(ArrowTransactionType.USER_TO_USER),
arrowTransaction.sender.id.eq(senderId),
arrowTransaction.receiver.id.eq(user.id));
}
+ private JPQLQuery findOneArrowReceivedTransactionBy(long receiverId) {
+
+ return JPAExpressions.selectOne()
+ .from(arrowTransaction)
+ .where(
+ arrowTransaction.type.eq(ArrowTransactionType.USER_TO_USER),
+ arrowTransaction.receiver.id.eq(receiverId),
+ arrowTransaction.sender.id.eq(user.id));
+ }
+
private BooleanExpression isUserSatisfiedPreferenceCondition(UserPreference preference) {
return Expressions.allOf(
- user.age.between(preference.getAgeLowerBound(), preference.getAgeUpperBound()),
- user.height.between(preference.getHeightLowerBound(), preference.getHeightUpperBound()),
user.mbti.eq(preference.getMbti()),
user.bodyType.eq(preference.getBodyType()),
user.vocalRange.id.eq(preference.getVocalRange().getId()),
user.area.id.eq(preference.getArea().getId()),
- user.animal.id.eq(preference.getAnimal().getId()));
+ user.animal.id.eq(preference.getAnimal().getId()),
+ userAgeRangeCondition(preference.getAgeLowerBound(), preference.getAgeUpperBound()),
+ userHeightRangeCondition(
+ preference.getHeightLowerBound(), preference.getHeightUpperBound())
+ );
}
private BooleanExpression isNotUserRecommendedOnDayCondition(
@@ -256,9 +364,8 @@ private BooleanExpression isNotUserSearchedOnDayCondition(
}
/*
- 응답 dto에 필요한 필드를 select하는 공통 사용 쿼리, 별도 분리
- 경우에 따라 즐겨찾기 등록 여부(favorite)을 상수로 주입하기에
- 해당 부분만 파라미터로 받도록 구성
+ 응답 dto에 필요한 필드를 select하는 공통 사용 쿼리, 사용하는 로직에 따라
+ 조회하는 사용자, 조회되는 사용자간 즐겨찾기 존재 여부를 상수로 주입, 혹은 서브 쿼리로 확인
*/
private JPAQuery selectUserQueryResponse(
Expression checkFavoriteExistsNestedQuery) {
@@ -285,6 +392,11 @@ private JPAQuery selectUserQueryResponse(
.where(isRepresentativeProfilePhotoCondition());
}
+ private BooleanExpression notSameGenderCondition(boolean userGender) {
+
+ return user.gender.ne(userGender);
+ }
+
private BooleanExpression isRepresentativeProfilePhotoCondition() {
return profilePhoto.id.eq(
@@ -295,8 +407,7 @@ private BooleanExpression isRepresentativeProfilePhotoCondition() {
private BooleanExpression isActiveAndNotDeletedUserCondition() {
- return user.deleted.isFalse()
- .and(user.inactive.isFalse());
+ return user.deleted.isFalse().and(user.inactive.isFalse());
}
private BooleanExpression isNotAcquaintanceCondition(long userId) {
@@ -318,4 +429,26 @@ private BooleanExpression isNotBlockedUserCondition(long userId) {
block.blockedUser.id.eq(user.id))
.notExists();
}
-}
\ No newline at end of file
+
+ private BooleanExpression userAgeRangeCondition(
+ Integer ageLowerBound, Integer ageUpperBound) {
+
+ BooleanExpression userAgeGoeCondition =
+ Objects.nonNull(ageLowerBound) ? user.age.goe(ageLowerBound) : null;
+ BooleanExpression userAgeLoeCondition =
+ Objects.nonNull(ageUpperBound) ? user.age.loe(ageUpperBound) : null;
+
+ return Expressions.allOf(userAgeLoeCondition, userAgeGoeCondition);
+ }
+
+ private BooleanExpression userHeightRangeCondition(
+ Integer heightLowerBound, Integer heightUpperBound) {
+
+ BooleanExpression userHeightGoeCondition =
+ Objects.nonNull(heightLowerBound) ? user.height.goe(heightLowerBound) : null;
+ BooleanExpression userHeightLoeCondition =
+ Objects.nonNull(heightUpperBound) ? user.height.loe(heightUpperBound) : null;
+
+ return Expressions.allOf(userHeightGoeCondition, userHeightLoeCondition);
+ }
+}
diff --git a/be/src/main/java/yeonba/be/user/repository/userpreference/UserPreferenceRepository.java b/be/src/main/java/yeonba/be/user/repository/userpreference/UserPreferenceRepository.java
index 43f1ad8d..b6d7fc86 100644
--- a/be/src/main/java/yeonba/be/user/repository/userpreference/UserPreferenceRepository.java
+++ b/be/src/main/java/yeonba/be/user/repository/userpreference/UserPreferenceRepository.java
@@ -10,5 +10,4 @@
public interface UserPreferenceRepository extends JpaRepository {
Optional findFirstByUser(User user);
-
}
diff --git a/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogCommand.java b/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogCommand.java
new file mode 100644
index 00000000..e57b7965
--- /dev/null
+++ b/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogCommand.java
@@ -0,0 +1,18 @@
+package yeonba.be.user.repository.usersearchlog;
+
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import yeonba.be.user.entity.UserSearchLog;
+
+@Component
+@RequiredArgsConstructor
+public class UserSearchLogCommand {
+
+ private final UserSearchLogRepository userSearchLogRepository;
+
+ public List saveAll(List userSearchLogs) {
+
+ return userSearchLogRepository.saveAll(userSearchLogs);
+ }
+}
diff --git a/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogRepository.java b/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogRepository.java
new file mode 100644
index 00000000..8a67b2cf
--- /dev/null
+++ b/be/src/main/java/yeonba/be/user/repository/usersearchlog/UserSearchLogRepository.java
@@ -0,0 +1,10 @@
+package yeonba.be.user.repository.usersearchlog;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import yeonba.be.user.entity.UserSearchLog;
+
+@Repository
+public interface UserSearchLogRepository extends JpaRepository {
+
+}
diff --git a/be/src/main/java/yeonba/be/user/service/UserService.java b/be/src/main/java/yeonba/be/user/service/UserService.java
index 991c44b7..081a0127 100644
--- a/be/src/main/java/yeonba/be/user/service/UserService.java
+++ b/be/src/main/java/yeonba/be/user/service/UserService.java
@@ -1,8 +1,11 @@
package yeonba.be.user.service;
+import static yeonba.be.util.BoundsValidator.validateBounds;
+
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@@ -16,6 +19,7 @@
import yeonba.be.exception.UserException;
import yeonba.be.login.dto.request.UserJoinRequest;
import yeonba.be.user.dto.request.UserQueryRequest;
+import yeonba.be.user.dto.request.UserSearchRequest;
import yeonba.be.user.dto.request.UserUpdateDeviceTokenRequest;
import yeonba.be.user.dto.response.UserProfileResponse;
import yeonba.be.user.dto.response.UserQueryPageResponse;
@@ -26,6 +30,7 @@
import yeonba.be.user.entity.User;
import yeonba.be.user.entity.UserPreference;
import yeonba.be.user.entity.UserRecommendation;
+import yeonba.be.user.entity.UserSearchLog;
import yeonba.be.user.entity.VocalRange;
import yeonba.be.user.enums.Gender;
import yeonba.be.user.enums.LoginType;
@@ -37,6 +42,7 @@
import yeonba.be.user.repository.userpreference.UserPreferenceCommand;
import yeonba.be.user.repository.userrecommendation.UserRecommendationCommand;
import yeonba.be.user.repository.userrecommendation.UserRecommendationQuery;
+import yeonba.be.user.repository.usersearchlog.UserSearchLogCommand;
import yeonba.be.user.repository.vocalrange.VocalRangeQuery;
import yeonba.be.util.AgeValidator;
import yeonba.be.util.S3Service;
@@ -49,6 +55,7 @@ public class UserService {
private final UserCommand userCommand;
private final UserPreferenceCommand userPreferenceCommand;
private final UserRecommendationCommand userRecommendationCommand;
+ private final UserSearchLogCommand userSearchLogCommand;
private final AnimalQuery animalQuery;
private final AreaQuery areaQuery;
@@ -173,7 +180,7 @@ public void saveUserPreference(User user, UserJoinRequest request) {
public UserQueryPageResponse findUsersByQueryCondition(long userId, UserQueryRequest request) {
int page = Optional.ofNullable(request.getPage()).orElse(0);
- int size = 6;
+ int size = 30;
PageRequest pageRequest = PageRequest.of(page, size);
// 사용자 존재 여부 검증
@@ -208,10 +215,9 @@ public UserQueryPageResponse findRecommendUsers(long userId, LocalDate recommend
// 추천 사용자 응답 조회,
int numberOfRecommendUsers = 2;
- boolean userGender = Gender.from(user.getGenderString()).genderBoolean;
PageRequest pageRequest = PageRequest.of(0, numberOfRecommendUsers);
UserQueryPageResponse response = userQuery
- .findRecommendUsers(userId, userGender, pageRequest, recommendDay);
+ .findRecommendUsers(user, pageRequest, recommendDay);
// 추천 가능 여부 확인(추천 가능한 사용자 2명 이상)
List content = response.getUsers();
@@ -220,10 +226,7 @@ public UserQueryPageResponse findRecommendUsers(long userId, LocalDate recommend
}
// 추천 사용자 조회
- List userIds = content.stream()
- .map(UserQueryResponse::getId)
- .toList();
- List recommendUsers = userQuery.findByIds(userIds);
+ List recommendUsers = findAllUsersInResponse(response);
// 추천 내역 저장
List userRecommendations = recommendUsers.stream()
@@ -240,4 +243,49 @@ public void updateDeviceToken(long userId, UserUpdateDeviceTokenRequest request)
User user = userQuery.findById(userId);
user.updateDeviceToken(request.getDeviceToken());
}
+
+ @Transactional
+ public UserQueryPageResponse findUsersBySearchCondition(long userId,
+ UserSearchRequest request) {
+
+ int page = 0;
+ if(Objects.nonNull(request) && Objects.nonNull(request.getPage())) {
+ page = request.getPage();
+
+ // 검색 나이/키 하한 <= 상한 여부 검증
+ validateBounds(request.getAgeLowerBound(), request.getAgeUpperBound());
+ validateBounds(request.getHeightLowerBound(), request.getHeightUpperBound());
+ }
+
+ int size = 30;
+ PageRequest pageRequest = PageRequest.of(page, size);
+ LocalDate searchDay = LocalDate.now();
+
+ // 검색하는 사용자 조회
+ User user = userQuery.findById(userId);
+
+ // 응답 조회
+ UserQueryPageResponse response = userQuery
+ .findUsersBySearchCondition(user, pageRequest, searchDay, request);
+
+ // 검색된 사용자 조회
+ List searchingUsers = findAllUsersInResponse(response);
+
+ // 검색 내역 저장
+ List userSearchLogs = searchingUsers.stream()
+ .map(searchingUser -> new UserSearchLog(user, searchingUser))
+ .toList();
+ userSearchLogCommand.saveAll(userSearchLogs);
+
+ return response;
+ }
+
+ private List findAllUsersInResponse(UserQueryPageResponse response) {
+
+ List userIds = response.getUsers().stream()
+ .map(UserQueryResponse::getId)
+ .toList();
+
+ return userQuery.findByIds(userIds);
+ }
}
diff --git a/be/src/main/java/yeonba/be/util/BoundsValidator.java b/be/src/main/java/yeonba/be/util/BoundsValidator.java
new file mode 100644
index 00000000..fb7bc087
--- /dev/null
+++ b/be/src/main/java/yeonba/be/util/BoundsValidator.java
@@ -0,0 +1,20 @@
+package yeonba.be.util;
+
+import java.util.Objects;
+import yeonba.be.exception.GeneralException;
+import yeonba.be.exception.UserException;
+
+public class BoundsValidator {
+
+ public static void validateBounds(Integer lowerBound, Integer upperBound) {
+
+ if (Objects.isNull(lowerBound) || Objects.isNull(upperBound)) {
+
+ return;
+ }
+
+ if (lowerBound > upperBound) {
+ throw new GeneralException(UserException.LOWER_BOUND_LESS_THAN_OR_EQUAL_UPPER_BOUND);
+ }
+ }
+}
diff --git a/be/src/main/java/yeonba/be/util/JwtUtil.java b/be/src/main/java/yeonba/be/util/JwtUtil.java
index a3f9cc7e..8dfeac5d 100644
--- a/be/src/main/java/yeonba/be/util/JwtUtil.java
+++ b/be/src/main/java/yeonba/be/util/JwtUtil.java
@@ -50,7 +50,8 @@ public long getUserIdFromJwt(String token) {
} else if (userIdObject instanceof Integer) {
return ((Integer) userIdObject).longValue();
} else {
- throw new IllegalArgumentException("Unexpected type for userId: " + userIdObject.getClass().getName());
+ throw new IllegalArgumentException(
+ "Unexpected type for userId: " + userIdObject.getClass().getName());
}
}