From 846721d5fcf45220436158ea3809ea761dcbb8f9 Mon Sep 17 00:00:00 2001 From: toychip Date: Sun, 22 Sep 2024 15:33:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=86=8C=ED=86=B5=ED=95=B4=EC=9A=94=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EA=B0=99=EC=9D=80=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=EC=9C=BC=EB=A1=9C=20=ED=95=84=ED=84=B0=EB=A7=81.=20?= =?UTF-8?q?=EC=8B=9C,=20=EA=B5=AC,=20=EB=8F=99=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CITY("시"), DISTRICT("구"), TOWN("동")으로 구분 --- .../api/ttoklip/domain/town/TownCriteria.java | 28 ++++++ .../repository/CommunityRepositoryCustom.java | 3 +- .../repository/CommunityRepositoryImpl.java | 85 ++++++++++++++++--- .../post/service/CommunityPostService.java | 5 +- .../main/controller/TownMainController.java | 7 +- .../town/main/service/TownMainService.java | 12 ++- .../ttoklip/global/exception/ErrorType.java | 5 +- 7 files changed, 124 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/api/ttoklip/domain/town/TownCriteria.java diff --git a/src/main/java/com/api/ttoklip/domain/town/TownCriteria.java b/src/main/java/com/api/ttoklip/domain/town/TownCriteria.java new file mode 100644 index 00000000..128bcf23 --- /dev/null +++ b/src/main/java/com/api/ttoklip/domain/town/TownCriteria.java @@ -0,0 +1,28 @@ +package com.api.ttoklip.domain.town; + +import com.api.ttoklip.global.exception.ApiException; +import com.api.ttoklip.global.exception.ErrorType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public enum TownCriteria { + + CITY("시"), + DISTRICT("구"), + TOWN("동"), + + ; + + private final String Criteria; + + public static TownCriteria findTownCriteriaByValue(final String value) { + try { + return TownCriteria.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + log.error("Invalid value for TownCriteria: {}", value); + throw new ApiException(ErrorType.INVALID_SORT_TYPE); + } + } +} diff --git a/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryCustom.java b/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryCustom.java index 6f8903a5..9811f6cf 100644 --- a/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryCustom.java +++ b/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryCustom.java @@ -1,5 +1,6 @@ package com.api.ttoklip.domain.town.community.post.repository; +import com.api.ttoklip.domain.town.TownCriteria; import com.api.ttoklip.domain.town.community.comment.CommunityComment; import com.api.ttoklip.domain.town.community.post.entity.Community; import java.util.List; @@ -16,5 +17,5 @@ public interface CommunityRepositoryCustom { List getRecent3(); - Page getPaging(Pageable pageable); + Page getPaging(final TownCriteria townCriteria, final Pageable pageable); } diff --git a/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryImpl.java b/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryImpl.java index 13ebc02e..172ceb5d 100644 --- a/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryImpl.java +++ b/src/main/java/com/api/ttoklip/domain/town/community/post/repository/CommunityRepositoryImpl.java @@ -1,11 +1,13 @@ package com.api.ttoklip.domain.town.community.post.repository; import static com.api.ttoklip.domain.member.domain.QMember.member; +import static com.api.ttoklip.domain.privacy.domain.QProfile.profile; import static com.api.ttoklip.domain.town.community.comment.QCommunityComment.communityComment; import static com.api.ttoklip.domain.town.community.image.entity.QCommunityImage.communityImage; import static com.api.ttoklip.domain.town.community.post.entity.QCommunity.community; +import static com.api.ttoklip.global.util.SecurityUtil.getCurrentMember; -import com.api.ttoklip.domain.privacy.domain.QProfile; +import com.api.ttoklip.domain.town.TownCriteria; import com.api.ttoklip.domain.town.community.comment.CommunityComment; import com.api.ttoklip.domain.town.community.post.entity.Community; import com.api.ttoklip.global.exception.ApiException; @@ -24,6 +26,8 @@ @RequiredArgsConstructor public class CommunityRepositoryImpl implements CommunityRepositoryCustom { + private static final String SEOUL = "서울특별시"; + private static final String SPLIT_CRITERIA = " "; private final JPAQueryFactory jpaQueryFactory; @Override @@ -100,37 +104,96 @@ private BooleanExpression matchCommunityId(final Long communityId) { return communityComment.community.id.eq(communityId); } - - // Community 페이징 처리 쿼리 추가 @Override - public Page getPaging(final Pageable pageable) { - List pageContent = getPageContent(pageable); - Long count = countQuery(); + public Page getPaging(final TownCriteria townCriteria, final Pageable pageable) { + List pageContent = getPageContent(townCriteria, pageable); + Long count = countQuery(townCriteria); return new PageImpl<>(pageContent, pageable, count); } - private List getPageContent(final Pageable pageable) { + private List getPageContent(final TownCriteria townCriteria, final Pageable pageable) { + String writerStreet = getCurrentMember().getStreet(); + if (!writerStreet.startsWith(SEOUL)) { + throw new ApiException(ErrorType.INVALID_STREET_TYPE); + } + return jpaQueryFactory .selectFrom(community) .where( - getCommunityActivate() + getCommunityActivate(), + getLocationFilterByTownCriteria(townCriteria, writerStreet) ) .leftJoin(community.member, member).fetchJoin() - .leftJoin(community.member.profile, QProfile.profile).fetchJoin() + .leftJoin(community.member.profile, profile).fetchJoin() .limit(pageable.getPageSize()) .offset(pageable.getOffset()) .orderBy(community.id.desc()) .fetch(); } - private Long countQuery() { + private Long countQuery(final TownCriteria townCriteria) { + String writerStreet = getCurrentMember().getStreet(); return jpaQueryFactory .select(Wildcard.count) .from(community) .where( - getCommunityActivate() + getCommunityActivate(), + getLocationFilterByTownCriteria(townCriteria, writerStreet) ) .fetchOne(); } + private BooleanExpression getLocationFilterByTownCriteria(final TownCriteria townCriteria, final String street) { + String[] streetParts = splitStreet(street); // 공통 메서드로 분리 + + if (townCriteria.equals(TownCriteria.CITY)) { + return filterByCity(streetParts); + } + + if (townCriteria.equals(TownCriteria.DISTRICT)) { + return filterByDistrict(streetParts); + } + + if (townCriteria.equals(TownCriteria.TOWN)) { + return filterByTown(streetParts); + } + + throw new ApiException(ErrorType.INTERNAL_STREET_TYPE); + } + + // 주소를 공백으로 분리하는 로직을 하나의 메서드로 추출 + private String[] splitStreet(final String street) { + return street.split(SPLIT_CRITERIA); + } + + // '시' 부분만 추출해서 필터링 (예: '서울특별시'로 시작하는 모든 주소) + private BooleanExpression filterByCity(final String[] streetParts) { + if (streetParts.length > 0) { + String city = streetParts[0]; + return community.member.street.startsWith(city); + } + return null; + } + + // '시'와 '구'가 모두 일치해야 함 (예: '서울특별시 서대문구') + private BooleanExpression filterByDistrict(final String[] streetParts) { + if (streetParts.length > 1) { + String city = streetParts[0]; + String district = streetParts[1]; + return community.member.street.startsWith(city + SPLIT_CRITERIA + district); + } + return null; + } + + // '시', '구', '동'이 모두 일치해야 함 (예: '서울특별시 서대문구 북가좌동') + private BooleanExpression filterByTown(final String[] streetParts) { + if (streetParts.length > 2) { + String city = streetParts[0]; + String district = streetParts[1]; + String town = streetParts[2]; + return community.member.street.startsWith(city + SPLIT_CRITERIA + district + SPLIT_CRITERIA + town); + } + return null; + } + } diff --git a/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityPostService.java b/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityPostService.java index bb56f9a0..3a53571a 100644 --- a/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityPostService.java +++ b/src/main/java/com/api/ttoklip/domain/town/community/post/service/CommunityPostService.java @@ -8,6 +8,7 @@ import com.api.ttoklip.domain.common.report.dto.ReportCreateRequest; import com.api.ttoklip.domain.common.report.service.ReportService; import com.api.ttoklip.domain.member.domain.Member; +import com.api.ttoklip.domain.town.TownCriteria; import com.api.ttoklip.domain.town.community.comment.CommunityComment; import com.api.ttoklip.domain.town.community.image.service.CommunityImageService; import com.api.ttoklip.domain.town.community.like.service.CommunityLikeService; @@ -202,8 +203,8 @@ public List getRecent3() { /* -------------------------------------------- Community 페이징 -------------------------------------------- */ - public Page getPaging(final Pageable pageable) { - return communityRepository.getPaging(pageable); + public Page getPaging(final TownCriteria townCriteria, final Pageable pageable) { + return communityRepository.getPaging(townCriteria, pageable); } /* -------------------------------------------- Community 끝 -------------------------------------------- */ } diff --git a/src/main/java/com/api/ttoklip/domain/town/main/controller/TownMainController.java b/src/main/java/com/api/ttoklip/domain/town/main/controller/TownMainController.java index 3063df69..b33516e4 100644 --- a/src/main/java/com/api/ttoklip/domain/town/main/controller/TownMainController.java +++ b/src/main/java/com/api/ttoklip/domain/town/main/controller/TownMainController.java @@ -64,10 +64,13 @@ public SuccessResponse getCarts() { )))}) @GetMapping("/community") public SuccessResponse getCommunities( + @Parameter(description = "페이지 번호 (기본값 CITY)", example = "CITY, DISTRICT, TOWN") + @RequestParam(required = false, defaultValue = "CITY") final String criteria, @Parameter(description = "페이지 번호 (0부터 시작, 기본값 0)", example = "0") - @RequestParam(required = false, defaultValue = "0") final int page) { + @RequestParam(required = false, defaultValue = "0") final int page + ) { Pageable pageable = PageRequest.of(page, PAGE_SIZE); - return new SuccessResponse<>(townMainService.getCommunities(pageable)); + return new SuccessResponse<>(townMainService.getCommunities(criteria, pageable)); } /* Cart Paging */ diff --git a/src/main/java/com/api/ttoklip/domain/town/main/service/TownMainService.java b/src/main/java/com/api/ttoklip/domain/town/main/service/TownMainService.java index 438fd413..a0a8b659 100644 --- a/src/main/java/com/api/ttoklip/domain/town/main/service/TownMainService.java +++ b/src/main/java/com/api/ttoklip/domain/town/main/service/TownMainService.java @@ -6,6 +6,7 @@ import com.api.ttoklip.domain.search.response.CartSearchPaging; import com.api.ttoklip.domain.search.response.CommunityPaging; import com.api.ttoklip.domain.search.response.CommunitySingleResponse; +import com.api.ttoklip.domain.town.TownCriteria; import com.api.ttoklip.domain.town.cart.post.entity.Cart; import com.api.ttoklip.domain.town.cart.post.repository.CartSearchRepository; import com.api.ttoklip.domain.town.cart.post.service.CartPostService; @@ -27,14 +28,13 @@ public class TownMainService { private final CommunityPostService communityPostService; private final CartPostService cartPostService; - public CommunityPaging getCommunities(final Pageable pageable) { + public CommunityPaging getCommunities(final String criteria, final Pageable pageable) { + TownCriteria townCriteria = validCriteria(criteria); - Page contentPaging = communityPostService.getPaging(pageable); + Page contentPaging = communityPostService.getPaging(townCriteria, pageable); - // List List contents = contentPaging.getContent(); - // Entity -> SingleResponse 반복 List communitySingleData = contents.stream() .map(CommunitySingleResponse::communityFrom) .toList(); @@ -49,6 +49,10 @@ public CommunityPaging getCommunities(final Pageable pageable) { } + private TownCriteria validCriteria(final String criteria) { + return TownCriteria.findTownCriteriaByValue(criteria); + } + public CartSearchPaging getCarts(final Pageable pageable, final Long startMoney, final Long lastMoney, diff --git a/src/main/java/com/api/ttoklip/global/exception/ErrorType.java b/src/main/java/com/api/ttoklip/global/exception/ErrorType.java index b63b9753..5f58d120 100644 --- a/src/main/java/com/api/ttoklip/global/exception/ErrorType.java +++ b/src/main/java/com/api/ttoklip/global/exception/ErrorType.java @@ -60,6 +60,8 @@ public enum ErrorType { // ------------------------------------------ Community ------------------------------------------ COMMUNITY_NOT_FOUND(NOT_FOUND, "Community_4040", "소통해요를 찾을 수 없습니다."), + INTERNAL_STREET_TYPE(INTERNAL_SERVER_ERROR, "STREET_5001", "TownCriteria에서 이미 필터링했지만, repository에서 잘못된 값을 받았습니다."), + INVALID_STREET_TYPE(BAD_REQUEST, "STREET_4001", "서울특별시 외의 지역은 아직 개발중입니다."), // ------------------------------------------ Comment ------------------------------------------ @@ -185,7 +187,8 @@ public enum ErrorType { INVALID_EMAIL_KEY_TYPE(INTERNAL_SERVER_ERROR, "AOP_5002", "분산락에 적용할 Unique Email 이 null"), DUPLICATED_CREATE_BOARD_REQUEST(BAD_REQUEST, "DUPLICATED_4002", "중복된 게시글 작성입니다."), - INVALID_HASH_LENGTH_TYPE(INTERNAL_SERVER_ERROR, "HASH_5001", "잘못된 Hash 길이 요청"); + INVALID_HASH_LENGTH_TYPE(INTERNAL_SERVER_ERROR, "HASH_5001", "잘못된 Hash 길이 요청"), + ; private final HttpStatus status; private final String errorCode;