From 93f1ba9055286d2c7565a7712aaac0c0b88dca30 Mon Sep 17 00:00:00 2001 From: siyeonSon <87802191+siyeonSon@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:37:43 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(api):=20count=20items=20filter?= =?UTF-8?q?ed=20by=20location=20(#535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: feat(api): count user items group by location * :sparkles: feat(api): count user liked items group by location * :bug: fix(api): korean grammar correction --- .../item/dao/ItemLocationCountDao.java | 11 ++++ .../item/repository/ItemLikeRepository.java | 2 - .../repository/ItemLikeRepositoryImpl.java | 60 +++++++++++++++++++ .../ItemLocationRepositoryImpl.java | 31 ++++++++-- .../QueryDslItemLikeRepository.java | 5 ++ .../QueryDslItemLocationRepository.java | 4 +- .../user/controller/UserItemController.java | 43 +++++++++++-- .../UserItemCountGroupByLocationDto.java | 8 +++ .../domains/user/service/UserItemService.java | 43 +++++++++++-- .../user/service/UserItemServiceTest.java | 6 +- 10 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 backend/streetdrop-api/src/main/java/com/depromeet/domains/item/dao/ItemLocationCountDao.java create mode 100644 backend/streetdrop-api/src/main/java/com/depromeet/domains/user/dto/response/UserItemCountGroupByLocationDto.java diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/dao/ItemLocationCountDao.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/dao/ItemLocationCountDao.java new file mode 100644 index 00000000..4c85fd74 --- /dev/null +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/dao/ItemLocationCountDao.java @@ -0,0 +1,11 @@ +package com.depromeet.domains.item.dao; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +public class ItemLocationCountDao { + private String locationName; + private Long count; +} diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepository.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepository.java index 34b2f034..ae520d64 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepository.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepository.java @@ -10,8 +10,6 @@ @Repository public interface ItemLikeRepository extends JpaRepository, QueryDslItemLikeRepository { Optional findByItemIdAndUser(Long itemId, User user); - boolean existsByUserIdAndItemId(Long userId, Long itemId); - int countByItemId(Long itemId); } diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepositoryImpl.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepositoryImpl.java index 2addd765..416610d1 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepositoryImpl.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLikeRepositoryImpl.java @@ -1,14 +1,17 @@ package com.depromeet.domains.item.repository; +import com.depromeet.domains.item.dao.ItemLocationCountDao; import com.depromeet.domains.item.dao.UserItemLikeDao; import com.depromeet.domains.user.dao.UserItemPointDao; import com.depromeet.domains.user.dto.request.ItemOrderType; import com.depromeet.item.QItemLike; import com.querydsl.core.types.Expression; +import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.DateExpression; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -18,6 +21,7 @@ import java.util.Date; import java.util.List; +import static com.depromeet.area.state.QStateArea.stateArea; import static com.depromeet.area.village.QVillageArea.villageArea; import static com.depromeet.item.QItem.item; import static com.depromeet.item.QItemLike.itemLike; @@ -181,6 +185,62 @@ public List findByUserIdAndCity(Long userId, Long lastCursor, I return query.fetch(); } + @Override + public List countItemsGroupByState(Long userId) { + return queryFactory.select( + Projections.fields( + ItemLocationCountDao.class, + stateArea.stateName.as("locationName"), + ExpressionUtils.as( + JPAExpressions.select(itemLike.count()) + .from(itemLike) + .join(item).on(item.id.eq(itemLike.item.id)) + .join(itemLocation).on(item.id.eq(itemLocation.item.id)) + .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) + .where(itemLike.user.id.eq(userId)) + .where(villageArea.cityArea.stateArea.id.eq(stateArea.id)) + , "count") + )) + .from(stateArea) + .fetch(); + } + + @Override + public Long countItemsByState(Long userId, String state) { + return queryFactory + .select(itemLike.count()) + .from(itemLike) + .join(item).on(item.id.eq(itemLike.item.id)) + .join(itemLocation).on(item.id.eq(itemLocation.item.id)) + .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) + .where(itemLike.user.id.eq(userId)) + .where(villageArea.cityArea.stateArea.stateName.eq(state)) + .fetchFirst(); + } + + @Override + public Long countItemsByCity(Long userId, String city) { + return queryFactory + .select(itemLike.count()) + .from(itemLike) + .join(item).on(item.id.eq(itemLike.item.id)) + .join(itemLocation).on(item.id.eq(itemLocation.item.id)) + .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) + .where(itemLike.user.id.eq(userId)) + .where(villageArea.cityArea.cityName.eq(city)) + .fetchFirst(); + } + + @Override + public Long countItems(Long userId) { + return queryFactory + .select(itemLike.count()) + .from(itemLike) + .join(item).on(item.id.eq(itemLike.item.id)) + .where(itemLike.user.id.eq(userId)) + .fetchFirst(); + } + private void orderBy(ItemOrderType orderType, JPAQuery query) { switch (orderType) { case RECENT -> query.orderBy(item.createdAt.desc()); diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLocationRepositoryImpl.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLocationRepositoryImpl.java index f2e9c0e8..6e390fe5 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLocationRepositoryImpl.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/ItemLocationRepositoryImpl.java @@ -1,8 +1,11 @@ package com.depromeet.domains.item.repository; +import com.depromeet.domains.item.dao.ItemLocationCountDao; import com.depromeet.domains.item.dao.ItemPointDao; import com.depromeet.domains.user.dao.UserItemPointDao; +import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.locationtech.jts.geom.Point; @@ -10,6 +13,7 @@ import java.util.List; +import static com.depromeet.area.state.QStateArea.stateArea; import static com.depromeet.area.village.QVillageArea.villageArea; import static com.depromeet.external.querydsl.mysql.spatial.MySqlSpatialFunction.mySqlDistanceSphereFunction; import static com.depromeet.item.QItem.item; @@ -63,26 +67,45 @@ public List findUserDropItemsPoints(Long userId) { } @Override - public Long countItemsByCity(Long userId, String city) { + public List countItemsGroupByState(Long userId) { + return queryFactory.select( + Projections.fields( + ItemLocationCountDao.class, + stateArea.stateName.as("locationName"), + ExpressionUtils.as( + JPAExpressions.select(itemLocation.count()) + .from(itemLocation) + .join(item).on(itemLocation.item.id.eq(item.id)) + .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) + .where(item.user.id.eq(userId)) + .where(villageArea.cityArea.stateArea.id.eq(stateArea.id)) + , "count") + )) + .from(stateArea) + .fetch(); + } + + @Override + public Long countItemsByState(Long userId, String state) { return queryFactory .select(itemLocation.count()) .from(itemLocation) .join(item).on(itemLocation.item.id.eq(item.id)) .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) .where(item.user.id.eq(userId)) - .where(villageArea.cityArea.cityName.eq(city)) + .where(villageArea.cityArea.stateArea.stateName.eq(state)) .fetchFirst(); } @Override - public Long countItemsByState(Long userId, String state) { + public Long countItemsByCity(Long userId, String city) { return queryFactory .select(itemLocation.count()) .from(itemLocation) .join(item).on(itemLocation.item.id.eq(item.id)) .join(villageArea).on(itemLocation.villageArea.id.eq(villageArea.id)) .where(item.user.id.eq(userId)) - .where(villageArea.cityArea.stateArea.stateName.eq(state)) + .where(villageArea.cityArea.cityName.eq(city)) .fetchFirst(); } diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLikeRepository.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLikeRepository.java index 6de8c2d3..c481d3b1 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLikeRepository.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLikeRepository.java @@ -1,5 +1,6 @@ package com.depromeet.domains.item.repository; +import com.depromeet.domains.item.dao.ItemLocationCountDao; import com.depromeet.domains.item.dao.UserItemLikeDao; import com.depromeet.domains.user.dao.UserItemPointDao; import com.depromeet.domains.user.dto.request.ItemOrderType; @@ -11,4 +12,8 @@ public interface QueryDslItemLikeRepository { List findByUserId(Long userId, Long lastCursor, ItemOrderType itemOrderType); List findByUserIdAndState(Long userId, Long lastCursor, ItemOrderType itemOrderType, String state); List findByUserIdAndCity(Long userId, Long lastCursor, ItemOrderType itemOrderType, String city); + List countItemsGroupByState(Long userId); + Long countItemsByState(Long userId, String state); + Long countItemsByCity(Long userId, String city); + Long countItems(Long userId); } diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLocationRepository.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLocationRepository.java index 31f1acf4..7b5c7453 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLocationRepository.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/item/repository/QueryDslItemLocationRepository.java @@ -1,5 +1,6 @@ package com.depromeet.domains.item.repository; +import com.depromeet.domains.item.dao.ItemLocationCountDao; import com.depromeet.domains.item.dao.ItemPointDao; import com.depromeet.domains.user.dao.UserItemPointDao; import org.locationtech.jts.geom.Point; @@ -9,7 +10,8 @@ public interface QueryDslItemLocationRepository { List findNearItemsPointsByDistance(Point point, Double distance, Double innerDistance, List blockedUserIds); List findUserDropItemsPoints(Long userId); - Long countItemsByCity(Long userId, String city); + List countItemsGroupByState(Long userId); Long countItemsByState(Long userId, String state); + Long countItemsByCity(Long userId, String city); Long countItems(Long userId); } diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/controller/UserItemController.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/controller/UserItemController.java index 26e73e52..b9b0daf5 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/controller/UserItemController.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/controller/UserItemController.java @@ -3,6 +3,7 @@ import com.depromeet.common.dto.PaginationResponseDto; import com.depromeet.common.dto.ResponseDto; import com.depromeet.domains.user.dto.request.ItemOrderType; +import com.depromeet.domains.user.dto.response.UserItemCountGroupByLocationDto; import com.depromeet.domains.user.dto.response.UserItemLocationCountDto; import com.depromeet.domains.user.dto.response.UserPoiResponseDto; import com.depromeet.domains.user.service.UserItemService; @@ -50,6 +51,28 @@ public ResponseEntity getUserDropItemsPoints( return ResponseDto.ok(response); } + @Operation(summary = "사용자가 드랍한 아이템 지역 별 개수 조회") + @ApiResponse(responseCode = "200", description = "사용자가 드랍한 아이템 지역별 개수 조회 성공") + @GetMapping("/drop/count/all-locations") + public ResponseEntity countUserItemsGroupByStates( + @ReqUser User user + ) { + var response = userItemService.countUserItemsGroupByLocation(user); + return ResponseEntity.ok(response); + } + + @Operation(summary = "사용자가 드랍한 아이템 개수 조회") + @ApiResponse(responseCode = "200", description = "사용자가 드랍한 아이템 개수 조회 성공") + @GetMapping("/drop/count") + public ResponseEntity countUserItemsByLocation( + @ReqUser User user, + @RequestParam(value = "state", required = false) String state, + @RequestParam(value = "city", required = false) String city + ) { + var response = userItemService.countUserItemsByLocation(user, state, city); + return ResponseEntity.ok(response); + } + @Operation(summary = "사용자가 찜한 아이템 조회") @ApiResponse(responseCode = "200", description = "사용자가 찜한 아이템 조회 성공") @GetMapping("/like") @@ -74,15 +97,25 @@ public ResponseEntity getUserLikedItemsPoints( return ResponseDto.ok(response); } - @Operation(summary = "사용자가 드랍한 아이템 개수 조회") - @ApiResponse(responseCode = "200", description = "사용자가 드랍한 아이템 개수 조회 성공") - @GetMapping("/drop/count") - public ResponseEntity countItemsByLocation( + @Operation(summary = "사용자가 찜한 아이템 지역 별 개수 조회") + @ApiResponse(responseCode = "200", description = "사용자가 찜한 아이템 지역별 개수 조회 성공") + @GetMapping("/like/count/all-locations") + public ResponseEntity countLikedItemsGroupByStates( + @ReqUser User user + ) { + var response = userItemService.countLikedItemsGroupByLocation(user); + return ResponseEntity.ok(response); + } + + @Operation(summary = "사용자가 찜한 아이템 개수 조회") + @ApiResponse(responseCode = "200", description = "사용자가 찜한 아이템 개수 조회 성공") + @GetMapping("/like/count") + public ResponseEntity countUserLikedItemsByLocation( @ReqUser User user, @RequestParam(value = "state", required = false) String state, @RequestParam(value = "city", required = false) String city ) { - var response = userItemService.countItemsByLocation(user, state, city); + var response = userItemService.countLikedItemsByLocation(user, state, city); return ResponseEntity.ok(response); } diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/dto/response/UserItemCountGroupByLocationDto.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/dto/response/UserItemCountGroupByLocationDto.java new file mode 100644 index 00000000..c09f7dfa --- /dev/null +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/dto/response/UserItemCountGroupByLocationDto.java @@ -0,0 +1,8 @@ +package com.depromeet.domains.user.dto.response; + +import java.util.List; + +public record UserItemCountGroupByLocationDto( + List data +) { +} diff --git a/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/service/UserItemService.java b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/service/UserItemService.java index 040eb583..22cb5f75 100644 --- a/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/service/UserItemService.java +++ b/backend/streetdrop-api/src/main/java/com/depromeet/domains/user/service/UserItemService.java @@ -10,6 +10,7 @@ import com.depromeet.domains.item.service.ItemLikeService; import com.depromeet.domains.music.dto.response.MusicResponseDto; import com.depromeet.domains.user.dto.request.ItemOrderType; +import com.depromeet.domains.user.dto.response.UserItemCountGroupByLocationDto; import com.depromeet.domains.user.dto.response.UserItemLocationCountDto; import com.depromeet.domains.user.dto.response.UserPoiResponseDto; import com.depromeet.domains.user.dto.response.UserResponseDto; @@ -55,7 +56,6 @@ public class UserItemService { return new PaginationResponseDto<>(itemGroupByDateResponseDto, meta); } - @Transactional(readOnly = true) public PaginationResponseDto getDropItemsV2(User user, long nextCursor, ItemOrderType orderType, String state, String city) { List itemList = getItemList(user, nextCursor, orderType, state, city); @@ -121,7 +121,6 @@ private ItemGroupResponseDto itemDaotoItemGroupResponseDto(User user, ItemDao it .build(); } - private ItemGroupResponseV2Dto itemDaotoItemGroupResponseV2Dto(User user, ItemDao itemDao) { return ItemGroupResponseV2Dto .builder() @@ -143,6 +142,29 @@ private ItemGroupResponseV2Dto itemDaotoItemGroupResponseV2Dto(User user, ItemDa .build(); } + @Transactional(readOnly = true) + public UserItemCountGroupByLocationDto countUserItemsGroupByLocation(User user) { + var list = itemLocationRepository.countItemsGroupByState(user.getId()).stream() + .map(itemLocationCountDao -> new UserItemLocationCountDto(itemLocationCountDao.getCount(), itemLocationCountDao.getLocationName(), null)) + .toList(); + return new UserItemCountGroupByLocationDto(list); + } + + @Transactional(readOnly = true) + public UserItemLocationCountDto countUserItemsByLocation(User user, String state, String city) { + Long count = 0L; + if (state == null) { + count = itemLocationRepository.countItems(user.getId()); + } + else if (city == null) { + count = itemLocationRepository.countItemsByState(user.getId(), state); + } + else { + count = itemLocationRepository.countItemsByCity(user.getId(), city); + } + return new UserItemLocationCountDto(count, state, city); + } + @Transactional(readOnly = true) public PaginationResponseDto getLikedItems(User user, long nextCursor, ItemOrderType itemOrderType, String state, String city) { @@ -158,17 +180,26 @@ public UserPoiResponseDto getLikedItemsPoints(User user) { return new UserPoiResponseDto(userPoiDtoList); } + + @Transactional(readOnly = true) + public UserItemCountGroupByLocationDto countLikedItemsGroupByLocation(User user) { + var list = itemLikeRepository.countItemsGroupByState(user.getId()).stream() + .map(itemLocationCountDao -> new UserItemLocationCountDto(itemLocationCountDao.getCount(), itemLocationCountDao.getLocationName(), null)) + .toList(); + return new UserItemCountGroupByLocationDto(list); + } + @Transactional(readOnly = true) - public UserItemLocationCountDto countItemsByLocation(User user, String state, String city) { + public UserItemLocationCountDto countLikedItemsByLocation(User user, String state, String city) { Long count = 0L; if (state == null) { - count = itemLocationRepository.countItems(user.getId()); + count = itemLikeRepository.countItems(user.getId()); } else if (city == null) { - count = itemLocationRepository.countItemsByState(user.getId(), state); + count = itemLikeRepository.countItemsByState(user.getId(), state); } else { - count = itemLocationRepository.countItemsByCity(user.getId(), city); + count = itemLikeRepository.countItemsByCity(user.getId(), city); } return new UserItemLocationCountDto(count, state, city); } diff --git a/backend/streetdrop-api/src/test/java/unit/domains/user/service/UserItemServiceTest.java b/backend/streetdrop-api/src/test/java/unit/domains/user/service/UserItemServiceTest.java index d7ceb253..603cc78a 100644 --- a/backend/streetdrop-api/src/test/java/unit/domains/user/service/UserItemServiceTest.java +++ b/backend/streetdrop-api/src/test/java/unit/domains/user/service/UserItemServiceTest.java @@ -183,7 +183,7 @@ void getUserDropItemCountFilteredByCityTest() { ReflectionTestUtils.setField(user, "id", 1L); when(itemLocationRepository.countItemsByCity(user.getId(), city)).thenReturn(2L); - var result = userItemService.countItemsByLocation(user, state, city); + var result = userItemService.countUserItemsByLocation(user, state, city); var expected = new UserItemLocationCountDto(2L, state, city); assertThat(result).isEqualTo(expected); @@ -198,7 +198,7 @@ void getUserDropItemCountFilteredByStateTest() { ReflectionTestUtils.setField(user, "id", 1L); when(itemLocationRepository.countItemsByState(user.getId(), state)).thenReturn(2L); - var result = userItemService.countItemsByLocation(user, state, city); + var result = userItemService.countUserItemsByLocation(user, state, city); var expected = new UserItemLocationCountDto(2L, state, null); assertThat(result).isEqualTo(expected); @@ -213,7 +213,7 @@ void getUserDropItemCountWithNoFilterTest() { ReflectionTestUtils.setField(user, "id", 1L); when(itemLocationRepository.countItems(user.getId())).thenReturn(2L); - var result = userItemService.countItemsByLocation(user, state, city); + var result = userItemService.countUserItemsByLocation(user, state, city); var expected = new UserItemLocationCountDto(2L, null, null); assertThat(result).isEqualTo(expected);