Skip to content

Commit

Permalink
[MERGE] feat/#291 -> dev
Browse files Browse the repository at this point in the history
[FEAT/#290] 배너 삭제 & 외부 배너 조회 기능 구현
  • Loading branch information
yummygyudon authored Dec 26, 2024
2 parents 0b1a0d5 + 0fa024f commit b0bc6dd
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,48 @@ public interface BannerApi {
ResponseEntity<BaseResponse<?>> getBannerDetail(Long bannerId);

@Operation(
summary = "배너 삭제 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "배너 삭제 성공"
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청"
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 배너 ID 요청"
),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류"
)
}
)
ResponseEntity<BaseResponse<?>> deleteBanner(Long bannerId);

@Operation(
summary = "게시 중인 외부 배너 리스트 조회 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "게시 중인 외부 배너 리스트 조회 성공"
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청"
),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류"
)
}
)
ResponseEntity<BaseResponse<?>> getExternalBanners(String platform, String location);

@Operation(
summary = "배너 이미지 PreSignedUrl 조회 API",
responses = {
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,82 @@
import lombok.RequiredArgsConstructor;
import lombok.val;

import org.sopt.makers.operation.code.success.web.BannerSuccessCode;
import org.sopt.makers.operation.dto.BaseResponse;
import org.sopt.makers.operation.util.ApiResponseUtil;
import org.sopt.makers.operation.web.banner.dto.request.BannerRequest;
import org.sopt.makers.operation.web.banner.dto.request.BannerRequest.BannerCreate;
import org.sopt.makers.operation.web.banner.service.BannerService;
import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_CREATE_BANNER;
import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_DELETE_BANNER;
import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_BANNER_DETAIL;
import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL;
import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_EXTERNAL_BANNERS;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/banners")
@RequiredArgsConstructor
public class BannerApiController implements BannerApi {
private final BannerService bannerService;

@Override
@GetMapping("/{bannerId}")
public ResponseEntity<BaseResponse<?>> getBannerDetail(
@PathVariable("bannerId") Long bannerId
) {
val response = bannerService.getBannerDetail(bannerId);
return ApiResponseUtil.success(SUCCESS_GET_BANNER_DETAIL, response);
}

@Override
@GetMapping("/img/pre-signed")
public ResponseEntity<BaseResponse<?>> getIssuedPreSignedUrlForPutImage(@RequestParam("content-name") String contentName, @RequestParam("image-type") String imageType,
@RequestParam("image-extension") String imageExtension, @RequestParam("content-type") String contentType) {
val response = bannerService.getIssuedPreSignedUrlForPutImage(contentName, imageType, imageExtension, contentType);
return ApiResponseUtil.success(SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL, response);
}

@PostMapping
@Override
public ResponseEntity<BaseResponse<?>> createBanner(@RequestBody BannerRequest.BannerCreate request) {
val response = bannerService.createBanner(request);
return ApiResponseUtil.success(SUCCESS_CREATE_BANNER, response);
}

private final BannerService bannerService;

@Override
@GetMapping("/{bannerId}")
public ResponseEntity<BaseResponse<?>> getBannerDetail(
@PathVariable("bannerId") Long bannerId
) {
val response = bannerService.getBannerDetail(bannerId);
return ApiResponseUtil.success(SUCCESS_GET_BANNER_DETAIL, response);
}

@Override
@DeleteMapping("/{bannerId}")
public ResponseEntity<BaseResponse<?>> deleteBanner(
@PathVariable("bannerId") Long bannerId
) {
bannerService.deleteBanner(bannerId);
return ApiResponseUtil.success(SUCCESS_DELETE_BANNER);
}

@Override
@GetMapping("/images")
public ResponseEntity<BaseResponse<?>> getExternalBanners(
@RequestParam("image_type") String imageType,
@RequestParam("location") String location
) {
return ApiResponseUtil.success(SUCCESS_GET_EXTERNAL_BANNERS,
bannerService.getExternalBanners(imageType, location));
}

@Override
@GetMapping("/img/pre-signed")
public ResponseEntity<BaseResponse<?>> getIssuedPreSignedUrlForPutImage(
@RequestParam("content-name") String contentName,
@RequestParam("image-type") String imageType,
@RequestParam("image-extension") String imageExtension,
@RequestParam("content-type") String contentType
) {
val response = bannerService.getIssuedPreSignedUrlForPutImage(contentName, imageType,
imageExtension, contentType);
return ApiResponseUtil.success(SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL, response);
}

@PostMapping
@Override
public ResponseEntity<BaseResponse<?>> createBanner(
@RequestBody BannerRequest.BannerCreate request
) {
val response = bannerService.createBanner(request);
return ApiResponseUtil.success(SUCCESS_CREATE_BANNER, response);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.sopt.makers.operation.web.banner.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.sopt.makers.operation.banner.domain.Banner;
Expand Down Expand Up @@ -40,6 +42,18 @@ public static BannerDetail fromEntity(Banner banner) {
.mobileImageUrl(banner.getImage().getMobileImageUrl())
.build();
}


}
public record BannerImageUrl(
@JsonProperty("url") String url
){

public static List<BannerImageUrl> fromEntity(List<String> urlList){
return urlList.stream()
.map(BannerImageUrl::new)
.collect(Collectors.toUnmodifiableList());
}
}

@Builder(access = PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package org.sopt.makers.operation.web.banner.service;

import java.util.List;

import org.sopt.makers.operation.web.banner.dto.request.*;
import org.sopt.makers.operation.web.banner.dto.response.BannerResponse;
import org.sopt.makers.operation.web.banner.dto.response.BannerResponse.BannerDetail;
import org.sopt.makers.operation.web.banner.dto.response.BannerResponse.BannerImageUrl;

public interface BannerService {

BannerResponse.BannerDetail getBannerDetail(final long bannerId);

void deleteBanner(final long bannerId);

List<BannerImageUrl> getExternalBanners(final String platform, final String location);

BannerResponse.ImagePreSignedUrl getIssuedPreSignedUrlForPutImage(String contentName, String imageType, String imageExtension, String contentType);

BannerResponse.BannerDetail createBanner(BannerRequest.BannerCreate request);

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package org.sopt.makers.operation.web.banner.service;

import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.sopt.makers.operation.banner.domain.Banner;
import org.sopt.makers.operation.banner.domain.PublishLocation;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.sopt.makers.operation.banner.domain.*;

import org.sopt.makers.operation.banner.repository.BannerRepository;
import org.sopt.makers.operation.client.s3.S3Service;
import org.sopt.makers.operation.code.failure.BannerFailureCode;
Expand All @@ -31,7 +39,26 @@ public BannerResponse.BannerDetail getBannerDetail(final long bannerId) {
return BannerResponse.BannerDetail.fromEntity(banner);
}

private Banner getBannerById(final long id) {
@Override
public void deleteBanner(final long bannerId) {
val banner = getBannerById(bannerId);
bannerRepository.delete(banner);
}

@Override
public List<BannerResponse.BannerImageUrl> getExternalBanners(final String imageType, final String location) {
val publishLocation = PublishLocation.getByValue(location);

val bannerList = bannerRepository.findBannersByLocation(publishLocation);

List<String> list = bannerList.stream()
.map( banner -> banner.getImage().retrieveImageUrl(imageType))
.collect(Collectors.toUnmodifiableList());

return BannerResponse.BannerImageUrl.fromEntity(list);
}

private Banner getBannerById(final long id) {
return bannerRepository.findById(id)
.orElseThrow(() -> new BannerException(BannerFailureCode.NOT_FOUND_BANNER));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -65,7 +67,7 @@ void getBannerDetail() throws Exception {
this.mockMvc.perform(
// when
get("/api/v1/banners/" + MOCK_BANNER_ID)
.contentType(MediaType.APPLICATION_JSON)
.contentType(APPLICATION_JSON)
.principal(mock(Principal.class)))
// then
.andExpect(status().isOk())
Expand All @@ -82,4 +84,41 @@ void getBannerDetail() throws Exception {
.andExpect(jsonPath("$.data.image_url_pc").value(givenBannerDetail.pcImageUrl()))
.andExpect(jsonPath("$.data.image_url_mobile").value(givenBannerDetail.mobileImageUrl()));
}
}

@Test
@DisplayName("(DELETE) Banner Delete")
void deleteBanner() throws Exception {
//given
BannerResponse.BannerDetail mockBannerDetail = bannerService.getBannerDetail(MOCK_BANNER_ID);

this.mockMvc.perform(
//when
delete("/api/v1/banners/" + MOCK_BANNER_ID)
.contentType(APPLICATION_JSON)
.principal(mock(Principal.class)))

//then
.andExpect(status().isNoContent())
.andExpect(jsonPath("$.success").value("true"));

}

@Test
@DisplayName("(GET) External Banners")
void getExternalBanners() throws Exception {
// given
String imageType = "pc";
String location = "pg_community";

this.mockMvc.perform(
// when
get("/api/v1/banners/images")
.contentType(APPLICATION_JSON)
.param("image_type", imageType)
.param("location", location)
.principal(mock(Principal.class)))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value("true"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ public enum BannerFailureCode implements FailureCode {
NOT_FOUND_STATUS(NOT_FOUND, "존재하지 않는 게시 상태입니다."),
NOT_FOUND_LOCATION(NOT_FOUND, "존재하지 않는 게시 위치입니다."),
NOT_FOUND_CONTENT_TYPE(NOT_FOUND, "존재하지 않는 게시 유형입니다."),
NOT_FOUNT_BANNER(NOT_FOUND, "존재하지 않는 배너입니다."),
NOT_SUPPORTED_PLATFORM_TYPE(NOT_FOUND, "지원하지 않는 플랫폼 유형입니다."),
NOT_FOUND_BANNER(NOT_FOUND, "존재하지 않는 배너입니다."),
NOT_FOUND_BANNER_IMAGE(NOT_FOUND, "존재하지 않는 배너 이미지입니다.")

;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
@RequiredArgsConstructor(access = PRIVATE)
public enum BannerSuccessCode implements SuccessCode {
SUCCESS_GET_BANNER_DETAIL(HttpStatus.OK, "배너 상세 정보 조회 성공"),
SUCCESS_DELETE_BANNER(HttpStatus. NO_CONTENT, "배너 삭제 성공"),
SUCCESS_GET_EXTERNAL_BANNERS(HttpStatus.OK, "외부 배너 조회 성공"),
SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL(HttpStatus.OK, "이미지 업로드 pre signed url 조회에 성공했습니다"),
SUCCESS_CREATE_BANNER(HttpStatus.CREATED, "배너 생성에 성공했습니다")
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

import jakarta.persistence.Embeddable;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.sopt.makers.operation.code.failure.BannerFailureCode;
import org.sopt.makers.operation.code.failure.FailureCode;
import org.sopt.makers.operation.exception.BannerException;
import lombok.*;

import static lombok.AccessLevel.PROTECTED;
import static org.sopt.makers.operation.code.failure.BannerFailureCode.NOT_SUPPORTED_PLATFORM_TYPE;

@Getter
@Embeddable
Expand All @@ -21,6 +28,14 @@ public void updateMobileImage(String updateMobileImageUrl) {
this.mobileImageUrl = updateMobileImageUrl;
}

public String retrieveImageUrl(String platform) {
return switch (platform) {
case "pc" -> pcImageUrl;
case "mobile" -> mobileImageUrl;
default -> throw new BannerException(NOT_SUPPORTED_PLATFORM_TYPE);
};
}

@Builder
private BannerImage(String pcImageUrl, String mobileImageUrl) {
this.pcImageUrl = pcImageUrl;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.sopt.makers.operation.banner.repository;

import java.util.List;
import org.sopt.makers.operation.banner.domain.Banner;

import org.sopt.makers.operation.banner.domain.PublishLocation;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;

@Repository
public interface BannerRepository extends JpaRepository<Banner, Long> {

List<Banner> findBannersByLocation(PublishLocation publishLocation);
}

0 comments on commit b0bc6dd

Please sign in to comment.