diff --git a/src/main/java/site/kikihi/custom/global/response/page/SliceResponse.java b/src/main/java/site/kikihi/custom/global/response/page/SliceResponse.java index 2342ffc..892d307 100644 --- a/src/main/java/site/kikihi/custom/global/response/page/SliceResponse.java +++ b/src/main/java/site/kikihi/custom/global/response/page/SliceResponse.java @@ -1,12 +1,18 @@ package site.kikihi.custom.global.response.page; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import org.springframework.data.domain.Slice; import java.util.List; +/// null 값은 직렬화에서 제외 + @Builder +@JsonInclude(JsonInclude.Include.NON_NULL) public record SliceResponse( + + Long totalCount, List content, boolean hasNext, int page, @@ -14,10 +20,22 @@ public record SliceResponse( ) { public static SliceResponse from(Slice slice) { return SliceResponse.builder() + .totalCount(null) .content(slice.getContent()) .hasNext(slice.hasNext()) .page(slice.getNumber() + 1) .size(slice.getSize()) .build(); } + + public static SliceResponse from(Slice slice, long totalCount) { + return SliceResponse.builder() + .totalCount(totalCount) + .content(slice.getContent()) + .hasNext(slice.hasNext()) + .page(slice.getNumber() + 1) + .size(slice.getSize()) + .build(); + } + } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/DevAuthController.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/DevAuthController.java index 96111e9..cb91a59 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/DevAuthController.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/DevAuthController.java @@ -74,7 +74,6 @@ private User createDev() { .provider(Provider.KAKAO) // 테스트용 값 (Enum) .role(Role.ADMIN) // 관리자 권한 부여 .address(Address.of()) - .isSearch(true) .build(); } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java index 42ae3d4..bc74918 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java @@ -57,6 +57,7 @@ public ApiResponse> getProductList(PageReques Sort.by(Sort.Direction.DESC, "id") ); + /// 공통 객체들 저장 Slice products; @@ -84,7 +85,10 @@ public ApiResponse> getProductList(PageReques throw new IllegalArgumentException(ErrorCode.BAD_REQUEST.getMessage()); } - return ApiResponse.ok(SliceResponse.from(products)); + /// 카테고리에 따른 전체 개수 조회 + Long totalCounts = productService.getCountProducts(category.getValue()); + + return ApiResponse.ok(SliceResponse.from(products, totalCounts)); } /** @@ -129,22 +133,5 @@ public ApiResponse> getManufacturers( return ApiResponse.ok(SliceResponse.from(responses)); } - - /** - * 전체 상품 개수 조회 API - * @param category 카테 고리 - */ - @GetMapping("/total") - public ApiResponse getTotalProducts( - @RequestParam CategoryType category - ) { - - /// 서비스 호출 - Long response = productService.getCountProducts(category.getValue()); - - /// 리턴 - return ApiResponse.ok(response); - } - } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/SearchController.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/SearchController.java index f8785ab..a902077 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/SearchController.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/SearchController.java @@ -1,13 +1,16 @@ package site.kikihi.custom.platform.adapter.in.web; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; import org.springframework.security.core.annotation.AuthenticationPrincipal; import site.kikihi.custom.global.response.ApiResponse; import site.kikihi.custom.global.response.page.PageRequest; +import site.kikihi.custom.global.response.page.SliceResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.product.ProductListResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.search.SearchListResponse; import site.kikihi.custom.platform.adapter.in.web.swagger.SearchControllerSpec; import site.kikihi.custom.platform.application.in.search.SearchUseCase; -import site.kikihi.custom.platform.domain.product.Product; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import site.kikihi.custom.platform.domain.search.Search; @@ -25,23 +28,30 @@ public class SearchController implements SearchControllerSpec { /// 상품 검색 @GetMapping - public ApiResponse> searchProducts( + public ApiResponse> searchProducts( @RequestParam("keyword") String keyword, PageRequest pageRequest, @AuthenticationPrincipal PrincipalDetails principalDetails ) { + /// Pageable + Pageable pageable = org.springframework.data.domain.PageRequest.of( + pageRequest.getPage() - 1, + pageRequest.getSize(), + Sort.by(Sort.Direction.DESC, "id") + ); + /// 유저가 없다면 null 저장 UUID userId = principalDetails != null ? principalDetails.getId() : null; /// 서비스 호출 - List productList = service.searchProducts(keyword, pageRequest.getPage(), pageRequest.getSize(), userId); + Slice products = service.searchProducts(keyword, pageable, userId); - /// DTO 수정 - List responses = ProductListResponse.from(productList); + /// 서비스 호출(총 검색 결과 개수) + long countByKeyword = service.countByKeyword(keyword); /// 응답 - return ApiResponse.ok(responses); + return ApiResponse.ok(SliceResponse.from(products, countByKeyword)); } /// 나의 최근 검색어 조회 @@ -84,47 +94,4 @@ public ApiResponse deleteAllSearch( return ApiResponse.deleted(); } - /// 자동 저장 기능 조회 - @GetMapping("/auto") - public ApiResponse getMyAutoSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ) { - - /// 서비스 호출 - boolean checked = service.checkSearch(principalDetails.getId()); - - String autoSearch = checked ? "자동 저장이 활성화되었습니다." : "자동 저장이 꺼져있습니다."; - - /// 리턴 - return ApiResponse.ok(autoSearch); - - } - - /// 자동 저장 기능 켜기 - @PutMapping("/auto/on") - public ApiResponse turnOnSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ){ - - /// 서비스 호출 - service.turnOnMySearchKeyword(principalDetails.getId()); - - /// 리턴 - return ApiResponse.updated(); - - } - - /// 자동 저장 기능 끄기 - @PutMapping("/auto/off") - public ApiResponse turnOffSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ){ - - /// 서비스 호출 - service.turnOffMySearchKeyword(principalDetails.getId()); - - /// 리턴 - return ApiResponse.updated(); - } - } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java index 093233e..7106234 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java @@ -19,14 +19,6 @@ public interface ProductControllerSpec { - @Operation( - summary = "상품 개수 조회 API", - description = "카테고리별 상품 전체 개수를 파악합니다." - ) - ApiResponse getTotalProducts( - @RequestParam CategoryType category - ); - /** * 상품 상세 조회 API * @param id 상품 아이디 diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/SearchControllerSpec.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/SearchControllerSpec.java index 6794a94..c47fb25 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/SearchControllerSpec.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/SearchControllerSpec.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; import site.kikihi.custom.global.response.ApiResponse; import site.kikihi.custom.global.response.page.PageRequest; +import site.kikihi.custom.global.response.page.SliceResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.product.ProductListResponse; import site.kikihi.custom.platform.adapter.in.web.dto.response.search.SearchListResponse; import site.kikihi.custom.security.oauth2.domain.PrincipalDetails; @@ -21,7 +22,7 @@ public interface SearchControllerSpec { summary = "검색 API", description = "키워드를 바탕으로 조회합니다." ) - ApiResponse> searchProducts( + ApiResponse> searchProducts( @Parameter(example = "하우징") @RequestParam("keyword") String keyword, PageRequest pageRequest, @@ -57,32 +58,4 @@ ApiResponse deleteAllSearch( @AuthenticationPrincipal PrincipalDetails principalDetails ); - - @Operation( - summary = "검색어 자동저장 여부 API", - description = "JWT를 바탕으로 자동저장을 확인합니다." - ) - ApiResponse getMyAutoSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ); - - - @Operation( - summary = "검색어 자동저장 기능 ON API", - description = "JWT를 바탕으로 자동저장을 킵니다." - ) - ApiResponse turnOnSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ); - - - - - @Operation( - summary = "검색어 자동저장 기능 OFF API", - description = "JWT를 바탕으로 자동저장을 끕니다." - ) - ApiResponse turnOffSearch( - @AuthenticationPrincipal PrincipalDetails principalDetails - ); } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/BookmarkAdapter.java b/src/main/java/site/kikihi/custom/platform/adapter/out/BookmarkAdapter.java index 1137d31..f06be2b 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/BookmarkAdapter.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/BookmarkAdapter.java @@ -67,6 +67,13 @@ public List getBookmarksByUserIdAndCategoryId(UUID userId, String cate .toList(); } + @Override + public List getBookmarksByUserId(UUID userId) { + return repository.findByUserId(userId).stream() + .map(BookmarkJpaEntity::toDomain) + .toList(); + } + /** * 북마크ID 와 유저가 존재하는지 체크 * diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/UserAdapter.java b/src/main/java/site/kikihi/custom/platform/adapter/out/UserAdapter.java index a1c4103..69a8464 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/UserAdapter.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/UserAdapter.java @@ -33,9 +33,6 @@ public void updateUser(User user) { /// 조회 var entity = userJpaRepository.findById(user.getId()) .orElseThrow(() -> new IllegalArgumentException(ErrorCode.USER_NOT_FOUND.getMessage())); - - /// 자동저장 여부 수정 - entity.updateSearch(user.isSearch()); } @Override diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/bookmark/BookmarkJpaRepository.java b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/bookmark/BookmarkJpaRepository.java index 28b6c02..e745242 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/bookmark/BookmarkJpaRepository.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/bookmark/BookmarkJpaRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import site.kikihi.custom.platform.domain.bookmark.Bookmark; import java.util.List; import java.util.UUID; @@ -35,4 +36,5 @@ public interface BookmarkJpaRepository extends JpaRepository findByUserId(UUID userId); } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/user/UserJpaEntity.java b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/user/UserJpaEntity.java index 8a0a235..535744b 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/user/UserJpaEntity.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/jpa/user/UserJpaEntity.java @@ -41,8 +41,6 @@ public class UserJpaEntity extends BaseTimeEntity { @Embedded private AddressJpaEntity address; - private boolean isSearch; - @PrePersist public void generateUUID() { if (this.id == null) { @@ -61,7 +59,6 @@ public static UserJpaEntity from(User user) { .role(user.getRole()) .profileImage(user.getProfileImage()) .address(AddressJpaEntity.from(user.getAddress())) - .isSearch(user.isSearch()) .build(); } @@ -76,12 +73,7 @@ public User toDomain() { .role(role) .profileImage(profileImage) .address(address.toDomain()) - .isSearch(isSearch) .build(); } - /// 엔티티 수정용 - public void updateSearch(boolean isSearch) { - this.isSearch = isSearch; - } } diff --git a/src/main/java/site/kikihi/custom/platform/application/in/search/SearchUseCase.java b/src/main/java/site/kikihi/custom/platform/application/in/search/SearchUseCase.java index ab6d812..d080baf 100644 --- a/src/main/java/site/kikihi/custom/platform/application/in/search/SearchUseCase.java +++ b/src/main/java/site/kikihi/custom/platform/application/in/search/SearchUseCase.java @@ -1,6 +1,8 @@ package site.kikihi.custom.platform.application.in.search; -import site.kikihi.custom.platform.domain.product.Product; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import site.kikihi.custom.platform.adapter.in.web.dto.response.product.ProductListResponse; import site.kikihi.custom.platform.domain.search.Search; import java.util.List; @@ -16,25 +18,17 @@ public interface SearchUseCase { /// 검색 - List searchProducts(String keyword, int page, int size, UUID userId); + Slice searchProducts(String keyword, Pageable pageable, UUID userId); /// 나의 검색에 목록 확인하기 List getMySearches(UUID userId); + /// 키워드 검색 결과 개수 + long countByKeyword(String keyword); + /// 키워드 하나 삭제하기 void deleteMySearchKeyword(Long searchId, UUID userId); /// 키워드 모두 삭제하기 void deleteAllKeywords(UUID userId); - - /// 나의 최근 검색어 여부 - boolean checkSearch(UUID userId); - - /// 나의 최근 검색어 저장 끄기 - void turnOffMySearchKeyword(UUID userId); - - /// 나의 최근 검색어 저장 켜기 - void turnOnMySearchKeyword(UUID userId); - - } diff --git a/src/main/java/site/kikihi/custom/platform/application/out/bookmark/BookmarkPort.java b/src/main/java/site/kikihi/custom/platform/application/out/bookmark/BookmarkPort.java index bdbd548..09496f8 100644 --- a/src/main/java/site/kikihi/custom/platform/application/out/bookmark/BookmarkPort.java +++ b/src/main/java/site/kikihi/custom/platform/application/out/bookmark/BookmarkPort.java @@ -29,6 +29,8 @@ public interface BookmarkPort { /// 유저와 카테고리 ID를 바탕으로 북마크 목록 조회 List getBookmarksByUserIdAndCategoryId(UUID userId, String category); + List getBookmarksByUserId(UUID userId); + /// 유저와 북마크 ID를 바탕으로 북마크 여부 조회 boolean checkBookmarkByUserIdAndId(UUID userId, Long bookmarkId); diff --git a/src/main/java/site/kikihi/custom/platform/application/service/SearchService.java b/src/main/java/site/kikihi/custom/platform/application/service/SearchService.java index 24a3dfc..f119336 100644 --- a/src/main/java/site/kikihi/custom/platform/application/service/SearchService.java +++ b/src/main/java/site/kikihi/custom/platform/application/service/SearchService.java @@ -1,14 +1,19 @@ package site.kikihi.custom.platform.application.service; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.elasticsearch.core.SearchHits; import site.kikihi.custom.global.response.ErrorCode; +import site.kikihi.custom.platform.adapter.in.web.dto.response.product.ProductListResponse; import site.kikihi.custom.platform.adapter.out.elasticSearch.ProductESDocument; -import site.kikihi.custom.platform.adapter.out.elasticSearch.ProductESRepository; import site.kikihi.custom.platform.application.in.search.SearchUseCase; +import site.kikihi.custom.platform.application.out.bookmark.BookmarkPort; import site.kikihi.custom.platform.application.out.search.SearchPort; import site.kikihi.custom.platform.application.out.user.UserPort; +import site.kikihi.custom.platform.domain.bookmark.Bookmark; import site.kikihi.custom.platform.domain.product.Product; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.client.elc.NativeQuery; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; @@ -21,6 +26,7 @@ import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -30,18 +36,21 @@ public class SearchService implements SearchUseCase { /// 의존성 private final ElasticsearchOperations elasticsearchOperations; - private final ProductESRepository productESRepository; private final SearchPort port; /// 외부 의존성 private final UserPort userPort; + private final BookmarkPort bookmarkPort; /// 스태틱 private final Float minScore = 0.001f; /// 키워드 검색 (name, description) @Override - public List searchProducts(String keyword, int page, int size, UUID userId) { + public Slice searchProducts(String keyword, Pageable pageable, UUID userId) { + + /// Pageable 구성 + // match 쿼리 구성 Query nameMatch = MatchQuery.of(m -> m.field("name").query(keyword))._toQuery(); Query descMatch = MatchQuery.of(m -> m.field("description").query(keyword))._toQuery(); @@ -56,8 +65,8 @@ public List searchProducts(String keyword, int page, int size, UUID use // NativeQuery NativeQuery query = NativeQuery.builder() .withQuery(boolQuery) - .withPageable(PageRequest.of(page, size)) //page-> from으로 자동 변환(from=page * size) - .withMinScore(minScore) // <<-- 추가됨 + .withPageable(pageable) + .withMinScore(minScore) .build(); /// 로그인 한 유저가 확인한다면, 최근 검색 기록 DB에 저장하기 @@ -66,20 +75,37 @@ public List searchProducts(String keyword, int page, int size, UUID use /// 유저 조회 User user = getUser(userId); - /// 자동저장이 ON인 유저만 저장한다. - if (user.isSearch()) { + /// DB에 최신 검색어 저장하기 + Search search = Search.of(user.getId(), keyword); + port.saveSearch(search); - /// DB에 최신 검색어 저장하기 - Search search = Search.of(user.getId(), keyword); - port.saveSearch(search); - } } - return elasticsearchOperations.search(query, ProductESDocument.class) - .stream() + SearchHits searchHits = elasticsearchOperations.search(query, ProductESDocument.class); + + /// 결과물 출력 + List elasticProducts = searchHits.stream() .map(SearchHit::getContent) .map(ProductESDocument::toDomain) - .collect(Collectors.toList()); + .toList(); + + /// 페이징 처리 + // 현재 페이지 결과 수 + boolean hasNext = checkNext(pageable.getPageNumber(), pageable.getPageSize(), searchHits); + + Slice products = new SliceImpl<>(elasticProducts, pageable, hasNext); + + return toProductListResponse(userId, products); + } + + private boolean checkNext(int page, int size, SearchHits searchHits) { + + long totalHits = searchHits.getTotalHits(); + + // 현재 페이지와 size를 이용해 현재 페이지가 마지막 페이지인지 판단 + boolean hasNext = (page + 1) * size < totalHits; + + return hasNext; } @Override @@ -129,47 +155,35 @@ public void deleteAllKeywords(UUID userId) { port.deleteALlSearch(user.getId()); } - @Override - public boolean checkSearch(UUID userId) { - - /// 유저 예외 처리 - User user = getUser(userId); - - /// 유저의 여부 체크 - return user.isSearch(); - } - - /** - * 검색 기록을 저장하지않도록 끕니다. - * @param userId 유저 ID + * 키워드에 따른 검색으로 검색 결과가 몇 개인지 + * @param keyword 키워드 */ @Override - public void turnOffMySearchKeyword(UUID userId) { - - /// 유저 - User user = getUser(userId); + public long countByKeyword(String keyword) { + // match 쿼리 구성 + Query nameMatch = MatchQuery.of(m -> m.field("name").query(keyword))._toQuery(); + Query descMatch = MatchQuery.of(m -> m.field("description").query(keyword))._toQuery(); - /// 켜져있을때만 끌 수있게 - if (user.isSearch()) { - user.turnOffSearch(); - userPort.updateUser(user); - } - } + // bool 쿼리 + Query boolQuery = BoolQuery.of(b -> b + .should(nameMatch) + .should(descMatch) + .minimumShouldMatch("1") + )._toQuery(); - @Override - public void turnOnMySearchKeyword(UUID userId) { - /// 유저 - User user = getUser(userId); + // NativeQuery - 페이징 없이 전체 개수 조회용 + NativeQuery query = NativeQuery.builder() + .withQuery(boolQuery) + .withMinScore(minScore) + .build(); - /// 꺼져있을때만 켤 수있게 - if (!user.isSearch()) { - user.turnOnSearch(); - userPort.updateUser(user); - } + SearchHits searchHits = elasticsearchOperations.search(query, ProductESDocument.class); + return searchHits.getTotalHits(); } + /// 유저 조회 private User getUser(UUID userId) { @@ -185,4 +199,41 @@ private Search getSearch(Long searchId) { .orElseThrow(() -> new NoSuchElementException(ErrorCode.SEARCH_NOT_FOUND.getMessage())); } + + /** + * 상품 목록 조회를 진행할때, 북마크 여부를 파악하는 함수입니다. + * @param userId 유저 ID + * @param products 상품 목록 + */ + private Slice toProductListResponse(UUID userId, Slice products) { + /// 응답 값 + List dtoList; + + /// 상품 목록 꺼내서 DTO 변환 + List content = products.getContent(); + + /// 로그인 하지 않은 유저가 확인한다면 + if (userId == null) { + + /// 하트가 전부 false 되는 로직 + dtoList = ProductListResponse.from(content); + + /// 새로운 Slice 객체로 생성 + return new SliceImpl<>(dtoList, products.getPageable(), products.hasNext()); + } + + /// 유저가 북마크를 했는지 체크 + List bookmarks = bookmarkPort.getBookmarksByUserId(userId); + + /// 북마크된 상품 ID만 추출 + Set bookmarkedProductIds = bookmarks.stream() + .map(Bookmark::getProductId) + .collect(Collectors.toSet()); + + // 북마크 여부 반영하여 DTO 변환 + dtoList = ProductListResponse.from(content, bookmarkedProductIds); + + return new SliceImpl<>(dtoList, products.getPageable(), products.hasNext()); + } + } diff --git a/src/main/java/site/kikihi/custom/platform/domain/user/User.java b/src/main/java/site/kikihi/custom/platform/domain/user/User.java index 6873967..0ee34e3 100644 --- a/src/main/java/site/kikihi/custom/platform/domain/user/User.java +++ b/src/main/java/site/kikihi/custom/platform/domain/user/User.java @@ -24,7 +24,6 @@ public class User { private Role role; private String profileImage; private Address address; - private boolean isSearch = true; /// 정적 팩토리 메서드 public static User of(OAuth2UserInfo userInfo) { @@ -38,17 +37,7 @@ public static User of(OAuth2UserInfo userInfo) { .profileImage(userInfo.getImageUrl()) .role(Role.USER) .address(Address.of()) - .isSearch(true) .build(); } - /// 비즈니스 로직 - public void turnOnSearch() { - isSearch = true; - } - - public void turnOffSearch() { - isSearch = false; - } - }