From a25dd38449a7ee4668ec780be6c0be3a26fd7cb3 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:23:47 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9C=A0=ED=8B=B8=20=ED=99=95=EC=9E=A5=20?= =?UTF-8?q?(defaultIfBlank,=20joinOrDefault)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/ndgl/common/util/TextUtils.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java b/common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java index 14fb9d5..6fbcd19 100644 --- a/common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java +++ b/common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java @@ -1,5 +1,7 @@ package com.yapp.ndgl.common.util; +import java.util.Collection; + import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -22,4 +24,32 @@ public static String truncate(final String text, final int max) { } return text.substring(0, max - 1) + "…"; } + + /** + * 공백/빈 문자열이면 fallback을 반환한다. + * + * @param value 원본 텍스트 (nullable) + * @param fallback 비어 있을 때 반환할 값 + */ + public static String defaultIfBlank(final String value, final String fallback) { + return (value != null && !value.isBlank()) ? value : fallback; + } + + /** + * 컬렉션을 구분자로 join한다. null이거나 비어 있으면 fallback을 반환한다. + * + * @param items join 대상 (nullable) + * @param delimiter 구분자 + * @param fallback 비어 있을 때 반환할 값 + */ + public static String joinOrDefault( + final Collection items, + final String delimiter, + final String fallback + ) { + if (items == null || items.isEmpty()) { + return fallback; + } + return String.join(delimiter, items); + } } From e8d55567c29fbb32db818b38d8bc06d80023a455 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:24:20 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A0=9C=EC=95=88=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=A0=95=EB=B9=84=20(=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=8B=A4=EC=A4=91=ED=99=94,=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=A0=9C=EA=B1=B0,=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EC=9D=B4=EC=9C=A0=20=EC=84=A0=ED=83=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/UserSuggestedTemplateApi.java | 7 ++- .../AdminUserSuggestedTemplateResponse.java | 6 +-- .../CreateUserSuggestedTemplateRequest.java | 18 +++----- .../dto/UserSuggestedTemplateResponse.java | 44 ------------------- .../UserSuggestedTemplateCreatedEvent.java | 5 ++- .../UserSuggestedTemplateNotification.java | 14 +++--- .../UserSuggestedTemplateEventPublisher.java | 9 ++-- .../service/UserSuggestedTemplateService.java | 3 +- .../UserSuggestedTemplateConcurrencyTest.java | 4 +- .../yapp/ndgl/common/type/DomesticRegion.java | 12 ----- .../yapp/ndgl/common/type/TravelCategory.java | 9 +++- .../entity/UserSuggestedTemplateEntity.java | 22 +++++----- .../domain/travel/UserSuggestedTemplate.java | 9 ++-- .../mapper/UserSuggestedTemplateMapper.java | 2 - 14 files changed, 50 insertions(+), 114 deletions(-) delete mode 100644 application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/UserSuggestedTemplateResponse.java delete mode 100644 common/src/main/java/com/yapp/ndgl/common/type/DomesticRegion.java diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java index 70aaaf8..95a273d 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java @@ -37,10 +37,9 @@ public interface UserSuggestedTemplateApi { name = "요청 예시", value = """ { - "video_link": "https://youtu.be/abc12345678", - "recommend_reason": "산정호수 일출이 정말 아름다워요. 새벽 5시에 가면 혼자 볼 수 있어요.", - "category": "UNCATEGORIZED", - "region": "UNDEFINED" + "videoLink": "https://youtu.be/abc12345678", + "recommendReason": "산정호수 일출이 정말 아름다워요. 새벽 5시에 가면 혼자 볼 수 있어요.", + "category": ["FOOD", "CAFE"] } """ ) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java index 190d603..22e8ab2 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java @@ -1,8 +1,8 @@ package com.yapp.ndgl.application.domains.travel.controller.dto; import java.time.LocalDateTime; +import java.util.List; -import com.yapp.ndgl.common.type.DomesticRegion; import com.yapp.ndgl.common.type.SuggestionStatus; import com.yapp.ndgl.common.type.TravelCategory; import com.yapp.ndgl.domain.travel.UserSuggestedTemplate; @@ -13,8 +13,7 @@ public record AdminUserSuggestedTemplateResponse( String videoLink, String recommendReason, String suggesterUuid, - TravelCategory category, - DomesticRegion region, + List category, SuggestionStatus status, LocalDateTime createdAt ) { @@ -27,7 +26,6 @@ public static AdminUserSuggestedTemplateResponse toResponse(final UserSuggestedT domain.getRecommendReason(), domain.getSuggesterUuid(), domain.getCategory(), - domain.getRegion(), domain.getStatus(), domain.getCreatedAt() ); diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/CreateUserSuggestedTemplateRequest.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/CreateUserSuggestedTemplateRequest.java index 1874904..09b8cd5 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/CreateUserSuggestedTemplateRequest.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/CreateUserSuggestedTemplateRequest.java @@ -1,29 +1,25 @@ package com.yapp.ndgl.application.domains.travel.controller.dto; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.yapp.ndgl.common.type.DomesticRegion; +import java.util.List; + import com.yapp.ndgl.common.type.TravelCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; public record CreateUserSuggestedTemplateRequest( @Schema(description = "YouTube 영상 링크", example = "https://youtu.be/abc12345678", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank(message = "영상 링크는 필수입니다.") - @JsonProperty("video_link") String videoLink, - @Schema(description = "추천 이유", example = "산정호수 일출 명소", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "추천 이유는 필수입니다.") + @Schema(description = "추천 이유 (선택)", example = "산정호수 일출 명소") @Size(max = 1000, message = "추천 이유는 1000자 이하여야 합니다.") - @JsonProperty("recommend_reason") String recommendReason, - @Schema(description = "여행 카테고리 (선택)", example = "UNCATEGORIZED") - TravelCategory category, - - @Schema(description = "국내 지역 (선택)", example = "UNDEFINED") - DomesticRegion region + @Schema(description = "여행 카테고리 (복수 선택)", example = "[\"FOOD\"]", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "카테고리는 최소 한 개 이상 선택해야 합니다.") + List category ) { } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/UserSuggestedTemplateResponse.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/UserSuggestedTemplateResponse.java deleted file mode 100644 index c6c70a1..0000000 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/UserSuggestedTemplateResponse.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.yapp.ndgl.application.domains.travel.controller.dto; - -import com.yapp.ndgl.common.type.DomesticRegion; -import com.yapp.ndgl.common.type.SuggestionStatus; -import com.yapp.ndgl.common.type.TravelCategory; -import com.yapp.ndgl.domain.travel.UserSuggestedTemplate; - -import io.swagger.v3.oas.annotations.media.Schema; - -public record UserSuggestedTemplateResponse( - @Schema(description = "제안 템플릿 ID", example = "1") - Long id, - - @Schema(description = "영상 링크") - String videoLink, - - @Schema(description = "추천 이유") - String recommendReason, - - @Schema(description = "제안자 UUID") - String suggesterUuid, - - @Schema(description = "여행 카테고리") - TravelCategory category, - - @Schema(description = "국내 지역") - DomesticRegion region, - - @Schema(description = "검토 상태") - SuggestionStatus status -) { - - public static UserSuggestedTemplateResponse from(final UserSuggestedTemplate domain) { - return new UserSuggestedTemplateResponse( - domain.getId(), - domain.getVideoLink(), - domain.getRecommendReason(), - domain.getSuggesterUuid(), - domain.getCategory(), - domain.getRegion(), - domain.getStatus() - ); - } -} diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java index ae18ec1..c70d0b1 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java @@ -1,12 +1,13 @@ package com.yapp.ndgl.application.domains.travel.event; +import java.util.List; + public record UserSuggestedTemplateCreatedEvent( Long templateId, String videoId, String videoLink, String suggesterUuid, - String category, - String region, + List category, String recommendReason ) { } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java index 210f787..6ed18f1 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java @@ -11,6 +11,7 @@ import com.yapp.ndgl.clients.discord.request.DiscordEmbed; import com.yapp.ndgl.clients.discord.request.DiscordEmbed.Field; import com.yapp.ndgl.clients.discord.request.DiscordEmbed.Image; +import com.yapp.ndgl.common.util.TextUtils; import lombok.RequiredArgsConstructor; @@ -20,6 +21,8 @@ public final class UserSuggestedTemplateNotification implements DiscordNotificat private static final int EMBED_COLOR = 5763719; private static final String CONTENT_HEADER = "## 🎬 새로운 영상이 요청되었어요. 지금 확인해보세요!"; private static final String EMBED_TITLE = "영상 보기"; + private static final String EMPTY_PLACEHOLDER = "-"; + private static final String CATEGORY_DELIMITER = ", "; private final UserSuggestedTemplateCreatedEvent event; @@ -41,13 +44,12 @@ public String createContent() { public List createEmbeds() { List fields = List.of( - Field.of("카테고리", emptyIfBlank(event.category()), true), - Field.of("지역", emptyIfBlank(event.region()), true), + Field.of("카테고리", TextUtils.joinOrDefault(event.category(), CATEGORY_DELIMITER, EMPTY_PLACEHOLDER), true), Field.of("템플릿 ID", "#" + event.templateId(), true), Field.of("제안자", maskUuid(event.suggesterUuid()), false) ); - String description = StringUtils.hasText(event.recommendReason()) ? event.recommendReason() : null; + String description = TextUtils.defaultIfBlank(event.recommendReason(), null); Image image = StringUtils.hasText(event.videoId()) ? Image.of(DiscordPayloadConstraints.YOUTUBE_THUMBNAIL_URL_FORMAT.formatted(event.videoId())) : null; @@ -64,13 +66,9 @@ public List createEmbeds() { return List.of(embed); } - private static String emptyIfBlank(final String value) { - return StringUtils.hasText(value) ? value : "-"; - } - private static String maskUuid(final String uuid) { if (!StringUtils.hasText(uuid)) { - return "-"; + return EMPTY_PLACEHOLDER; } if (uuid.length() <= 8) { return uuid; diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java index cf2ea92..701b587 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java @@ -1,10 +1,13 @@ package com.yapp.ndgl.application.domains.travel.event.publisher; +import java.util.List; + import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import com.yapp.ndgl.application.domains.travel.controller.dto.CreateUserSuggestedTemplateRequest; import com.yapp.ndgl.application.domains.travel.event.UserSuggestedTemplateCreatedEvent; +import com.yapp.ndgl.common.type.TravelCategory; import lombok.RequiredArgsConstructor; @@ -20,8 +23,9 @@ public void publish( final String uuid, final CreateUserSuggestedTemplateRequest request ) { - String category = request.category() != null ? request.category().name() : null; - String region = request.region() != null ? request.region().name() : null; + List category = request.category().stream() + .map(TravelCategory::name) + .toList(); UserSuggestedTemplateCreatedEvent event = new UserSuggestedTemplateCreatedEvent( templateId, @@ -29,7 +33,6 @@ public void publish( request.videoLink(), uuid, category, - region, request.recommendReason() ); diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java index d139e68..ea96ee6 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java @@ -54,8 +54,7 @@ public Long createUserSuggestedTemplate( request.videoLink(), request.recommendReason(), uuid, - request.category(), - request.region() + request.category() ) ); diff --git a/application/src/test/java/com/yapp/ndgl/application/domains/travel/UserSuggestedTemplateConcurrencyTest.java b/application/src/test/java/com/yapp/ndgl/application/domains/travel/UserSuggestedTemplateConcurrencyTest.java index 90884cf..b27d231 100644 --- a/application/src/test/java/com/yapp/ndgl/application/domains/travel/UserSuggestedTemplateConcurrencyTest.java +++ b/application/src/test/java/com/yapp/ndgl/application/domains/travel/UserSuggestedTemplateConcurrencyTest.java @@ -16,7 +16,6 @@ import com.yapp.ndgl.application.domains.travel.controller.dto.CreateUserSuggestedTemplateRequest; import com.yapp.ndgl.application.domains.travel.facade.UserSuggestedTemplateFacade; import com.yapp.ndgl.application.executor.ConcurrencyExecutor; -import com.yapp.ndgl.common.type.DomesticRegion; import com.yapp.ndgl.common.type.SuggestionStatus; import com.yapp.ndgl.common.type.TravelCategory; import com.yapp.ndgl.domain.travel.entity.UserSuggestedTemplateEntity; @@ -46,8 +45,7 @@ void cleanup() { CreateUserSuggestedTemplateRequest request = new CreateUserSuggestedTemplateRequest( TEST_VIDEO_LINK, "동시성 테스트 추천 이유", - TravelCategory.UNCATEGORIZED, - DomesticRegion.UNDEFINED + List.of(TravelCategory.FOOD) ); AtomicInteger successCount = new AtomicInteger(0); diff --git a/common/src/main/java/com/yapp/ndgl/common/type/DomesticRegion.java b/common/src/main/java/com/yapp/ndgl/common/type/DomesticRegion.java deleted file mode 100644 index 39dd847..0000000 --- a/common/src/main/java/com/yapp/ndgl/common/type/DomesticRegion.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.yapp.ndgl.common.type; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum DomesticRegion { - UNDEFINED("미지정"); - - private final String label; -} diff --git a/common/src/main/java/com/yapp/ndgl/common/type/TravelCategory.java b/common/src/main/java/com/yapp/ndgl/common/type/TravelCategory.java index a8aece8..aa40559 100644 --- a/common/src/main/java/com/yapp/ndgl/common/type/TravelCategory.java +++ b/common/src/main/java/com/yapp/ndgl/common/type/TravelCategory.java @@ -6,7 +6,14 @@ @Getter @RequiredArgsConstructor public enum TravelCategory { - UNCATEGORIZED("미분류"); + FOOD("맛집"), + CAFE("카페/디저트"), + HEALING("힐링/풍경"), + ATTRACTION("명소"), + LOCAL("로컬"), + SHOPPING("쇼핑"), + ACTIVITY("액티비티/체험"), + BUDGET("가성비"); private final String label; } diff --git a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/travel/entity/UserSuggestedTemplateEntity.java b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/travel/entity/UserSuggestedTemplateEntity.java index 13fd8a4..1ec6355 100644 --- a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/travel/entity/UserSuggestedTemplateEntity.java +++ b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/travel/entity/UserSuggestedTemplateEntity.java @@ -1,6 +1,10 @@ package com.yapp.ndgl.domain.travel.entity; -import com.yapp.ndgl.common.type.DomesticRegion; +import java.util.List; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + import com.yapp.ndgl.common.type.SuggestionStatus; import com.yapp.ndgl.common.type.TravelCategory; import com.yapp.ndgl.domain.common.entity.BaseEntity; @@ -27,20 +31,16 @@ public class UserSuggestedTemplateEntity extends BaseEntity { @Column(name = "video_link", nullable = false, length = 500) private String videoLink; - @Column(name = "recommend_reason", nullable = false, length = 1000) + @Column(name = "recommend_reason", length = 1000) private String recommendReason; // NOTE: 사용자 시스템 정비 시 식별자 변경 가능성 있음 (UUID → User PK) @Column(name = "suggester_uuid", nullable = false, length = 64) private String suggesterUuid; - @Enumerated(EnumType.STRING) - @Column(name = "category", length = 50) - private TravelCategory category; - - @Enumerated(EnumType.STRING) - @Column(name = "region", length = 50) - private DomesticRegion region; + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "category", nullable = false, columnDefinition = "json") + private List category; @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false, length = 20) @@ -52,8 +52,7 @@ public UserSuggestedTemplateEntity( final String videoLink, final String recommendReason, final String suggesterUuid, - final TravelCategory category, - final DomesticRegion region, + final List category, final SuggestionStatus status ) { this.videoId = videoId; @@ -61,7 +60,6 @@ public UserSuggestedTemplateEntity( this.recommendReason = recommendReason; this.suggesterUuid = suggesterUuid; this.category = category; - this.region = region; this.status = status == null ? SuggestionStatus.PENDING : status; } } diff --git a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/UserSuggestedTemplate.java b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/UserSuggestedTemplate.java index 72b8c56..f479fb8 100644 --- a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/UserSuggestedTemplate.java +++ b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/UserSuggestedTemplate.java @@ -1,8 +1,8 @@ package com.yapp.ndgl.domain.travel; import java.time.LocalDateTime; +import java.util.List; -import com.yapp.ndgl.common.type.DomesticRegion; import com.yapp.ndgl.common.type.SuggestionStatus; import com.yapp.ndgl.common.type.TravelCategory; @@ -18,8 +18,7 @@ public class UserSuggestedTemplate { private String videoLink; private String recommendReason; private String suggesterUuid; - private TravelCategory category; - private DomesticRegion region; + private List category; private SuggestionStatus status; private LocalDateTime createdAt; @@ -28,8 +27,7 @@ public static UserSuggestedTemplate of( final String videoLink, final String recommendReason, final String suggesterUuid, - final TravelCategory category, - final DomesticRegion region + final List category ) { return UserSuggestedTemplate.builder() .videoId(videoId) @@ -37,7 +35,6 @@ public static UserSuggestedTemplate of( .recommendReason(recommendReason) .suggesterUuid(suggesterUuid) .category(category) - .region(region) .status(SuggestionStatus.PENDING) .build(); } diff --git a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/mapper/UserSuggestedTemplateMapper.java b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/mapper/UserSuggestedTemplateMapper.java index efd09e2..024d9ce 100644 --- a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/mapper/UserSuggestedTemplateMapper.java +++ b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/travel/mapper/UserSuggestedTemplateMapper.java @@ -16,7 +16,6 @@ public static UserSuggestedTemplate toDomain(final UserSuggestedTemplateEntity e .recommendReason(entity.getRecommendReason()) .suggesterUuid(entity.getSuggesterUuid()) .category(entity.getCategory()) - .region(entity.getRegion()) .status(entity.getStatus()) .createdAt(entity.getCreatedAt()) .build(); @@ -29,7 +28,6 @@ public static UserSuggestedTemplateEntity toEntity(final UserSuggestedTemplate d .recommendReason(domain.getRecommendReason()) .suggesterUuid(domain.getSuggesterUuid()) .category(domain.getCategory()) - .region(domain.getRegion()) .status(domain.getStatus()) .build(); } From 09cbc113e2f9d063ccc5ff9040b0efe7b79f6acf Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:24:36 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=ED=95=9C=EA=B8=80=20=EB=9D=BC=EB=B2=A8=EB=A1=9C=20=EB=85=B8?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/dto/AdminUserSuggestedTemplateResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java index 22e8ab2..dc9bb5d 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/AdminUserSuggestedTemplateResponse.java @@ -13,7 +13,7 @@ public record AdminUserSuggestedTemplateResponse( String videoLink, String recommendReason, String suggesterUuid, - List category, + List category, SuggestionStatus status, LocalDateTime createdAt ) { @@ -25,7 +25,7 @@ public static AdminUserSuggestedTemplateResponse toResponse(final UserSuggestedT domain.getVideoLink(), domain.getRecommendReason(), domain.getSuggesterUuid(), - domain.getCategory(), + domain.getCategory().stream().map(TravelCategory::getLabel).toList(), domain.getStatus(), domain.getCreatedAt() ); From 02d0f02214f71cde70fcde41d2b1d986a76e6996 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:31:01 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=20chore:=20=EB=B6=80=ED=8A=B8=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=9E=A9=20LOG=5FPATH=20=EB=AF=B8=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20=EC=9E=94=EC=97=AC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(logback=20property=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/src/main/resources/logback-spring.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/logback-spring.xml b/application/src/main/resources/logback-spring.xml index 769d485..83a0a09 100644 --- a/application/src/main/resources/logback-spring.xml +++ b/application/src/main/resources/logback-spring.xml @@ -1,7 +1,7 @@ - + From 0b4034a1aad51279b978beab319a4547d1e68f25 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:56:37 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=EA=B5=AC=EB=8F=85=20API?= =?UTF-8?q?=EB=A5=BC=20=EC=98=81=EC=83=81=20=EB=A7=81=ED=81=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../travel/controller/UserSuggestedTemplateApi.java | 9 +++++---- .../controller/UserSuggestedTemplateController.java | 10 +++++----- .../dto/SubscribeUserSuggestedTemplateRequest.java | 11 +++++++++++ .../travel/facade/UserSuggestedTemplateFacade.java | 6 ++++-- .../travel/service/UserSuggestedTemplateService.java | 11 ++++++++--- 5 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/SubscribeUserSuggestedTemplateRequest.java diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java index 95a273d..b15dd4d 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java @@ -1,11 +1,11 @@ package com.yapp.ndgl.application.domains.travel.controller; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import com.yapp.ndgl.application.domains.auth.annotation.CurrentUuid; import com.yapp.ndgl.application.domains.travel.controller.dto.CreateUserSuggestedTemplateRequest; +import com.yapp.ndgl.application.domains.travel.controller.dto.SubscribeUserSuggestedTemplateRequest; import com.yapp.ndgl.common.response.ErrorResponse; import com.yapp.ndgl.common.response.SuccessResponse; @@ -202,8 +202,9 @@ ResponseEntity> createUserSuggestedTemplate( summary = "게시 알림 구독", description = """ 다른 사용자가 이미 요청한 영상(TRAVEL-03-003)에 대해 게시 알림을 신청합니다. + 영상 링크로 요청하면 서버가 해당 영상의 PENDING 상태 제안 템플릿을 찾아 구독자로 등록합니다. 영상이 ACCEPTED 처리되면 구독자 전체에게 FCM 알림이 발송됩니다. - PENDING 상태인 영상에만 구독 신청이 가능합니다. ACCEPTED/DENIED 상태이면 400을 반환합니다. + 동일 영상에 PENDING 상태의 제안이 없으면(미등록 또는 이미 처리됨) 404를 반환합니다. """, security = @SecurityRequirement(name = "bearerAuth") ) @@ -294,7 +295,7 @@ ResponseEntity> createUserSuggestedTemplate( ) }) ResponseEntity> subscribe( - @PathVariable("id") Long templateId, - @CurrentUuid String uuid + @CurrentUuid String uuid, + @RequestBody @Valid final SubscribeUserSuggestedTemplateRequest request ); } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateController.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateController.java index d2ba723..fbccb5e 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateController.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateController.java @@ -2,7 +2,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -10,6 +9,7 @@ import com.yapp.ndgl.application.domains.auth.annotation.CurrentUuid; import com.yapp.ndgl.application.domains.travel.controller.dto.CreateUserSuggestedTemplateRequest; +import com.yapp.ndgl.application.domains.travel.controller.dto.SubscribeUserSuggestedTemplateRequest; import com.yapp.ndgl.application.domains.travel.facade.UserSuggestedTemplateFacade; import com.yapp.ndgl.common.response.SuccessResponse; @@ -35,12 +35,12 @@ public ResponseEntity> createUserSuggestedTemplate( } @Override - @PostMapping("/{id}/subscribe") + @PostMapping("/subscribe") public ResponseEntity> subscribe( - @PathVariable("id") final Long templateId, - @CurrentUuid String uuid + @CurrentUuid String uuid, + @Valid @RequestBody final SubscribeUserSuggestedTemplateRequest request ) { - userSuggestedTemplateFacade.subscribe(templateId, uuid); + userSuggestedTemplateFacade.subscribe(uuid, request); return ResponseEntity.ok(SuccessResponse.noContent()); } } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/SubscribeUserSuggestedTemplateRequest.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/SubscribeUserSuggestedTemplateRequest.java new file mode 100644 index 0000000..a11eff0 --- /dev/null +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/dto/SubscribeUserSuggestedTemplateRequest.java @@ -0,0 +1,11 @@ +package com.yapp.ndgl.application.domains.travel.controller.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +public record SubscribeUserSuggestedTemplateRequest( + @Schema(description = "YouTube 영상 링크", example = "https://youtu.be/abc12345678", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "영상 링크는 필수입니다.") + String videoLink +) { +} diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java index 4e99ae6..fdc00f7 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java @@ -3,6 +3,7 @@ import com.yapp.ndgl.application.common.annotation.Facade; import com.yapp.ndgl.application.domains.travel.controller.dto.AdminUserSuggestedTemplateResponse; import com.yapp.ndgl.application.domains.travel.controller.dto.CreateUserSuggestedTemplateRequest; +import com.yapp.ndgl.application.domains.travel.controller.dto.SubscribeUserSuggestedTemplateRequest; import com.yapp.ndgl.application.domains.travel.event.publisher.UserSuggestedTemplateEventPublisher; import com.yapp.ndgl.application.domains.travel.service.UserSuggestedTemplateService; import com.yapp.ndgl.application.utils.YoutubeUrlParser; @@ -30,8 +31,9 @@ public void createUserSuggestedTemplate( userSuggestedTemplateEventPublisher.publish(templateId, videoId, uuid, request); } - public void subscribe(final Long templateId, final String uuid) { - userSuggestedTemplateService.subscribe(templateId, uuid); + public void subscribe(final String uuid, final SubscribeUserSuggestedTemplateRequest request) { + String videoId = YoutubeUrlParser.extractVideoId(request.videoLink()); + userSuggestedTemplateService.subscribe(videoId, uuid); } public PageResponse readUserSuggestedTemplatesForAdmin( diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java index ea96ee6..045708e 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java @@ -62,9 +62,14 @@ public Long createUserSuggestedTemplate( return template.getId(); } - public void subscribe(final Long templateId, final String uuid) { - log.info("사용자 제안 여행 템플릿 구독을 신청합니다. templateId={}, subscriberUuid={}", templateId, uuid); - userSuggestedTemplateDomainService.subscribe(templateId, uuid); + public void subscribe(final String videoId, final String uuid) { + UserSuggestedTemplate template = userSuggestedTemplateDomainService + .findByVideoIdAndStatus(videoId, SuggestionStatus.PENDING) + .orElseThrow(() -> new GlobalException(TravelErrorCode.NOT_FOUND_SUGGESTED_TEMPLATE)); + + log.info("사용자 제안 여행 템플릿 구독을 신청합니다. videoId={}, templateId={}, subscriberUuid={}", + videoId, template.getId(), uuid); + userSuggestedTemplateDomainService.subscribe(template.getId(), uuid); } public PageResponse readUserSuggestedTemplatesForAdmin( From 1c02f33233bb793fdeee6a36f04e6543c1001b09 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Thu, 14 May 2026 15:56:49 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20Swagger=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=B3=B8=EB=AC=B8=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EA=B1=B0=20(DTO=20@Schema=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/UserSuggestedTemplateApi.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java index b15dd4d..ac88d29 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/travel/controller/UserSuggestedTemplateApi.java @@ -31,20 +31,6 @@ public interface UserSuggestedTemplateApi { """, security = @SecurityRequirement(name = "bearerAuth") ) - @io.swagger.v3.oas.annotations.parameters.RequestBody( - content = @Content( - examples = @ExampleObject( - name = "요청 예시", - value = """ - { - "videoLink": "https://youtu.be/abc12345678", - "recommendReason": "산정호수 일출이 정말 아름다워요. 새벽 5시에 가면 혼자 볼 수 있어요.", - "category": ["FOOD", "CAFE"] - } - """ - ) - ) - ) @ApiResponses({ @ApiResponse( responseCode = "200",