From 6beb1bb6ca39afb8d126653910687c7b5e909420 Mon Sep 17 00:00:00 2001 From: hgkim Date: Tue, 24 Mar 2026 15:59:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Banner=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=97=90=20mediaType=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(IMAGE,=20VIDEO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../banner/AdminBannerControllerDocsTest.kt | 4 ++++ .../kotlin/app/helper/banner/BannerHelper.kt | 11 ++++++++-- .../bottlenote/banner/constant/MediaType.java | 22 +++++++++++++++++++ .../app/bottlenote/banner/domain/Banner.java | 9 ++++++++ .../dto/request/AdminBannerCreateRequest.java | 3 +++ .../dto/request/AdminBannerUpdateRequest.java | 2 ++ .../response/AdminBannerDetailResponse.java | 2 ++ .../dto/response/AdminBannerListResponse.java | 2 ++ .../banner/dto/response/BannerResponse.java | 2 ++ .../CustomBannerRepositoryImpl.java | 1 + .../banner/service/AdminBannerService.java | 3 +++ .../banner/service/BannerQueryService.java | 1 + .../banner/fixture/BannerTestFactory.java | 10 +++++++++ .../fixture/InMemoryBannerRepository.java | 1 + .../file/upload/ImageUploadServiceTest.java | 2 +- .../banner/RestBannerQueryControllerTest.java | 5 +++++ 16 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 bottlenote-mono/src/main/java/app/bottlenote/banner/constant/MediaType.java diff --git a/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt b/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt index 3e1d08ac5..0919fc25b 100644 --- a/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt +++ b/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt @@ -87,6 +87,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("배너 목록"), fieldWithPath("data[].id").type(JsonFieldType.NUMBER).description("배너 ID"), fieldWithPath("data[].name").type(JsonFieldType.STRING).description("배너명"), + fieldWithPath("data[].mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO)"), fieldWithPath("data[].bannerType").type(JsonFieldType.STRING).description("배너 유형"), fieldWithPath("data[].sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"), fieldWithPath("data[].isActive").type(JsonFieldType.BOOLEAN).description("활성화 상태"), @@ -147,6 +148,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("data.textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등)"), fieldWithPath("data.isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부"), fieldWithPath("data.targetUrl").type(JsonFieldType.VARIES).description("이동 URL. [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), + fieldWithPath("data.mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO)"), fieldWithPath("data.bannerType").type(JsonFieldType.STRING).description("배너 유형"), fieldWithPath("data.sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"), fieldWithPath("data.startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), @@ -202,6 +204,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등, 기본값: RT)").optional(), fieldWithPath("isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부 (기본값: false)").optional(), fieldWithPath("targetUrl").type(JsonFieldType.VARIES).description("이동 URL (isExternalUrl=true 시 필수). [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), + fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO, 기본값: IMAGE)").optional(), fieldWithPath("bannerType").type(JsonFieldType.STRING).description("배너 유형 (필수: CURATION/AD/SURVEY/PARTNERSHIP/ETC)"), fieldWithPath("sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서 (0 이상, 기본값: 0)").optional(), fieldWithPath("startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), @@ -266,6 +269,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등)"), fieldWithPath("isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부"), fieldWithPath("targetUrl").type(JsonFieldType.VARIES).description("이동 URL (isExternalUrl=true 시 필수). [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), + fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO)").optional(), fieldWithPath("bannerType").type(JsonFieldType.STRING).description("배너 유형 (필수: CURATION/AD/SURVEY/PARTNERSHIP/ETC)"), fieldWithPath("sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서 (0 이상, 필수)"), fieldWithPath("startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), diff --git a/bottlenote-admin-api/src/test/kotlin/app/helper/banner/BannerHelper.kt b/bottlenote-admin-api/src/test/kotlin/app/helper/banner/BannerHelper.kt index 3057e8e42..2ce810118 100644 --- a/bottlenote-admin-api/src/test/kotlin/app/helper/banner/BannerHelper.kt +++ b/bottlenote-admin-api/src/test/kotlin/app/helper/banner/BannerHelper.kt @@ -1,6 +1,7 @@ package app.helper.banner import app.bottlenote.banner.constant.BannerType +import app.bottlenote.banner.constant.MediaType import app.bottlenote.banner.constant.TextPosition import app.bottlenote.banner.dto.response.AdminBannerDetailResponse import app.bottlenote.banner.dto.response.AdminBannerListResponse @@ -12,6 +13,7 @@ object BannerHelper { fun createAdminBannerListResponse( id: Long = 1L, name: String = "테스트 배너", + mediaType: MediaType = MediaType.IMAGE, bannerType: BannerType = BannerType.CURATION, sortOrder: Int = 0, isActive: Boolean = true, @@ -19,7 +21,7 @@ object BannerHelper { endDate: LocalDateTime? = null, createdAt: LocalDateTime = LocalDateTime.of(2024, 1, 1, 0, 0) ): AdminBannerListResponse = AdminBannerListResponse( - id, name, bannerType, sortOrder, isActive, startDate, endDate, createdAt + id, name, mediaType, bannerType, sortOrder, isActive, startDate, endDate, createdAt ) fun createAdminBannerListResponses(count: Int = 3): List = @@ -43,6 +45,7 @@ object BannerHelper { textPosition: TextPosition = TextPosition.RT, isExternalUrl: Boolean = false, targetUrl: String? = null, + mediaType: MediaType = MediaType.IMAGE, bannerType: BannerType = BannerType.CURATION, sortOrder: Int = 0, startDate: LocalDateTime? = null, @@ -52,7 +55,7 @@ object BannerHelper { modifiedAt: LocalDateTime = LocalDateTime.of(2024, 6, 1, 0, 0) ): AdminBannerDetailResponse = AdminBannerDetailResponse( id, name, nameFontColor, descriptionA, descriptionB, descriptionFontColor, - imageUrl, textPosition, isExternalUrl, targetUrl, bannerType, sortOrder, + imageUrl, textPosition, isExternalUrl, targetUrl, mediaType, bannerType, sortOrder, startDate, endDate, isActive, createdAt, modifiedAt ) @@ -66,6 +69,7 @@ object BannerHelper { textPosition: String = "RT", isExternalUrl: Boolean = false, targetUrl: String? = null, + mediaType: String = "IMAGE", bannerType: String = "CURATION", sortOrder: Int = 0, startDate: String? = null, @@ -80,6 +84,7 @@ object BannerHelper { "textPosition" to textPosition, "isExternalUrl" to isExternalUrl, "targetUrl" to targetUrl, + "mediaType" to mediaType, "bannerType" to bannerType, "sortOrder" to sortOrder, "startDate" to startDate, @@ -96,6 +101,7 @@ object BannerHelper { textPosition: String = "CENTER", isExternalUrl: Boolean = false, targetUrl: String? = null, + mediaType: String = "IMAGE", bannerType: String = "CURATION", sortOrder: Int = 1, startDate: String? = null, @@ -111,6 +117,7 @@ object BannerHelper { "textPosition" to textPosition, "isExternalUrl" to isExternalUrl, "targetUrl" to targetUrl, + "mediaType" to mediaType, "bannerType" to bannerType, "sortOrder" to sortOrder, "startDate" to startDate, diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/constant/MediaType.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/constant/MediaType.java new file mode 100644 index 000000000..a61f53ca9 --- /dev/null +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/constant/MediaType.java @@ -0,0 +1,22 @@ +package app.bottlenote.banner.constant; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MediaType { + IMAGE("이미지"), + VIDEO("동영상"); + + private final String description; + + @JsonCreator + public static MediaType parsing(String source) { + if (source == null || source.isEmpty()) { + return null; + } + return MediaType.valueOf(source.toUpperCase()); + } +} diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java index 504022d77..02d6faf70 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.domain; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import app.bottlenote.common.domain.BaseEntity; import jakarta.persistence.Column; @@ -73,6 +74,12 @@ public class Banner extends BaseEntity { @Column(name = "target_url") private String targetUrl; + @Comment("미디어 유형") + @Column(name = "media_type", nullable = false) + @Enumerated(EnumType.STRING) + @Builder.Default + private MediaType mediaType = MediaType.IMAGE; + @Comment("배너 유형") @Column(name = "banner_type", nullable = false) @Enumerated(EnumType.STRING) @@ -106,6 +113,7 @@ public void update( TextPosition textPosition, Boolean isExternalUrl, String targetUrl, + MediaType mediaType, BannerType bannerType, Integer sortOrder, LocalDateTime startDate, @@ -120,6 +128,7 @@ public void update( this.textPosition = textPosition; this.isExternalUrl = isExternalUrl; this.targetUrl = targetUrl; + this.mediaType = mediaType; this.bannerType = bannerType; this.sortOrder = sortOrder; this.startDate = startDate; diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java index 150d85681..9c26ef4b9 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerCreateRequest.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.dto.request; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -22,6 +23,7 @@ public record AdminBannerCreateRequest( TextPosition textPosition, Boolean isExternalUrl, String targetUrl, + MediaType mediaType, @NotNull(message = "BANNER_TYPE_REQUIRED") BannerType bannerType, @Min(value = 0, message = "BANNER_SORT_ORDER_MINIMUM") Integer sortOrder, LocalDateTime startDate, @@ -33,6 +35,7 @@ public record AdminBannerCreateRequest( descriptionFontColor = descriptionFontColor != null ? descriptionFontColor : "#ffffff"; textPosition = textPosition != null ? textPosition : TextPosition.RT; isExternalUrl = isExternalUrl != null ? isExternalUrl : false; + mediaType = mediaType != null ? mediaType : MediaType.IMAGE; sortOrder = sortOrder != null ? sortOrder : 0; } } diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java index 39c4a2251..98d42471e 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/request/AdminBannerUpdateRequest.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.dto.request; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -21,6 +22,7 @@ public record AdminBannerUpdateRequest( TextPosition textPosition, Boolean isExternalUrl, String targetUrl, + MediaType mediaType, @NotNull(message = "BANNER_TYPE_REQUIRED") BannerType bannerType, @NotNull(message = "BANNER_SORT_ORDER_REQUIRED") @Min(value = 0, message = "BANNER_SORT_ORDER_MINIMUM") diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerDetailResponse.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerDetailResponse.java index 4d00249ac..68d3a7482 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerDetailResponse.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerDetailResponse.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.dto.response; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import java.time.LocalDateTime; @@ -15,6 +16,7 @@ public record AdminBannerDetailResponse( TextPosition textPosition, Boolean isExternalUrl, String targetUrl, + MediaType mediaType, BannerType bannerType, Integer sortOrder, LocalDateTime startDate, diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerListResponse.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerListResponse.java index cdf7daa47..2b2a2f54c 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerListResponse.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/AdminBannerListResponse.java @@ -1,11 +1,13 @@ package app.bottlenote.banner.dto.response; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import java.time.LocalDateTime; public record AdminBannerListResponse( Long id, String name, + MediaType mediaType, BannerType bannerType, Integer sortOrder, Boolean isActive, diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/BannerResponse.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/BannerResponse.java index a52c739af..93c076af8 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/BannerResponse.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/dto/response/BannerResponse.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.dto.response; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import java.time.LocalDateTime; import lombok.AllArgsConstructor; @@ -24,6 +25,7 @@ public class BannerResponse { private TextPosition textPosition; private String targetUrl; private Boolean isExternalUrl; + private MediaType mediaType; private BannerType bannerType; private Integer sortOrder; private LocalDateTime startDate; diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/repository/CustomBannerRepositoryImpl.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/repository/CustomBannerRepositoryImpl.java index b46542b33..b43cb3bc8 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/repository/CustomBannerRepositoryImpl.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/repository/CustomBannerRepositoryImpl.java @@ -32,6 +32,7 @@ public Page searchForAdmin( AdminBannerListResponse.class, banner.id, banner.name, + banner.mediaType, banner.bannerType, banner.sortOrder, banner.isActive, diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/service/AdminBannerService.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/service/AdminBannerService.java index cdb06871d..a75448b28 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/service/AdminBannerService.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/service/AdminBannerService.java @@ -60,6 +60,7 @@ public AdminBannerDetailResponse getDetail(Long bannerId) { banner.getTextPosition(), banner.getIsExternalUrl(), banner.getTargetUrl(), + banner.getMediaType(), banner.getBannerType(), banner.getSortOrder(), banner.getStartDate(), @@ -93,6 +94,7 @@ public AdminResultResponse create(AdminBannerCreateRequest request) { .textPosition(request.textPosition()) .isExternalUrl(request.isExternalUrl()) .targetUrl(request.targetUrl()) + .mediaType(request.mediaType()) .bannerType(request.bannerType()) .sortOrder(request.sortOrder()) .startDate(request.startDate()) @@ -130,6 +132,7 @@ public AdminResultResponse update(Long bannerId, AdminBannerUpdateRequest reques request.textPosition(), request.isExternalUrl(), request.targetUrl(), + request.mediaType(), request.bannerType(), request.sortOrder(), request.startDate(), diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/service/BannerQueryService.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/service/BannerQueryService.java index 3484f31e8..df59e8219 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/service/BannerQueryService.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/service/BannerQueryService.java @@ -34,6 +34,7 @@ public List getActiveBanners(Integer limit) { .textPosition(banner.getTextPosition()) .targetUrl(banner.getTargetUrl()) .isExternalUrl(banner.getIsExternalUrl()) + .mediaType(banner.getMediaType()) .bannerType(banner.getBannerType()) .sortOrder(banner.getSortOrder()) .startDate(banner.getStartDate()) diff --git a/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/BannerTestFactory.java b/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/BannerTestFactory.java index 3b37cdcaf..3683ede5e 100644 --- a/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/BannerTestFactory.java +++ b/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/BannerTestFactory.java @@ -1,6 +1,7 @@ package app.bottlenote.banner.fixture; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import app.bottlenote.banner.domain.Banner; import jakarta.persistence.EntityManager; @@ -33,6 +34,7 @@ public Banner persistBanner(@NotNull String name, @NotNull String imageUrl) { .descriptionB(name + " 설명B") .imageUrl(imageUrl) .textPosition(TextPosition.CENTER) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.CURATION) .sortOrder(0) .isActive(true) @@ -60,6 +62,7 @@ public Banner persistBanner( .descriptionB(name + " 설명B") .imageUrl(imageUrl) .textPosition(textPosition) + .mediaType(MediaType.IMAGE) .bannerType(bannerType) .sortOrder(sortOrder) .isActive(isActive) @@ -96,6 +99,7 @@ public List persistMultipleBanners(int count) { .descriptionB("배너 " + (i + 1) + " 설명B") .imageUrl("https://example.com/banner" + (i + 1) + ".jpg") .textPosition(TextPosition.CENTER) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.CURATION) .sortOrder(i) .isActive(true) @@ -122,6 +126,7 @@ public List persistMixedActiveBanners(int activeCount, int inactiveCount .descriptionB("활성 배너 " + (i + 1) + " 설명B") .imageUrl("https://example.com/active" + (i + 1) + ".jpg") .textPosition(TextPosition.CENTER) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.CURATION) .sortOrder(i) .isActive(true) @@ -139,6 +144,7 @@ public List persistMixedActiveBanners(int activeCount, int inactiveCount .descriptionB("비활성 배너 " + (i + 1) + " 설명B") .imageUrl("https://example.com/inactive" + (i + 1) + ".jpg") .textPosition(TextPosition.CENTER) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.AD) .sortOrder(activeCount + i) .isActive(false) @@ -164,6 +170,7 @@ public Banner persistBannerWithPeriod( .descriptionB(name + " 설명B") .imageUrl("https://example.com/" + name + ".jpg") .textPosition(TextPosition.CENTER) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.SURVEY) .sortOrder(0) .startDate(startDate) @@ -193,6 +200,9 @@ private Banner.BannerBuilder fillMissingBannerFields( if (tempBanner.getTextPosition() == null) { builder.textPosition(TextPosition.CENTER); } + if (tempBanner.getMediaType() == null) { + builder.mediaType(MediaType.IMAGE); + } if (tempBanner.getBannerType() == null) { builder.bannerType(BannerType.CURATION); } diff --git a/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/InMemoryBannerRepository.java b/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/InMemoryBannerRepository.java index bd554b5c8..90aa05beb 100644 --- a/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/InMemoryBannerRepository.java +++ b/bottlenote-mono/src/test/java/app/bottlenote/banner/fixture/InMemoryBannerRepository.java @@ -86,6 +86,7 @@ public Page searchForAdmin( new AdminBannerListResponse( b.getId(), b.getName(), + b.getMediaType(), b.getBannerType(), b.getSortOrder(), b.getIsActive(), diff --git a/bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/ImageUploadServiceTest.java b/bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/ImageUploadServiceTest.java index 993802ab6..002b49968 100644 --- a/bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/ImageUploadServiceTest.java +++ b/bottlenote-product-api/src/test/java/app/bottlenote/common/file/upload/ImageUploadServiceTest.java @@ -217,7 +217,7 @@ void test_4() { // given & when & then assertThrows( FileException.class, - () -> imageUploadService.getImageKey("review", 1L, "application/pdf")); + () -> imageUploadService.getImageKey("review", 1L, "application/zip")); } } diff --git a/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java b/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java index c5ca93842..092212c95 100644 --- a/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java +++ b/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java @@ -11,6 +11,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import app.bottlenote.banner.constant.BannerType; +import app.bottlenote.banner.constant.MediaType; import app.bottlenote.banner.constant.TextPosition; import app.bottlenote.banner.controller.BannerQueryController; import app.bottlenote.banner.dto.response.BannerResponse; @@ -49,6 +50,7 @@ void getActiveBanners() throws Exception { .textPosition(TextPosition.LB) .targetUrl("/events/new-whiskey") .isExternalUrl(false) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.CURATION) .sortOrder(0) .startDate(LocalDateTime.of(2025, 1, 1, 0, 0)) @@ -65,6 +67,7 @@ void getActiveBanners() throws Exception { .textPosition(TextPosition.CENTER) .targetUrl("https://forms.google.com/survey123") .isExternalUrl(true) + .mediaType(MediaType.IMAGE) .bannerType(BannerType.SURVEY) .sortOrder(1) .startDate(LocalDateTime.of(2025, 6, 1, 0, 0)) @@ -81,6 +84,7 @@ void getActiveBanners() throws Exception { .textPosition(TextPosition.RT) .targetUrl("/partners/brand-abc") .isExternalUrl(false) + .mediaType(MediaType.VIDEO) .bannerType(BannerType.PARTNERSHIP) .sortOrder(2) .startDate(null) @@ -115,6 +119,7 @@ void getActiveBanners() throws Exception { fieldWithPath("data[].targetUrl").description("클릭 시 이동할 URL").optional(), fieldWithPath("data[].isExternalUrl") .description("외부 URL 여부 (true: 외부, false: 내부)"), + fieldWithPath("data[].mediaType").description("미디어 유형 (IMAGE, VIDEO)"), fieldWithPath("data[].bannerType").description("배너 유형 (하단 BannerType 참조)"), fieldWithPath("data[].sortOrder").description("정렬 순서 (오름차순)"), fieldWithPath("data[].startDate").description("노출 시작일시 (nullable)").optional(), From c1888ffb58d9525e8c5c9c71ec48bc1d2591b17e Mon Sep 17 00:00:00 2001 From: hgkim Date: Tue, 24 Mar 2026 16:10:09 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20Banner.update()=EC=97=90=EC=84=9C=20?= =?UTF-8?q?mediaType=20null=20=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=20=EA=B0=92?= =?UTF-8?q?=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main/java/app/bottlenote/banner/domain/Banner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java b/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java index 02d6faf70..fa0c5a925 100644 --- a/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java +++ b/bottlenote-mono/src/main/java/app/bottlenote/banner/domain/Banner.java @@ -128,7 +128,7 @@ public void update( this.textPosition = textPosition; this.isExternalUrl = isExternalUrl; this.targetUrl = targetUrl; - this.mediaType = mediaType; + this.mediaType = mediaType != null ? mediaType : this.mediaType; this.bannerType = bannerType; this.sortOrder = sortOrder; this.startDate = startDate; From 8bdc328e7fe45ed75938644bde136e8d19aba4e3 Mon Sep 17 00:00:00 2001 From: hgkim Date: Tue, 24 Mar 2026 16:12:35 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20RestDocs=20mediaType=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=ED=99=95=EC=9E=A5=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=20=EA=B0=92=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/docs/banner/AdminBannerControllerDocsTest.kt | 8 ++++---- .../app/docs/banner/RestBannerQueryControllerTest.java | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt b/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt index 0919fc25b..a3d5c2062 100644 --- a/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt +++ b/bottlenote-admin-api/src/test/kotlin/app/docs/banner/AdminBannerControllerDocsTest.kt @@ -87,7 +87,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("배너 목록"), fieldWithPath("data[].id").type(JsonFieldType.NUMBER).description("배너 ID"), fieldWithPath("data[].name").type(JsonFieldType.STRING).description("배너명"), - fieldWithPath("data[].mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO)"), + fieldWithPath("data[].mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO). 프론트엔드에서 img/video 태그 분기용"), fieldWithPath("data[].bannerType").type(JsonFieldType.STRING).description("배너 유형"), fieldWithPath("data[].sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"), fieldWithPath("data[].isActive").type(JsonFieldType.BOOLEAN).description("활성화 상태"), @@ -148,7 +148,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("data.textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등)"), fieldWithPath("data.isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부"), fieldWithPath("data.targetUrl").type(JsonFieldType.VARIES).description("이동 URL. [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), - fieldWithPath("data.mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO)"), + fieldWithPath("data.mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE, VIDEO). 프론트엔드에서 img/video 태그 분기용"), fieldWithPath("data.bannerType").type(JsonFieldType.STRING).description("배너 유형"), fieldWithPath("data.sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"), fieldWithPath("data.startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), @@ -204,7 +204,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등, 기본값: RT)").optional(), fieldWithPath("isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부 (기본값: false)").optional(), fieldWithPath("targetUrl").type(JsonFieldType.VARIES).description("이동 URL (isExternalUrl=true 시 필수). [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), - fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO, 기본값: IMAGE)").optional(), + fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO, 기본값: IMAGE). 프론트엔드에서 img/video 태그 분기용").optional(), fieldWithPath("bannerType").type(JsonFieldType.STRING).description("배너 유형 (필수: CURATION/AD/SURVEY/PARTNERSHIP/ETC)"), fieldWithPath("sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서 (0 이상, 기본값: 0)").optional(), fieldWithPath("startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), @@ -269,7 +269,7 @@ class AdminBannerControllerDocsTest { fieldWithPath("textPosition").type(JsonFieldType.STRING).description("텍스트 위치 (RT/CENTER/LB 등)"), fieldWithPath("isExternalUrl").type(JsonFieldType.BOOLEAN).description("외부 URL 여부"), fieldWithPath("targetUrl").type(JsonFieldType.VARIES).description("이동 URL (isExternalUrl=true 시 필수). [주의] URL 형식 검증을 수행하지 않으므로 클라이언트에서 유효한 URL을 전달해야 합니다").optional(), - fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO)").optional(), + fieldWithPath("mediaType").type(JsonFieldType.STRING).description("미디어 유형 (IMAGE/VIDEO, 미입력 시 기존 값 유지). 프론트엔드에서 img/video 태그 분기용").optional(), fieldWithPath("bannerType").type(JsonFieldType.STRING).description("배너 유형 (필수: CURATION/AD/SURVEY/PARTNERSHIP/ETC)"), fieldWithPath("sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서 (0 이상, 필수)"), fieldWithPath("startDate").type(JsonFieldType.VARIES).description("시작일시").optional(), diff --git a/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java b/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java index 092212c95..b823f5194 100644 --- a/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java +++ b/bottlenote-product-api/src/test/java/app/docs/banner/RestBannerQueryControllerTest.java @@ -119,7 +119,8 @@ void getActiveBanners() throws Exception { fieldWithPath("data[].targetUrl").description("클릭 시 이동할 URL").optional(), fieldWithPath("data[].isExternalUrl") .description("외부 URL 여부 (true: 외부, false: 내부)"), - fieldWithPath("data[].mediaType").description("미디어 유형 (IMAGE, VIDEO)"), + fieldWithPath("data[].mediaType") + .description("미디어 유형 (IMAGE, VIDEO). 프론트엔드에서 img/video 태그 분기용"), fieldWithPath("data[].bannerType").description("배너 유형 (하단 BannerType 참조)"), fieldWithPath("data[].sortOrder").description("정렬 순서 (오름차순)"), fieldWithPath("data[].startDate").description("노출 시작일시 (nullable)").optional(),