Skip to content

Commit

Permalink
[feat] 지도뷰 핀 조회 및 식당 리스트 조회 api 구현 (#117)
Browse files Browse the repository at this point in the history
* [dependency] add querydsl dependency

* [chore] add Qclass path in .gitignore

* [fix] add minPrice in PriceCategory.java

* [refac] delete unused method in Store.java

* [feat] create QueryDslConfig.java

* [feat] create dtos for getting pins and list of stores

* [feat] create repository for dynamic query when searching stores

* [feat] create service layer for get stores pins and list api

* [feat] create get stores pins and list api

* [fix] fix error from rebase
  • Loading branch information
Parkjyun authored Jul 17, 2024
1 parent adaf0bd commit 01c81a6
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,6 @@ Temporary Items

application-local.yml
application-dev.yml

# Qclass
/src/main/generated
15 changes: 15 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,27 @@ dependencies {
// AWS
implementation("software.amazon.awssdk:s3:2.21.0")
implementation("software.amazon.awssdk:bom:2.21.0")

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
useJUnitPlatform()
}

def generated = 'src/main/generated'
tasks.withType(JavaCompile).configureEach {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}

clean {
delete file(generated)
}

tasks.register('copyYml', Copy) {
copy {
from './server-yml'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@
import org.hankki.hankkiserver.api.dto.HankkiResponse;
import org.hankki.hankkiserver.api.store.controller.request.StoreDuplicateValidationRequest;
import org.hankki.hankkiserver.api.store.controller.request.StorePostRequest;
import org.hankki.hankkiserver.api.store.parameter.PriceCategory;
import org.hankki.hankkiserver.api.store.parameter.SortOption;
import org.hankki.hankkiserver.api.store.service.StoreCommandService;
import org.hankki.hankkiserver.api.store.service.StoreQueryService;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.response.*;
import org.hankki.hankkiserver.common.code.CommonSuccessCode;
import org.hankki.hankkiserver.domain.store.model.StoreCategory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.hankki.hankkiserver.auth.UserId;
import org.hankki.hankkiserver.api.store.service.HeartCommandService;
import org.hankki.hankkiserver.api.store.service.StoreQueryService;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.command.StoreValidationCommand;
import org.hankki.hankkiserver.api.store.service.response.*;
import org.hankki.hankkiserver.auth.UserId;
import org.hankki.hankkiserver.common.code.CommonSuccessCode;
import org.hankki.hankkiserver.api.store.service.command.HeartDeleteCommand;
import org.hankki.hankkiserver.api.store.service.command.HeartPostCommand;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -43,6 +41,20 @@ public HankkiResponse<StoreGetResponse> getStore(@PathVariable final Long storeI
return HankkiResponse.success(CommonSuccessCode.OK, storeQueryService.getStoreInformation(storeId, userId));
}

@GetMapping("/stores/pins")
public HankkiResponse<StorePinsResponse> getStorePins(@RequestParam(required = false) final Long universityId,
@RequestParam(required = false) final StoreCategory storeCategory,
@RequestParam(required = false) final PriceCategory priceCategory,
@RequestParam(required = false) final SortOption sortOption) {
return HankkiResponse.success(CommonSuccessCode.OK, storeQueryService.getStorePins(universityId, storeCategory, priceCategory, sortOption));
}
@GetMapping("/stores")
public HankkiResponse<StoresResponse> getStores(@RequestParam(required = false) final Long universityId,
@RequestParam(required = false) final StoreCategory storeCategory,
@RequestParam(required = false) final PriceCategory priceCategory,
@RequestParam(required = false) final SortOption sortOption) {
return HankkiResponse.success(CommonSuccessCode.OK, storeQueryService.getStores(universityId, storeCategory, priceCategory, sortOption));
}
@GetMapping("/stores/{id}/thumbnail")
public HankkiResponse<StoreThumbnailResponse> getStoreThumbnail(@PathVariable final Long id) {
return HankkiResponse.success(CommonSuccessCode.OK, storeQueryService.getStoreThumbnail(id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
@RequiredArgsConstructor
public enum PriceCategory {

K6("6000원 이하", "6000"),
k8("6000~8000원", "8000");
K6("6000원 이하", 6000, 0),
K8("6000~8000원", 8000, 6000);

private final String name;
private final String maxPrice;
private final int maxPrice;
private final int minPrice;

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.hankki.hankkiserver.api.store.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.store.parameter.PriceCategory;
import org.hankki.hankkiserver.api.store.parameter.SortOption;
import org.hankki.hankkiserver.common.code.StoreErrorCode;
import org.hankki.hankkiserver.common.exception.NotFoundException;
import org.hankki.hankkiserver.domain.store.model.Store;
import org.hankki.hankkiserver.domain.store.model.StoreCategory;
import org.hankki.hankkiserver.domain.store.repository.StoreRepository;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;

@Component
Expand Down Expand Up @@ -35,4 +39,8 @@ protected Optional<Store> findByLatitudeAndLongitude(final double latitude, fina
protected boolean existsByLatitudeAndLongitude(final double latitude, final double longitude) {
return storeRepository.existsByPoint_LatitudeAndPoint_Longitude(latitude, longitude);
}

public List<Store> findAllDynamicQuery(final Long universityId, final StoreCategory storeCategory, final PriceCategory priceCategory, final SortOption sortOption) {
return storeRepository.findStoreByCategoryAndLowestPriceAndUniversityIdAndIsDeletedFalseOrderBySortOptions(storeCategory, priceCategory, universityId, sortOption);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ public StoreGetResponse getStoreInformation(final Long storeId, final Long userI
getMenus(store));
}

@Transactional(readOnly = true)
public StorePinsResponse getStorePins(final Long universityId, final StoreCategory storeCategory, final PriceCategory priceCategory, final SortOption sortOption) {
return StorePinsResponse.of(storeFinder.findAllDynamicQuery(universityId, storeCategory, priceCategory, sortOption)
.stream().map(PinResponse::of).toList());
}

@Transactional(readOnly = true)
public StoresResponse getStores(final Long universityId, final StoreCategory storeCategory, final PriceCategory priceCategory, final SortOption sortOption) {
return StoresResponse.of(storeFinder.findAllDynamicQuery(universityId, storeCategory, priceCategory, sortOption)
.stream().map(StoreResponse::of).toList());
}

public CategoriesResponse getCategories() {
return new CategoriesResponse(Arrays.stream(StoreCategory.values())
.map(CategoryResponse::of)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.hankki.hankkiserver.api.store.service.response;

import org.hankki.hankkiserver.domain.store.model.Store;

public record PinResponse(
double latitude,
double longitude,
long id,
String name
) {
public static PinResponse of(final Store store) {
return new PinResponse(store.getPoint().getLatitude(), store.getPoint().getLongitude(), store.getId(), store.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.hankki.hankkiserver.api.store.service.response;

import java.util.List;

public record StorePinsResponse(List<PinResponse> pins) {
public static StorePinsResponse of (final List<PinResponse> pins) {
return new StorePinsResponse(pins);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hankki.hankkiserver.api.store.service.response;

import org.hankki.hankkiserver.domain.store.model.Store;

public record StoreResponse (long id,
String imageUrl,
String category,
String name,
int lowestPrice,
int heartCount) {

public static StoreResponse of(final Store store) {
return new StoreResponse(store.getId(), store.getImages().get(0).getImageUrl(), store.getCategory().getName(), store.getName(), store.getLowestPrice(), store.getHeartCount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.hankki.hankkiserver.api.store.service.response;

import java.util.List;

public record StoresResponse(List<StoreResponse> stores) {
public static StoresResponse of(final List<StoreResponse> response) {
return new StoresResponse(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static UserStoreReportedResponse of(Store store) {
store.getId(),
store.getName(),
store.getCategory().getName(),
store.getImage(),
store.getImages().get(0).getImageUrl(),
store.getLowestPrice(),
store.getHeartCount());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hankki.hankkiserver.domain.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDslConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@ private Store (String name, Point point, String address, StoreCategory category,
this.isDeleted = isDeleted;
}

public String getImage() {
if (images.isEmpty()) {
return "default.com";
}
return images.get(0).getImageUrl();
}

public void decreaseHeartCount() {
this.heartCount--;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.hankki.hankkiserver.domain.store.repository;

import org.hankki.hankkiserver.api.store.parameter.PriceCategory;
import org.hankki.hankkiserver.api.store.parameter.SortOption;
import org.hankki.hankkiserver.domain.store.model.Store;
import org.hankki.hankkiserver.domain.store.model.StoreCategory;

import java.util.List;

public interface CustomStoreRepository {
List<Store> findStoreByCategoryAndLowestPriceAndUniversityIdAndIsDeletedFalseOrderBySortOptions(StoreCategory category, PriceCategory priceCategory, Long universityId, SortOption sortOptions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.hankki.hankkiserver.domain.store.repository;

import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.store.parameter.PriceCategory;
import org.hankki.hankkiserver.api.store.parameter.SortOption;
import org.hankki.hankkiserver.domain.store.model.Store;
import org.hankki.hankkiserver.domain.store.model.StoreCategory;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.hankki.hankkiserver.domain.store.model.QStore.store;
import static org.hankki.hankkiserver.domain.universitystore.model.QUniversityStore.universityStore;

@RequiredArgsConstructor
public class CustomStoreRepositoryImpl implements CustomStoreRepository {

private final JPAQueryFactory jpaQueryFactory;

@Override
public List<Store> findStoreByCategoryAndLowestPriceAndUniversityIdAndIsDeletedFalseOrderBySortOptions(StoreCategory category, PriceCategory priceCategory, Long universityId, SortOption sortOptions) {
OrderSpecifier[] orderSpecifiers = createOrderSpecifier(sortOptions);
return jpaQueryFactory
.select(store)
.from(store)
.join(store.universityStores, universityStore).fetchJoin()
.where(eqCategory(category),eqUniversity(universityId), evaluatePriceCategory(priceCategory))
.where(store.isDeleted.isFalse())
.orderBy(orderSpecifiers)
.fetch();
}

private BooleanExpression eqUniversity(Long university) {
if (university == null) {
return null;
}
return store.universityStores.any().university.id.eq(university);
}

private BooleanExpression eqCategory(StoreCategory category) {
if (category == null) {
return null;
}
return store.category.eq(category);
}

private BooleanExpression evaluatePriceCategory(PriceCategory priceCategory) {
if (priceCategory == null) {
return null;
}
return store.lowestPrice.goe(priceCategory.getMinPrice())
.and(store.lowestPrice.loe(priceCategory.getMaxPrice()));
}

private OrderSpecifier[] createOrderSpecifier(SortOption sortOption) {
List<OrderSpecifier> orderSpecifiers = new ArrayList<>();
if (Objects.isNull(sortOption)){
orderSpecifiers.add(new OrderSpecifier(Order.DESC, store.createdAt));
}
else if (SortOption.LATEST == sortOption){
orderSpecifiers.add(new OrderSpecifier(Order.DESC, store.createdAt));
}
else if (SortOption.LOWESTPRICE == sortOption){
orderSpecifiers.add(new OrderSpecifier(Order.ASC, store.lowestPrice));
}
else if (SortOption.RECOMMENDED == sortOption){
orderSpecifiers.add(new OrderSpecifier(Order.DESC, store.heartCount));
}
return orderSpecifiers.toArray(new OrderSpecifier[orderSpecifiers.size()]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import java.util.Optional;

public interface StoreRepository extends JpaRepository<Store, Long> {
public interface StoreRepository extends JpaRepository<Store, Long>, CustomStoreRepository {
@Query("select s from Store s where s.id = :id and s.isDeleted = false")
Optional<Store> findByIdAndIsDeletedIsFalse(Long id);

Expand Down

0 comments on commit 01c81a6

Please sign in to comment.