Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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). 프론트엔드에서 img/video 태그 분기용"),
fieldWithPath("data[].bannerType").type(JsonFieldType.STRING).description("배너 유형"),
fieldWithPath("data[].sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"),
fieldWithPath("data[].isActive").type(JsonFieldType.BOOLEAN).description("활성화 상태"),
Expand Down Expand Up @@ -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). 프론트엔드에서 img/video 태그 분기용"),
fieldWithPath("data.bannerType").type(JsonFieldType.STRING).description("배너 유형"),
fieldWithPath("data.sortOrder").type(JsonFieldType.NUMBER).description("정렬 순서"),
fieldWithPath("data.startDate").type(JsonFieldType.VARIES).description("시작일시").optional(),
Expand Down Expand Up @@ -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). 프론트엔드에서 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(),
Expand Down Expand Up @@ -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, 미입력 시 기존 값 유지). 프론트엔드에서 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(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,14 +13,15 @@ object BannerHelper {
fun createAdminBannerListResponse(
id: Long = 1L,
name: String = "테스트 배너",
mediaType: MediaType = MediaType.IMAGE,
bannerType: BannerType = BannerType.CURATION,
sortOrder: Int = 0,
isActive: Boolean = true,
startDate: LocalDateTime? = null,
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<AdminBannerListResponse> =
Expand All @@ -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,
Expand All @@ -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
)

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -106,6 +113,7 @@ public void update(
TextPosition textPosition,
Boolean isExternalUrl,
String targetUrl,
MediaType mediaType,
BannerType bannerType,
Integer sortOrder,
LocalDateTime startDate,
Expand All @@ -120,6 +128,7 @@ public void update(
this.textPosition = textPosition;
this.isExternalUrl = isExternalUrl;
this.targetUrl = targetUrl;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Banner.update()가 mediaType을 그대로 대입하고 있어, 상위 계층에서 null이 전달되면 @column(nullable=false) 제약을 위반하는 상태가 됩니다. 도메인 불변식 관점에서 update() 내부에서 null을 거부(예: 예외)하거나, null이면 기존 값 유지/기본값으로 보정해 엔티티가 항상 유효한 상태를 유지하도록 해주세요.

Suggested change
this.targetUrl = targetUrl;
this.targetUrl = targetUrl;
if (mediaType == null) {
// mediaType은 @Column(nullable = false) 제약을 가지므로 null을 허용하지 않는다.
throw new IllegalArgumentException("mediaType must not be null");
}

Copilot uses AI. Check for mistakes.
this.mediaType = mediaType != null ? mediaType : this.mediaType;
this.bannerType = bannerType;
this.sortOrder = sortOrder;
this.startDate = startDate;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,6 +16,7 @@ public record AdminBannerDetailResponse(
TextPosition textPosition,
Boolean isExternalUrl,
String targetUrl,
MediaType mediaType,
BannerType bannerType,
Integer sortOrder,
LocalDateTime startDate,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public Page<AdminBannerListResponse> searchForAdmin(
AdminBannerListResponse.class,
banner.id,
banner.name,
banner.mediaType,
banner.bannerType,
banner.sortOrder,
banner.isActive,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public AdminBannerDetailResponse getDetail(Long bannerId) {
banner.getTextPosition(),
banner.getIsExternalUrl(),
banner.getTargetUrl(),
banner.getMediaType(),
banner.getBannerType(),
banner.getSortOrder(),
banner.getStartDate(),
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public List<BannerResponse> getActiveBanners(Integer limit) {
.textPosition(banner.getTextPosition())
.targetUrl(banner.getTargetUrl())
.isExternalUrl(banner.getIsExternalUrl())
.mediaType(banner.getMediaType())
.bannerType(banner.getBannerType())
.sortOrder(banner.getSortOrder())
.startDate(banner.getStartDate())
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -60,6 +62,7 @@ public Banner persistBanner(
.descriptionB(name + " 설명B")
.imageUrl(imageUrl)
.textPosition(textPosition)
.mediaType(MediaType.IMAGE)
.bannerType(bannerType)
.sortOrder(sortOrder)
.isActive(isActive)
Expand Down Expand Up @@ -96,6 +99,7 @@ public List<Banner> 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)
Expand All @@ -122,6 +126,7 @@ public List<Banner> 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)
Expand All @@ -139,6 +144,7 @@ public List<Banner> 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)
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public Page<AdminBannerListResponse> searchForAdmin(
new AdminBannerListResponse(
b.getId(),
b.getName(),
b.getMediaType(),
b.getBannerType(),
b.getSortOrder(),
b.getIsActive(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

Expand Down
Loading
Loading