Skip to content

Commit 0544fd8

Browse files
authored
Merge pull request #76 from ConnectCo/fix/coupon
[FIX] 일부 오류 해결 및 쿠폰 조회 API 구햔
2 parents a43b2d4 + a97edd0 commit 0544fd8

File tree

20 files changed

+167
-75
lines changed

20 files changed

+167
-75
lines changed

src/main/java/com/connectCo/config/security/SecurityConstant.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class SecurityConstant {
1111
public static final String[] PUBLIC_URLS = {
1212
"/auth/login", "/auth/refresh",
1313
"/stores/*/detail", "/organizations/*/detail",
14-
"/coupons/*/detail", "/coupons/store/*", "/coupons/recent", "/coupons/distance", "/coupons/deadline",
14+
"/coupons/*/detail", "/coupons/store/*", "/coupons",
1515
"/events/*/detail",
1616
"/v3/**", "/swagger-ui/**",
1717
"/test/**",

src/main/java/com/connectCo/domain/coupon/controller/CouponController.java

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.connectCo.domain.coupon.dto.response.CouponIdResponse;
88
import com.connectCo.domain.coupon.dto.response.CouponPagingResponse;
99
import com.connectCo.domain.coupon.dto.response.CouponSummaryInquiryResponse;
10+
import com.connectCo.domain.coupon.entity.CouponSearchType;
1011
import com.connectCo.domain.coupon.service.CouponService;
1112
import com.connectCo.global.common.BaseResponse;
1213
import io.swagger.v3.oas.annotations.Operation;
@@ -43,7 +44,7 @@ public BaseResponse<CouponIdResponse> createCoupon(
4344
}
4445

4546
@Operation(summary = "쿠폰 수정 API", description = "가게 프로필만 수정 가능")
46-
@PutMapping(value = "/{couponId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
47+
@PatchMapping(value = "/{couponId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
4748
public BaseResponse<CouponIdResponse> updateCoupon(
4849
@AuthenticationPrincipal PrincipalDetails principal,
4950
@Parameter(description = "수정할 쿠폰 id") @PathVariable Long couponId,
@@ -130,52 +131,26 @@ public BaseResponse<CouponPagingResponse<CouponSummaryInquiryResponse>> inquiryC
130131
return BaseResponse.onSuccess(couponService.inquiryCouponsByStore(storeId, page, size));
131132
}
132133

133-
// TODO: 생성순, 거리순, 임박순 하나로 합치기? --> 생성순, 임박순도 결국 위치 기반으로 조회 해야 함(비로그인 시)
134-
@Operation(summary = "쿠폰 목록 조회 API(생성순)", description = "비로그인 시도 가능")
135-
@GetMapping("/recent")
134+
@Operation(summary = "쿠폰 목록 조회 API", description = "비로그인 시도 가능(비로그인 시 현재 위치 기준)")
135+
@GetMapping
136136
@Parameters(value = {
137+
@Parameter(name = "type", description = "쿠폰 조회 타입(생성순, 거리순, 신청마감일 임박순)"),
138+
@Parameter(name = "latitude", description = "위도, 비로그인 시 필수"),
139+
@Parameter(name = "longitude", description = "경도, 비로그인 시 핋수"),
137140
@Parameter(name = "page", description = "페이지 번호(0부터 시작)"),
138141
@Parameter(name = "size", description = "한 페이지 당 이벤트 개수"),
139142
})
140143
public BaseResponse<CouponPagingResponse<CouponSummaryInquiryResponse>> inquiryCouponByRecent(
141-
@RequestParam int page,
142-
@RequestParam int size
143-
) {
144-
return BaseResponse.onSuccess(couponService.inquiryCouponsByRecent(page, size));
145-
}
146-
147-
148-
// TODO: 위치에 따른 쿠폰 조회 추가
149-
@Operation(summary = "쿠폰 목록 조회 API(거리순)", description = "비로그인 시도 가능(비로그인 시 현재 위치 기준)")
150-
@GetMapping("/distance")
151-
@Parameters(value = {
152-
@Parameter(name = "latitude", description = "위도, 비로그인 시만 사용"),
153-
@Parameter(name = "longitude", description = "경도, 비로그인 시만 사용"),
154-
@Parameter(name = "page", description = "페이지 번호(0부터 시작)"),
155-
@Parameter(name = "size", description = "한 페이지 당 이벤트 개수"),
156-
})
157-
public BaseResponse<CouponPagingResponse<CouponSummaryInquiryResponse>> inquiryCouponByDistance(
158144
@AuthenticationPrincipal PrincipalDetails principal,
159-
@RequestParam(required = false) double latitude,
160-
@RequestParam(required = false) double longitude,
161-
@RequestParam int page,
162-
@RequestParam int size
163-
) {
164-
return BaseResponse.onSuccess(null);
165-
}
166-
167-
// TODO: 신청마감일 임박한 쿠폰 조회 추가
168-
@Operation(summary = "쿠폰 목록 조회 API(신청마감일 임박순)", description = "비로그인 시도 가능")
169-
@GetMapping("/deadline")
170-
@Parameters(value = {
171-
@Parameter(name = "page", description = "페이지 번호(0부터 시작)"),
172-
@Parameter(name = "size", description = "한 페이지 당 이벤트 개수"),
173-
})
174-
public BaseResponse<CouponPagingResponse<CouponSummaryInquiryResponse>> inquiryCouponByDeadline(
145+
@RequestParam CouponSearchType type,
146+
@RequestParam(required = false) Double latitude,
147+
@RequestParam(required = false) Double longitude,
175148
@RequestParam int page,
176149
@RequestParam int size
177150
) {
178-
return BaseResponse.onSuccess(null);
151+
return BaseResponse.onSuccess(couponService.inquiryCoupons(
152+
principal, type, latitude, longitude, page, size
153+
));
179154
}
180155

181156
// TODO: 쿠폰 추천 조회 추가

src/main/java/com/connectCo/domain/coupon/dto/response/CouponDetailInquiryResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public class CouponDetailInquiryResponse {
4444
@Schema(description = "내 쿠폰 여부")
4545
private Boolean isMine;
4646

47+
@Schema(description = "협찬 이벤트 개수")
48+
private Integer eventCount;
49+
4750
@Getter
4851
@AllArgsConstructor
4952
public static class StoreInfo {

src/main/java/com/connectCo/domain/coupon/dto/response/CouponSummaryInquiryResponse.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
@Builder
1111
public class CouponSummaryInquiryResponse {
1212
@Schema(description = "쿠폰 ID", example = "1")
13-
private Long couponId;
13+
private Long id;
1414

1515
@Schema(description = "가게 이름", example = "가게 A")
16-
private String storeName;
16+
private String name;
1717

1818
@Schema(description = "쿠폰 이름", example = "쿠폰 A")
19-
private String name;
19+
private String title;
2020

2121
@Schema(description = "쿠폰 신청 마감일", example = "2021-12-31")
2222
private LocalDate expiredAt;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.connectCo.domain.coupon.entity;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
6+
@Getter
7+
@RequiredArgsConstructor
8+
public enum CouponSearchType {
9+
DISTANCE( "거리순"),
10+
RECENCY("최신순"),
11+
DEADLINE("마감임박순");
12+
13+
private final String toKorean;
14+
}

src/main/java/com/connectCo/domain/coupon/entity/CouponType.java

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/main/java/com/connectCo/domain/coupon/mapper/CouponMapper.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ public CouponSummaryInquiryResponse toCouponSummaryInquiryResponse(Coupon coupon
6060
.orElse(null);
6161

6262
return CouponSummaryInquiryResponse.builder()
63-
.couponId(coupon.getId())
64-
.storeName(coupon.getStore().getName())
65-
.name(coupon.getName())
63+
.id(coupon.getId())
64+
.name(coupon.getStore().getName())
65+
.title(coupon.getName())
6666
.expiredAt(coupon.getExpiredAt())
6767
.thumbnail(thumbnail)
6868
.build();
@@ -86,6 +86,7 @@ public CouponDetailInquiryResponse toCouponDetailResponse(
8686
)
8787
.isLike(isLiked)
8888
.isMine(isMine)
89+
.eventCount(0) // TODO: 이벤트 개수 조회
8990
.build();
9091
}
9192

src/main/java/com/connectCo/domain/coupon/repository/CouponRepository.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import org.springframework.data.domain.Pageable;
99
import org.springframework.data.jpa.repository.JpaRepository;
1010

11-
import java.util.List;
11+
import org.springframework.data.jpa.repository.Query;
12+
import org.springframework.data.repository.query.Param;
1213

1314
public interface CouponRepository extends JpaRepository<Coupon, Long> {
1415

@@ -18,5 +19,14 @@ default Coupon getCoupon(Long couponId) {
1819
}
1920
Page<Coupon> findAllByStore(Store store, Pageable pageable);
2021
Page<Coupon> findAllByOrderByCreatedAtDesc(Pageable pageable);
21-
22+
Page<Coupon> findAllByOrderByExpiredAtAsc(Pageable pageable);
23+
@Query(value = """
24+
SELECT c.* FROM coupon c
25+
JOIN store s ON c.store_id = s.id
26+
JOIN address a ON s.address_id = a.id
27+
ORDER BY ST_Distance_Sphere(POINT(:longitude, :latitude), POINT(a.longitude, a.latitude))
28+
""", nativeQuery = true)
29+
Page<Coupon> findByDistance(
30+
@Param("latitude") double latitude, @Param("longitude") double longitude, Pageable pageable
31+
);
2232
}

src/main/java/com/connectCo/domain/coupon/service/CouponService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.connectCo.domain.coupon.service;
22

3+
import com.connectCo.config.security.auth.PrincipalDetails;
34
import com.connectCo.domain.coupon.dto.request.CouponCreateRequest;
45
import com.connectCo.domain.coupon.dto.request.CouponUpdateRequest;
56
import com.connectCo.domain.coupon.dto.response.CouponDetailInquiryResponse;
67
import com.connectCo.domain.coupon.dto.response.CouponIdResponse;
78
import com.connectCo.domain.coupon.dto.response.CouponPagingResponse;
89
import com.connectCo.domain.coupon.dto.response.CouponSummaryInquiryResponse;
10+
import com.connectCo.domain.coupon.entity.CouponSearchType;
911
import com.connectCo.domain.member.entity.ProfileType;
1012
import org.springframework.web.multipart.MultipartFile;
1113

@@ -26,5 +28,7 @@ CouponDetailInquiryResponse inquiryCouponDetail(
2628
CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCouponsByLike(Long profileId, int page, int size);
2729
CouponPagingResponse<CouponSummaryInquiryResponse> inquiryMyCoupons(Long profileId, int page, int size);
2830
CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCouponsByStore(Long storeId, int page, int size);
29-
CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCouponsByRecent(int page, int size);
31+
CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCoupons(
32+
PrincipalDetails principal, CouponSearchType type, Double latitude, Double longitude, int page, int size
33+
);
3034
}

src/main/java/com/connectCo/domain/coupon/service/CouponServiceImpl.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.connectCo.domain.coupon.service;
22

3+
import com.connectCo.config.security.auth.PrincipalDetails;
4+
import com.connectCo.domain.address.entity.Address;
35
import com.connectCo.domain.coupon.dto.request.CouponUpdateRequest;
46
import com.connectCo.domain.coupon.dto.response.CouponDetailInquiryResponse;
57
import com.connectCo.domain.coupon.dto.response.CouponPagingResponse;
@@ -9,16 +11,21 @@
911
import com.connectCo.domain.coupon.entity.Coupon;
1012
import com.connectCo.domain.coupon.entity.CouponImage;
1113
import com.connectCo.domain.coupon.entity.CouponLike;
14+
import com.connectCo.domain.coupon.entity.CouponSearchType;
1215
import com.connectCo.domain.coupon.mapper.CouponMapper;
1316
import com.connectCo.domain.coupon.repository.CouponImageRepository;
1417
import com.connectCo.domain.coupon.repository.CouponLikeRepository;
1518
import com.connectCo.domain.coupon.repository.CouponRepository;
19+
import com.connectCo.domain.member.entity.Profile;
1620
import com.connectCo.domain.member.entity.ProfileType;
21+
import com.connectCo.domain.member.repository.ProfileRepository;
1722
import com.connectCo.domain.organization.entity.Organization;
1823
import com.connectCo.domain.organization.service.OrganizationService;
1924
import com.connectCo.domain.store.entity.Store;
2025
import com.connectCo.domain.store.repository.StoreLikeRepository;
2126
import com.connectCo.domain.store.service.StoreService;
27+
import com.connectCo.global.exception.CustomApiException;
28+
import com.connectCo.global.exception.ErrorCode;
2229
import com.connectCo.global.validation.ParamValidator;
2330
import com.connectCo.utils.S3FileComponent;
2431
import java.util.Optional;
@@ -41,6 +48,7 @@ public class CouponServiceImpl implements CouponService {
4148
private final CouponLikeRepository couponLikeRepository;
4249
private final CouponMapper couponMapper;
4350
private final CouponImageRepository couponImageRepository;
51+
private final ProfileRepository profileRepository;
4452

4553
private final StoreService storeService;
4654
private final OrganizationService organizationService;
@@ -179,11 +187,66 @@ public CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCouponsByStore(
179187
}
180188

181189
@Override
182-
public CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCouponsByRecent(int page, int size) {
183-
Page<Coupon> couponList = couponRepository.findAllByOrderByCreatedAtDesc(PageRequest.of(page, size));
190+
@Transactional(readOnly = true)
191+
public CouponPagingResponse<CouponSummaryInquiryResponse> inquiryCoupons(
192+
PrincipalDetails principal, CouponSearchType type, Double latitude, Double longitude, int page, int size
193+
) {
194+
Pageable pageable = PageRequest.of(page, size);
195+
196+
// 비로그인 시에는 latitude, longitude가 반드시 존재해야 함.
197+
if (principal == null) {
198+
if (latitude == null || longitude == null) {
199+
throw new CustomApiException(ErrorCode.IS_MUST_INPUT_LOCATION);
200+
}
201+
ParamValidator.validLocation(latitude, longitude);
202+
return getCouponsByLocation(latitude, longitude, type, pageable);
203+
}
204+
205+
// 로그인한 경우
206+
Optional<Profile> profileOptional =
207+
profileRepository.findByIdAndProfileType(principal.profileId(), principal.profileType());
208+
209+
// latitude, longitude가 주어지면 해당 위치 기준으로 조회
210+
if (latitude != null && longitude != null) {
211+
ParamValidator.validLocation(latitude, longitude);
212+
return getCouponsByLocation(latitude, longitude, type, pageable);
213+
}
214+
215+
// latitude, longitude가 없는 경우, profile에서 address 정보 가져오기
216+
Address address = getAddressFromProfile(profileOptional.orElse(null));
217+
if (address == null) {
218+
throw new CustomApiException(ErrorCode.ADDRESS_NOT_FOUND);
219+
}
220+
221+
return getCouponsByLocation(address.getLatitude(), address.getLongitude(), type, pageable);
222+
223+
}
224+
225+
private Address getAddressFromProfile(Profile profile) {
226+
if (profile == null) {
227+
return null;
228+
}
229+
230+
if (profile instanceof Store store) {
231+
return store.getAddress();
232+
} else if (profile instanceof Organization organization) {
233+
return organization.getAddress();
234+
}
235+
return null;
236+
}
237+
238+
private CouponPagingResponse<CouponSummaryInquiryResponse> getCouponsByLocation(
239+
double latitude, double longitude, CouponSearchType type, Pageable pageable) {
240+
241+
Page<Coupon> couponPage = switch (type) {
242+
case RECENCY -> couponRepository.findAllByOrderByCreatedAtDesc(pageable);
243+
case DISTANCE -> couponRepository.findByDistance(latitude, longitude, pageable);
244+
case DEADLINE -> couponRepository.findAllByOrderByExpiredAtAsc(pageable);
245+
default -> throw new CustomApiException(ErrorCode.COUPON_SEARCH_TYPE_INVALID);
246+
};
184247

185248
return couponMapper.toCouponPagingResponse(
186-
couponList.map(couponMapper::toCouponSummaryInquiryResponse)
249+
couponPage.map(couponMapper::toCouponSummaryInquiryResponse)
187250
);
188251
}
189252

0 commit comments

Comments
 (0)