diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java index 75958592..7109f7f7 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java @@ -33,6 +33,48 @@ public interface BannerApi { ResponseEntity> 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> deleteBanner(Long bannerId); + + @Operation( + summary = "게시 중인 외부 배너 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "게시 중인 외부 배너 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getExternalBanners(String platform, String location); + + @Operation( summary = "배너 이미지 PreSignedUrl 조회 API", responses = { @ApiResponse( diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java index 074aa371..3e950e42 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java @@ -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> 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> 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> 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> getBannerDetail( + @PathVariable("bannerId") Long bannerId + ) { + val response = bannerService.getBannerDetail(bannerId); + return ApiResponseUtil.success(SUCCESS_GET_BANNER_DETAIL, response); + } + + @Override + @DeleteMapping("/{bannerId}") + public ResponseEntity> deleteBanner( + @PathVariable("bannerId") Long bannerId + ) { + bannerService.deleteBanner(bannerId); + return ApiResponseUtil.success(SUCCESS_DELETE_BANNER); + } + + @Override + @GetMapping("/images") + public ResponseEntity> 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> 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> createBanner( + @RequestBody BannerRequest.BannerCreate request + ) { + val response = bannerService.createBanner(request); + return ApiResponseUtil.success(SUCCESS_CREATE_BANNER, response); + } } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java index 22b0c91c..6f60e2a5 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java @@ -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; @@ -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 fromEntity(List urlList){ + return urlList.stream() + .map(BannerImageUrl::new) + .collect(Collectors.toUnmodifiableList()); + } } @Builder(access = PRIVATE) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java index ec33d31c..7c14ae53 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java @@ -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 getExternalBanners(final String platform, final String location); + BannerResponse.ImagePreSignedUrl getIssuedPreSignedUrlForPutImage(String contentName, String imageType, String imageExtension, String contentType); BannerResponse.BannerDetail createBanner(BannerRequest.BannerCreate request); + } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java index 8d51ac4b..ac40c5cd 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java @@ -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; @@ -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 getExternalBanners(final String imageType, final String location) { + val publishLocation = PublishLocation.getByValue(location); + + val bannerList = bannerRepository.findBannersByLocation(publishLocation); + + List 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)); } diff --git a/operation-api/src/test/java/org/sopt/makers/operation/web/banner/api/BannerApiControllerTest.java b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/api/BannerApiControllerTest.java index 86f87b5c..c58d10a7 100644 --- a/operation-api/src/test/java/org/sopt/makers/operation/web/banner/api/BannerApiControllerTest.java +++ b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/api/BannerApiControllerTest.java @@ -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; @@ -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()) @@ -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())); } -} \ No newline at end of file + + @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")); + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java index 75b2e4ee..d25ad9f8 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java @@ -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; diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java index 55546c1a..8c2f063f 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java @@ -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, "배너 생성에 성공했습니다") ; diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/BannerImage.java b/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/BannerImage.java index a60e155f..352a7d5a 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/BannerImage.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/BannerImage.java @@ -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 @@ -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; diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/banner/repository/BannerRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/banner/repository/BannerRepository.java index 025c1179..84b4afb6 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/banner/repository/BannerRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/banner/repository/BannerRepository.java @@ -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 { + + List findBannersByLocation(PublishLocation publishLocation); }