diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b7b3d1b..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 디폴트 무시된 파일 -/shelf/ -/workspace.xml -# 환경에 따라 달라지는 Maven 홈 디렉터리 -/mavenHomeManager.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 9018a0d..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 6ed36dd..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..fc7acb6 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 4 +} + + + + + + + + + + + + + + + true + true + false + false + + + + + + + 1749618504890 + + + + \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java index fec5d4e..dd8e603 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/service/SnsContentService.java @@ -42,7 +42,7 @@ public class SnsContentService implements SnsContentUseCase { public SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request) { // AI를 사용하여 SNS 콘텐츠 생성 String generatedContent = aiContentGenerator.generateSnsContent(request); - + // 플랫폼에 맞는 해시태그 생성 Platform platform = Platform.fromString(request.getPlatform()); List hashtags = aiContentGenerator.generateHashtags(generatedContent, platform); @@ -60,7 +60,7 @@ public class SnsContentService implements SnsContentUseCase { // 임시 콘텐츠 생성 (저장하지 않음) Content content = Content.builder() - .contentType(ContentType.SNS_POST) +// .contentType(ContentType.SNS_POST) .platform(platform) .title(request.getTitle()) .content(generatedContent) @@ -88,7 +88,7 @@ public class SnsContentService implements SnsContentUseCase { /** * SNS 콘텐츠 저장 - * + * * @param request SNS 콘텐츠 저장 요청 */ @Override @@ -107,7 +107,7 @@ public class SnsContentService implements SnsContentUseCase { // 콘텐츠 엔티티 생성 및 저장 Content content = Content.builder() - .contentType(ContentType.SNS_POST) +// .contentType(ContentType.SNS_POST) .platform(Platform.fromString(request.getPlatform())) .title(request.getTitle()) .content(request.getContent()) diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/PosterContentUseCase.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/PosterContentUseCase.java index 973b234..6bf2960 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/PosterContentUseCase.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/PosterContentUseCase.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/application/usecase/PosterContentUseCase.java package com.won.smarketing.content.application.usecase; import com.won.smarketing.content.presentation.dto.PosterContentCreateRequest; @@ -5,23 +6,21 @@ import com.won.smarketing.content.presentation.dto.PosterContentCreateResponse; import com.won.smarketing.content.presentation.dto.PosterContentSaveRequest; /** - * 포스터 콘텐츠 관련 Use Case 인터페이스 - * 홍보 포스터 생성 및 저장 기능 정의 + * 포스터 콘텐츠 관련 UseCase 인터페이스 + * Clean Architecture의 Application Layer에서 비즈니스 로직 정의 */ public interface PosterContentUseCase { - + /** * 포스터 콘텐츠 생성 - * * @param request 포스터 콘텐츠 생성 요청 - * @return 생성된 포스터 콘텐츠 정보 + * @return 포스터 콘텐츠 생성 응답 */ PosterContentCreateResponse generatePosterContent(PosterContentCreateRequest request); - + /** * 포스터 콘텐츠 저장 - * * @param request 포스터 콘텐츠 저장 요청 */ void savePosterContent(PosterContentSaveRequest request); -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java index e62902d..d2c6751 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/application/usecase/SnsContentUseCase.java package com.won.smarketing.content.application.usecase; import com.won.smarketing.content.presentation.dto.SnsContentCreateRequest; @@ -5,23 +6,21 @@ import com.won.smarketing.content.presentation.dto.SnsContentCreateResponse; import com.won.smarketing.content.presentation.dto.SnsContentSaveRequest; /** - * SNS 콘텐츠 관련 Use Case 인터페이스 - * SNS 게시물 생성 및 저장 기능 정의 + * SNS 콘텐츠 관련 UseCase 인터페이스 + * Clean Architecture의 Application Layer에서 비즈니스 로직 정의 */ public interface SnsContentUseCase { - + /** * SNS 콘텐츠 생성 - * * @param request SNS 콘텐츠 생성 요청 - * @return 생성된 SNS 콘텐츠 정보 + * @return SNS 콘텐츠 생성 응답 */ SnsContentCreateResponse generateSnsContent(SnsContentCreateRequest request); - + /** * SNS 콘텐츠 저장 - * * @param request SNS 콘텐츠 저장 요청 */ void saveSnsContent(SnsContentSaveRequest request); -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/JpaConfig.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/JpaConfig.java deleted file mode 100644 index e95312d..0000000 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/config/JpaConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -// marketing-content/src/main/java/com/won/smarketing/content/config/JpaConfig.java -package com.won.smarketing.content.config; - -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -/** - * JPA 설정 클래스 - * - * @author smarketing-team - * @version 1.0 - */ -@Configuration -@EntityScan(basePackages = "com.won.smarketing.content.infrastructure.entity") -@EnableJpaRepositories(basePackages = "com.won.smarketing.content.infrastructure.repository") -public class JpaConfig { -} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Content.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Content.java index 4e95d02..549520c 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Content.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Content.java @@ -46,7 +46,7 @@ public class Content { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "content_id") + @Column(name = "id") private Long id; // ==================== 콘텐츠 분류 ==================== @@ -97,8 +97,7 @@ public class Content { private ContentStatus status = ContentStatus.DRAFT; // ==================== AI 생성 조건 (Embedded) ==================== - - @Embedded + //@Embedded @AttributeOverrides({ @AttributeOverride(name = "toneAndManner", column = @Column(name = "tone_and_manner", length = 50)), @AttributeOverride(name = "promotionType", column = @Column(name = "promotion_type", length = 50)), @@ -191,15 +190,15 @@ public class Content { * @param status 새로운 상태 * @throws IllegalStateException 잘못된 상태 전환인 경우 */ - public void changeStatus(ContentStatus status) { - validateStatusTransition(this.status, status); - - if (status == ContentStatus.PUBLISHED) { - validateForPublication(); - } - - this.status = status; - } +// public void changeStatus(ContentStatus status) { +// validateStatusTransition(this.status, status); +// +// if (status == ContentStatus.PUBLISHED) { +// validateForPublication(); +// } +// +// this.status = status; +// } /** * 홍보 기간 설정 @@ -352,9 +351,9 @@ public class Content { * * @return SNS 게시물이면 true */ - public boolean isSnsContent() { - return this.contentType == ContentType.SNS_POST; - } +// public boolean isSnsContent() { +// return this.contentType == ContentType.SNS_POST; +// } /** * 포스터 콘텐츠 여부 확인 @@ -424,11 +423,11 @@ public class Content { /** * 상태 전환 유효성 검증 */ - private void validateStatusTransition(ContentStatus from, ContentStatus to) { - if (from == ContentStatus.ARCHIVED && to != ContentStatus.PUBLISHED) { - throw new IllegalStateException("보관된 콘텐츠는 발행 상태로만 변경할 수 있습니다."); - } - } +// private void validateStatusTransition(ContentStatus from, ContentStatus to) { +// if (from == ContentStatus.ARCHIVED && to != ContentStatus.PUBLISHED) { +// throw new IllegalStateException("보관된 콘텐츠는 발행 상태로만 변경할 수 있습니다."); +// } +// } /** * 발행을 위한 유효성 검증 @@ -502,7 +501,7 @@ public class Content { public static Content createSnsContent(String title, String content, Platform platform, Long storeId, CreationConditions conditions) { Content snsContent = Content.builder() - .contentType(ContentType.SNS_POST) +// .contentType(ContentType.SNS_POST) .platform(platform) .title(title) .content(content) diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentId.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentId.java index 13bb3b0..25220a8 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentId.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentId.java @@ -1,30 +1,53 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentId.java package com.won.smarketing.content.domain.model; -import lombok.*; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; /** - * 콘텐츠 식별자 값 객체 - * 콘텐츠의 고유 식별자를 나타내는 도메인 객체 + * 콘텐츠 ID 값 객체 + * Clean Architecture의 Domain Layer에 위치하는 식별자 */ +@Embeddable @Getter -@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) -@EqualsAndHashCode public class ContentId { private Long value; /** * ContentId 생성 팩토리 메서드 - * - * @param value 식별자 값 + * @param value ID 값 * @return ContentId 인스턴스 */ public static ContentId of(Long value) { if (value == null || value <= 0) { - throw new IllegalArgumentException("ContentId는 양수여야 합니다."); + throw new IllegalArgumentException("ContentId 값은 양수여야 합니다."); } return new ContentId(value); } -} + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContentId contentId = (ContentId) o; + return Objects.equals(value, contentId.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "ContentId{" + value + '}'; + } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentStatus.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentStatus.java index c40ec47..b235310 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentStatus.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentStatus.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentStatus.java package com.won.smarketing.content.domain.model; import lombok.Getter; @@ -5,35 +6,35 @@ import lombok.RequiredArgsConstructor; /** * 콘텐츠 상태 열거형 - * 콘텐츠의 생명주기 상태 정의 + * Clean Architecture의 Domain Layer에 위치하는 비즈니스 규칙 */ @Getter @RequiredArgsConstructor public enum ContentStatus { - + DRAFT("임시저장"), - PUBLISHED("발행됨"), - ARCHIVED("보관됨"); + PUBLISHED("게시됨"), + SCHEDULED("예약됨"), + DELETED("삭제됨"), + PROCESSING("처리중"); private final String displayName; /** * 문자열로부터 ContentStatus 변환 - * - * @param status 상태 문자열 - * @return ContentStatus + * @param value 문자열 값 + * @return ContentStatus enum + * @throws IllegalArgumentException 유효하지 않은 값인 경우 */ - public static ContentStatus fromString(String status) { - if (status == null) { - return DRAFT; + public static ContentStatus fromString(String value) { + if (value == null) { + throw new IllegalArgumentException("ContentStatus 값은 null일 수 없습니다."); } - - for (ContentStatus s : ContentStatus.values()) { - if (s.name().equalsIgnoreCase(status)) { - return s; - } + + try { + return ContentStatus.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("유효하지 않은 ContentStatus 값입니다: " + value); } - - throw new IllegalArgumentException("알 수 없는 콘텐츠 상태: " + status); } -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentType.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentType.java index dd91b91..f70228b 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentType.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentType.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/model/ContentType.java package com.won.smarketing.content.domain.model; import lombok.Getter; @@ -5,34 +6,34 @@ import lombok.RequiredArgsConstructor; /** * 콘텐츠 타입 열거형 - * 지원되는 마케팅 콘텐츠 유형 정의 + * Clean Architecture의 Domain Layer에 위치하는 비즈니스 규칙 */ @Getter @RequiredArgsConstructor public enum ContentType { - - SNS_POST("SNS 게시물"), - POSTER("홍보 포스터"); + + SNS("SNS 게시물"), + POSTER("홍보 포스터"), + VIDEO("동영상"), + BLOG("블로그 포스트"); private final String displayName; /** * 문자열로부터 ContentType 변환 - * - * @param type 타입 문자열 - * @return ContentType + * @param value 문자열 값 + * @return ContentType enum + * @throws IllegalArgumentException 유효하지 않은 값인 경우 */ - public static ContentType fromString(String type) { - if (type == null) { - return null; + public static ContentType fromString(String value) { + if (value == null) { + throw new IllegalArgumentException("ContentType 값은 null일 수 없습니다."); } - - for (ContentType contentType : ContentType.values()) { - if (contentType.name().equalsIgnoreCase(type)) { - return contentType; - } + + try { + return ContentType.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("유효하지 않은 ContentType 값입니다: " + value); } - - throw new IllegalArgumentException("알 수 없는 콘텐츠 타입: " + type); } -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/CreationConditions.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/CreationConditions.java index cf3c04e..cb1f914 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/CreationConditions.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/CreationConditions.java @@ -1,66 +1,68 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/model/CreationConditions.java package com.won.smarketing.content.domain.model; -import lombok.*; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDate; /** * 콘텐츠 생성 조건 도메인 모델 - * AI 콘텐츠 생성 시 사용되는 조건 정보 + * Clean Architecture의 Domain Layer에 위치하는 값 객체 */ +@Entity +@Table(name = "contents_conditions") @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor @AllArgsConstructor -@Builder(toBuilder = true) +@Builder public class CreationConditions { - /** - * 홍보 대상 카테고리 - */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + //@OneToOne(mappedBy = "creationConditions") + @Column(name = "content", length = 100) + private Content content; + + @Column(name = "category", length = 100) private String category; - /** - * 특별 요구사항 - */ + @Column(name = "requirement", columnDefinition = "TEXT") private String requirement; - /** - * 톤앤매너 - */ + @Column(name = "tone_and_manner", length = 100) private String toneAndManner; - /** - * 감정 강도 - */ + @Column(name = "emotion_intensity", length = 100) private String emotionIntensity; - /** - * 이벤트명 - */ + @Column(name = "event_name", length = 200) private String eventName; - /** - * 홍보 시작일 - */ + @Column(name = "start_date") private LocalDate startDate; - /** - * 홍보 종료일 - */ + @Column(name = "end_date") private LocalDate endDate; - /** - * 사진 스타일 (포스터용) - */ + @Column(name = "photo_style", length = 100) private String photoStyle; - /** - * 타겟 고객 - */ - private String targetAudience; - - /** - * 프로모션 타입 - */ + @Column(name = "promotionType", length = 100) private String promotionType; -} + + public CreationConditions(String category, String requirement, String toneAndManner, String emotionIntensity, String eventName, LocalDate startDate, LocalDate endDate, String photoStyle, String promotionType) { + } +// /** +// * 콘텐츠와의 연관관계 설정 +// * @param content 연관된 콘텐츠 +// */ +// public void setContent(Content content) { +// this.content = content; +// } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Platform.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Platform.java index acd6b33..66e266c 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Platform.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/model/Platform.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/model/Platform.java package com.won.smarketing.content.domain.model; import lombok.Getter; @@ -5,35 +6,36 @@ import lombok.RequiredArgsConstructor; /** * 플랫폼 열거형 - * 콘텐츠가 게시될 플랫폼 정의 + * Clean Architecture의 Domain Layer에 위치하는 비즈니스 규칙 */ @Getter @RequiredArgsConstructor public enum Platform { - + INSTAGRAM("인스타그램"), NAVER_BLOG("네이버 블로그"), - GENERAL("범용"); + FACEBOOK("페이스북"), + KAKAO_STORY("카카오스토리"), + YOUTUBE("유튜브"), + GENERAL("일반"); private final String displayName; /** * 문자열로부터 Platform 변환 - * - * @param platform 플랫폼 문자열 - * @return Platform + * @param value 문자열 값 + * @return Platform enum + * @throws IllegalArgumentException 유효하지 않은 값인 경우 */ - public static Platform fromString(String platform) { - if (platform == null) { - return GENERAL; + public static Platform fromString(String value) { + if (value == null) { + throw new IllegalArgumentException("Platform 값은 null일 수 없습니다."); } - - for (Platform p : Platform.values()) { - if (p.name().equalsIgnoreCase(platform)) { - return p; - } + + try { + return Platform.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("유효하지 않은 Platform 값입니다: " + value); } - - throw new IllegalArgumentException("알 수 없는 플랫폼: " + platform); } -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/ContentRepository.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/ContentRepository.java index 818506f..a2bfc43 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/ContentRepository.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/ContentRepository.java @@ -1,40 +1,36 @@ +// marketing-content/src/main/java/com/won/smarketing/content/domain/repository/ContentRepository.java package com.won.smarketing.content.domain.repository; import com.won.smarketing.content.domain.model.Content; import com.won.smarketing.content.domain.model.ContentId; import com.won.smarketing.content.domain.model.ContentType; import com.won.smarketing.content.domain.model.Platform; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; /** - * 콘텐츠 저장소 인터페이스 - * 콘텐츠 도메인의 데이터 접근 추상화 + * 콘텐츠 리포지토리 인터페이스 + * Clean Architecture의 Domain Layer에서 데이터 접근 정의 */ -@Repository public interface ContentRepository { - + /** * 콘텐츠 저장 - * * @param content 저장할 콘텐츠 * @return 저장된 콘텐츠 */ Content save(Content content); - + /** - * 콘텐츠 ID로 조회 - * + * ID로 콘텐츠 조회 * @param id 콘텐츠 ID - * @return 콘텐츠 (Optional) + * @return 조회된 콘텐츠 */ Optional findById(ContentId id); - + /** * 필터 조건으로 콘텐츠 목록 조회 - * * @param contentType 콘텐츠 타입 * @param platform 플랫폼 * @param period 기간 @@ -42,19 +38,17 @@ public interface ContentRepository { * @return 콘텐츠 목록 */ List findByFilters(ContentType contentType, Platform platform, String period, String sortBy); - + /** * 진행 중인 콘텐츠 목록 조회 - * * @param period 기간 * @return 진행 중인 콘텐츠 목록 */ List findOngoingContents(String period); - + /** - * 콘텐츠 삭제 - * + * ID로 콘텐츠 삭제 * @param id 삭제할 콘텐츠 ID */ void deleteById(ContentId id); -} +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/SpringDataContentRepository.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/SpringDataContentRepository.java new file mode 100644 index 0000000..d3a6e42 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/domain/repository/SpringDataContentRepository.java @@ -0,0 +1,38 @@ +package com.won.smarketing.content.domain.repository; +import com.won.smarketing.content.infrastructure.entity.ContentEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * Spring Data JPA ContentRepository + * JPA 기반 콘텐츠 데이터 접근 + */ +@Repository +public interface SpringDataContentRepository extends JpaRepository { + + /** + * 매장별 콘텐츠 조회 + * + * @param storeId 매장 ID + * @return 콘텐츠 목록 + */ + List findByStoreId(Long storeId); + + /** + * 콘텐츠 타입별 조회 + * + * @param contentType 콘텐츠 타입 + * @return 콘텐츠 목록 + */ + List findByContentType(String contentType); + + /** + * 플랫폼별 조회 + * + * @param platform 플랫폼 + * @return 콘텐츠 목록 + */ + List findByPlatform(String platform); +} diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentConditionsJpaEntity.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentConditionsJpaEntity.java index 17f49f8..940bbba 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentConditionsJpaEntity.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentConditionsJpaEntity.java @@ -1,6 +1,8 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentConditionsJpaEntity.java package com.won.smarketing.content.infrastructure.entity; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -8,24 +10,20 @@ import lombok.Setter; import java.time.LocalDate; /** - * 콘텐츠 조건 JPA 엔티티 - * - * @author smarketing-team - * @version 1.0 + * 콘텐츠 생성 조건 JPA 엔티티 */ @Entity -@Table(name = "contents_conditions") +@Table(name = "content_conditions") @Getter @Setter -@NoArgsConstructor public class ContentConditionsJpaEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne - @JoinColumn(name = "content_id") + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "content_id", nullable = false) private ContentJpaEntity content; @Column(name = "category", length = 100) @@ -37,7 +35,7 @@ public class ContentConditionsJpaEntity { @Column(name = "tone_and_manner", length = 100) private String toneAndManner; - @Column(name = "emotion_intensity", length = 100) + @Column(name = "emotion_intensity", length = 50) private String emotionIntensity; @Column(name = "event_name", length = 200) @@ -52,9 +50,9 @@ public class ContentConditionsJpaEntity { @Column(name = "photo_style", length = 100) private String photoStyle; - @Column(name = "TargetAudience", length = 100) + @Column(name = "target_audience", length = 200) private String targetAudience; - @Column(name = "PromotionType", length = 100) - private String PromotionType; -} + @Column(name = "promotion_type", length = 100) + private String promotionType; +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentEntity.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentEntity.java new file mode 100644 index 0000000..ba941d4 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentEntity.java @@ -0,0 +1,60 @@ +package com.won.smarketing.content.infrastructure.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +/** + * 콘텐츠 엔티티 + * 콘텐츠 정보를 데이터베이스에 저장하기 위한 JPA 엔티티 + */ +@Entity +@Table(name = "contents") +@Data +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class ContentEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "content_type", nullable = false) + private String contentType; + + @Column(name = "platform", nullable = false) + private String platform; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "content", columnDefinition = "TEXT") + private String content; + + @Column(name = "hashtags") + private String hashtags; + + @Column(name = "images", columnDefinition = "TEXT") + private String images; + + @Column(name = "status", nullable = false) + private String status; + + @Column(name = "store_id", nullable = false) + private Long storeId; + + @CreatedDate + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentJpaEntity.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentJpaEntity.java index 7f87560..2bd786a 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentJpaEntity.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentJpaEntity.java @@ -1,27 +1,24 @@ -// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/entity/ContentJpaEntity.java package com.won.smarketing.content.infrastructure.entity; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; -import java.util.List; /** * 콘텐츠 JPA 엔티티 - * - * @author smarketing-team - * @version 1.0 */ @Entity @Table(name = "contents") @Getter @Setter -@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) public class ContentJpaEntity { @Id @@ -43,24 +40,24 @@ public class ContentJpaEntity { @Column(name = "content", columnDefinition = "TEXT") private String content; - @Column(name = "hashtags", columnDefinition = "JSON") + @Column(name = "hashtags", columnDefinition = "TEXT") private String hashtags; - @Column(name = "images", columnDefinition = "JSON") + @Column(name = "images", columnDefinition = "TEXT") private String images; - @Column(name = "status", length = 50) + @Column(name = "status", nullable = false, length = 20) private String status; - @CreationTimestamp - @Column(name = "created_at") + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; - @UpdateTimestamp + @LastModifiedDate @Column(name = "updated_at") private LocalDateTime updatedAt; - // 연관 엔티티 + // CreationConditions와의 관계 - OneToOne으로 별도 엔티티로 관리 @OneToOne(mappedBy = "content", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private ContentConditionsJpaEntity conditions; } \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiContentGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiContentGenerator.java new file mode 100644 index 0000000..b1d0e6d --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiContentGenerator.java @@ -0,0 +1,32 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiContentGenerator.java +package com.won.smarketing.content.infrastructure.external; + +import com.won.smarketing.content.domain.model.Platform; +import com.won.smarketing.content.domain.model.CreationConditions; + +import java.util.List; + +/** + * AI 콘텐츠 생성 인터페이스 + * Clean Architecture의 Infrastructure Layer에서 외부 AI 서비스와의 연동 정의 + */ +public interface AiContentGenerator { + + /** + * SNS 콘텐츠 생성 + * @param title 제목 + * @param category 카테고리 + * @param platform 플랫폼 + * @param conditions 생성 조건 + * @return 생성된 콘텐츠 텍스트 + */ + String generateSnsContent(String title, String category, Platform platform, CreationConditions conditions); + + /** + * 해시태그 생성 + * @param content 콘텐츠 내용 + * @param platform 플랫폼 + * @return 생성된 해시태그 목록 + */ + List generateHashtags(String content, Platform platform); +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiPosterGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiPosterGenerator.java new file mode 100644 index 0000000..8bbe931 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiPosterGenerator.java @@ -0,0 +1,29 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/AiPosterGenerator.java +package com.won.smarketing.content.infrastructure.external; + +import com.won.smarketing.content.domain.model.CreationConditions; + +import java.util.Map; + +/** + * AI 포스터 생성 인터페이스 + * Clean Architecture의 Infrastructure Layer에서 외부 AI 서비스와의 연동 정의 + */ +public interface AiPosterGenerator { + + /** + * 포스터 이미지 생성 + * @param title 제목 + * @param category 카테고리 + * @param conditions 생성 조건 + * @return 생성된 포스터 이미지 URL + */ + String generatePoster(String title, String category, CreationConditions conditions); + + /** + * 포스터 다양한 사이즈 생성 + * @param originalImage 원본 이미지 URL + * @return 사이즈별 이미지 URL 맵 + */ + Map generatePosterSizes(String originalImage); +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java new file mode 100644 index 0000000..5cf42a4 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java @@ -0,0 +1,125 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiContentGenerator.java +package com.won.smarketing.content.infrastructure.external; + +import com.won.smarketing.content.domain.model.Platform; +import com.won.smarketing.content.domain.model.CreationConditions; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +/** + * Claude AI를 활용한 콘텐츠 생성 구현체 + * Clean Architecture의 Infrastructure Layer에 위치 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class ClaudeAiContentGenerator implements AiContentGenerator { + + /** + * SNS 콘텐츠 생성 + * Claude AI API를 호출하여 SNS 게시물을 생성합니다. + * + * @param title 제목 + * @param category 카테고리 + * @param platform 플랫폼 + * @param conditions 생성 조건 + * @return 생성된 콘텐츠 텍스트 + */ + @Override + public String generateSnsContent(String title, String category, Platform platform, CreationConditions conditions) { + try { + // Claude AI API 호출 로직 (실제 구현에서는 HTTP 클라이언트를 사용) + String prompt = buildContentPrompt(title, category, platform, conditions); + + // TODO: 실제 Claude AI API 호출 + // 현재는 더미 데이터 반환 + return generateDummySnsContent(title, platform); + + } catch (Exception e) { + log.error("AI 콘텐츠 생성 실패: {}", e.getMessage(), e); + return generateFallbackContent(title, platform); + } + } + + /** + * 해시태그 생성 + * 콘텐츠 내용을 분석하여 관련 해시태그를 생성합니다. + * + * @param content 콘텐츠 내용 + * @param platform 플랫폼 + * @return 생성된 해시태그 목록 + */ + @Override + public List generateHashtags(String content, Platform platform) { + try { + // TODO: 실제 Claude AI API 호출하여 해시태그 생성 + // 현재는 더미 데이터 반환 + return generateDummyHashtags(platform); + + } catch (Exception e) { + log.error("해시태그 생성 실패: {}", e.getMessage(), e); + return Arrays.asList("#맛집", "#신메뉴", "#추천"); + } + } + + /** + * AI 프롬프트 생성 + */ + private String buildContentPrompt(String title, String category, Platform platform, CreationConditions conditions) { + StringBuilder prompt = new StringBuilder(); + prompt.append("다음 조건에 맞는 ").append(platform.getDisplayName()).append(" 게시물을 작성해주세요:\n"); + prompt.append("제목: ").append(title).append("\n"); + prompt.append("카테고리: ").append(category).append("\n"); + + if (conditions.getRequirement() != null) { + prompt.append("요구사항: ").append(conditions.getRequirement()).append("\n"); + } + if (conditions.getToneAndManner() != null) { + prompt.append("톤앤매너: ").append(conditions.getToneAndManner()).append("\n"); + } + if (conditions.getEmotionIntensity() != null) { + prompt.append("감정 강도: ").append(conditions.getEmotionIntensity()).append("\n"); + } + + return prompt.toString(); + } + + /** + * 더미 SNS 콘텐츠 생성 (개발용) + */ + private String generateDummySnsContent(String title, Platform platform) { + switch (platform) { + case INSTAGRAM: + return String.format("🎉 %s\n\n맛있는 순간을 놓치지 마세요! 새로운 맛의 경험이 여러분을 기다리고 있어요. 따뜻한 분위기에서 즐기는 특별한 시간을 만들어보세요.\n\n📍 지금 바로 방문해보세요!", title); + case NAVER_BLOG: + return String.format("안녕하세요! 오늘은 %s에 대해 소개해드리려고 해요.\n\n정성스럽게 준비한 새로운 메뉴로 고객 여러분께 더 나은 경험을 선사하고 싶습니다. 많은 관심과 사랑 부탁드려요!", title); + default: + return String.format("%s - 새로운 경험을 만나보세요!", title); + } + } + + /** + * 더미 해시태그 생성 (개발용) + */ + private List generateDummyHashtags(Platform platform) { + switch (platform) { + case INSTAGRAM: + return Arrays.asList("#맛집", "#신메뉴", "#인스타그램", "#데일리", "#추천", "#음식스타그램"); + case NAVER_BLOG: + return Arrays.asList("#맛집", "#리뷰", "#추천", "#신메뉴", "#블로그"); + default: + return Arrays.asList("#맛집", "#신메뉴", "#추천"); + } + } + + /** + * 폴백 콘텐츠 생성 (AI 서비스 실패 시) + */ + private String generateFallbackContent(String title, Platform platform) { + return String.format("🎉 %s\n\n새로운 소식을 전해드립니다. 많은 관심 부탁드려요!", title); + } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java new file mode 100644 index 0000000..a667545 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java @@ -0,0 +1,118 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/external/ClaudeAiPosterGenerator.java +package com.won.smarketing.content.infrastructure.external; + +import com.won.smarketing.content.domain.model.CreationConditions; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * Claude AI를 활용한 포스터 생성 구현체 + * Clean Architecture의 Infrastructure Layer에 위치 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class ClaudeAiPosterGenerator implements AiPosterGenerator { + + /** + * 포스터 이미지 생성 + * Claude AI API를 호출하여 홍보 포스터를 생성합니다. + * + * @param title 제목 + * @param category 카테고리 + * @param conditions 생성 조건 + * @return 생성된 포스터 이미지 URL + */ + @Override + public String generatePoster(String title, String category, CreationConditions conditions) { + try { + // Claude AI API 호출 로직 (실제 구현에서는 HTTP 클라이언트를 사용) + String prompt = buildPosterPrompt(title, category, conditions); + + // TODO: 실제 Claude AI API 호출 + // 현재는 더미 데이터 반환 + return generateDummyPosterUrl(title); + + } catch (Exception e) { + log.error("AI 포스터 생성 실패: {}", e.getMessage(), e); + return generateFallbackPosterUrl(); + } + } + + /** + * 포스터 다양한 사이즈 생성 + * 원본 포스터를 기반으로 다양한 사이즈의 포스터를 생성합니다. + * + * @param originalImage 원본 이미지 URL + * @return 사이즈별 이미지 URL 맵 + */ + @Override + public Map generatePosterSizes(String originalImage) { + try { + // TODO: 실제 이미지 리사이징 API 호출 + // 현재는 더미 데이터 반환 + return generateDummyPosterSizes(originalImage); + + } catch (Exception e) { + log.error("포스터 사이즈 생성 실패: {}", e.getMessage(), e); + return new HashMap<>(); + } + } + + /** + * AI 포스터 프롬프트 생성 + */ + private String buildPosterPrompt(String title, String category, CreationConditions conditions) { + StringBuilder prompt = new StringBuilder(); + prompt.append("다음 조건에 맞는 홍보 포스터를 생성해주세요:\n"); + prompt.append("제목: ").append(title).append("\n"); + prompt.append("카테고리: ").append(category).append("\n"); + + if (conditions.getPhotoStyle() != null) { + prompt.append("사진 스타일: ").append(conditions.getPhotoStyle()).append("\n"); + } + if (conditions.getRequirement() != null) { + prompt.append("요구사항: ").append(conditions.getRequirement()).append("\n"); + } + if (conditions.getToneAndManner() != null) { + prompt.append("톤앤매너: ").append(conditions.getToneAndManner()).append("\n"); + } + + return prompt.toString(); + } + + /** + * 더미 포스터 URL 생성 (개발용) + */ + private String generateDummyPosterUrl(String title) { + return String.format("https://example.com/posters/%s-poster.jpg", + title.replaceAll("\\s+", "-").toLowerCase()); + } + + /** + * 더미 포스터 사이즈별 URL 생성 (개발용) + */ + private Map generateDummyPosterSizes(String originalImage) { + Map sizes = new HashMap<>(); + String baseUrl = originalImage.substring(0, originalImage.lastIndexOf(".")); + String extension = originalImage.substring(originalImage.lastIndexOf(".")); + + sizes.put("small", baseUrl + "-small" + extension); + sizes.put("medium", baseUrl + "-medium" + extension); + sizes.put("large", baseUrl + "-large" + extension); + sizes.put("xlarge", baseUrl + "-xlarge" + extension); + + return sizes; + } + + /** + * 폴백 포스터 URL 생성 (AI 서비스 실패 시) + */ + private String generateFallbackPosterUrl() { + return "https://example.com/posters/default-poster.jpg"; + } +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/mapper/ContentMapper.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/mapper/ContentMapper.java index 49cc6b4..a03954f 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/mapper/ContentMapper.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/mapper/ContentMapper.java @@ -91,7 +91,7 @@ public class ContentMapper { entity.getConditions().getStartDate(), entity.getConditions().getEndDate(), entity.getConditions().getPhotoStyle(), - entity.getConditions().getTargetAudience(), + // entity.getConditions().getTargetAudience(), entity.getConditions().getPromotionType() ); } diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepository.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepository.java index 9396d4d..da461e5 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepository.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepository.java @@ -1,3 +1,4 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepository.java package com.won.smarketing.content.infrastructure.repository; import com.won.smarketing.content.domain.model.Content; @@ -5,60 +6,44 @@ import com.won.smarketing.content.domain.model.ContentId; import com.won.smarketing.content.domain.model.ContentType; import com.won.smarketing.content.domain.model.Platform; import com.won.smarketing.content.domain.repository.ContentRepository; -import com.won.smarketing.content.infrastructure.entity.ContentJpaEntity; -import com.won.smarketing.content.infrastructure.mapper.ContentMapper; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; /** - * JPA 기반 콘텐츠 Repository 구현체 - * - * @author smarketing-team - * @version 1.0 + * JPA를 활용한 콘텐츠 리포지토리 구현체 + * Clean Architecture의 Infrastructure Layer에 위치 */ @Repository @RequiredArgsConstructor -@Slf4j public class JpaContentRepository implements ContentRepository { - private final SpringDataContentRepository springDataContentRepository; - private final ContentMapper contentMapper; + private final JpaContentRepositoryInterface jpaRepository; /** - * 콘텐츠를 저장합니다. - * + * 콘텐츠 저장 * @param content 저장할 콘텐츠 * @return 저장된 콘텐츠 */ @Override public Content save(Content content) { - log.debug("Saving content: {}", content.getId()); - ContentJpaEntity entity = contentMapper.toEntity(content); - ContentJpaEntity savedEntity = springDataContentRepository.save(entity); - return contentMapper.toDomain(savedEntity); + return jpaRepository.save(content); } /** - * ID로 콘텐츠를 조회합니다. - * + * ID로 콘텐츠 조회 * @param id 콘텐츠 ID * @return 조회된 콘텐츠 */ @Override public Optional findById(ContentId id) { - log.debug("Finding content by id: {}", id.getValue()); - return springDataContentRepository.findById(id.getValue()) - .map(contentMapper::toDomain); + return jpaRepository.findById(id.getValue()); } /** - * 필터 조건으로 콘텐츠 목록을 조회합니다. - * + * 필터 조건으로 콘텐츠 목록 조회 * @param contentType 콘텐츠 타입 * @param platform 플랫폼 * @param period 기간 @@ -67,45 +52,25 @@ public class JpaContentRepository implements ContentRepository { */ @Override public List findByFilters(ContentType contentType, Platform platform, String period, String sortBy) { - log.debug("Finding contents by filters - type: {}, platform: {}, period: {}, sortBy: {}", - contentType, platform, period, sortBy); - - List entities = springDataContentRepository.findByFilters( - contentType != null ? contentType.name() : null, - platform != null ? platform.name() : null, - period, - sortBy - ); - - return entities.stream() - .map(contentMapper::toDomain) - .collect(Collectors.toList()); + return jpaRepository.findByFilters(contentType, platform, period, sortBy); } /** - * 진행 중인 콘텐츠 목록을 조회합니다. - * + * 진행 중인 콘텐츠 목록 조회 * @param period 기간 * @return 진행 중인 콘텐츠 목록 */ @Override public List findOngoingContents(String period) { - log.debug("Finding ongoing contents for period: {}", period); - List entities = springDataContentRepository.findOngoingContents(period); - - return entities.stream() - .map(contentMapper::toDomain) - .collect(Collectors.toList()); + return jpaRepository.findOngoingContents(period); } /** - * ID로 콘텐츠를 삭제합니다. - * - * @param id 콘텐츠 ID + * ID로 콘텐츠 삭제 + * @param id 삭제할 콘텐츠 ID */ @Override public void deleteById(ContentId id) { - log.debug("Deleting content by id: {}", id.getValue()); - springDataContentRepository.deleteById(id.getValue()); + jpaRepository.deleteById(id.getValue()); } } \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepositoryInterface.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepositoryInterface.java new file mode 100644 index 0000000..380bba6 --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepositoryInterface.java @@ -0,0 +1,49 @@ +// marketing-content/src/main/java/com/won/smarketing/content/infrastructure/repository/JpaContentRepositoryInterface.java +package com.won.smarketing.content.infrastructure.repository; + +import com.won.smarketing.content.domain.model.Content; +import com.won.smarketing.content.domain.model.ContentType; +import com.won.smarketing.content.domain.model.Platform; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +/** + * Spring Data JPA 콘텐츠 리포지토리 인터페이스 + * Clean Architecture의 Infrastructure Layer에 위치 + */ +public interface JpaContentRepositoryInterface extends JpaRepository { + + /** + * 필터 조건으로 콘텐츠 목록 조회 + */ + @Query("SELECT c FROM Content c WHERE " + + "(:contentType IS NULL OR c.contentType = :contentType) AND " + + "(:platform IS NULL OR c.platform = :platform) AND " + + "(:period IS NULL OR " + + " (:period = 'week' AND c.createdAt >= CURRENT_DATE - 7) OR " + + " (:period = 'month' AND c.createdAt >= CURRENT_DATE - 30) OR " + + " (:period = 'year' AND c.createdAt >= CURRENT_DATE - 365)) " + + "ORDER BY " + + "CASE WHEN :sortBy = 'latest' THEN c.createdAt END DESC, " + + "CASE WHEN :sortBy = 'oldest' THEN c.createdAt END ASC, " + + "CASE WHEN :sortBy = 'title' THEN c.title END ASC") + List findByFilters(@Param("contentType") ContentType contentType, + @Param("platform") Platform platform, + @Param("period") String period, + @Param("sortBy") String sortBy); + + /** + * 진행 중인 콘텐츠 목록 조회 + */ + @Query("SELECT c FROM Content c WHERE " + + "c.status IN ('PUBLISHED', 'SCHEDULED') AND " + + "(:period IS NULL OR " + + " (:period = 'week' AND c.createdAt >= CURRENT_DATE - 7) OR " + + " (:period = 'month' AND c.createdAt >= CURRENT_DATE - 30) OR " + + " (:period = 'year' AND c.createdAt >= CURRENT_DATE - 365)) " + + "ORDER BY c.createdAt DESC") + List findOngoingContents(@Param("period") String period); +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/CreationConditionsDto.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/CreationConditionsDto.java new file mode 100644 index 0000000..403cdfa --- /dev/null +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/CreationConditionsDto.java @@ -0,0 +1,45 @@ +// marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/CreationConditionsDto.java +package com.won.smarketing.content.presentation.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +/** + * 콘텐츠 생성 조건 DTO + */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Schema(description = "콘텐츠 생성 조건") +public class CreationConditionsDto { + + @Schema(description = "카테고리", example = "음식") + private String category; + + @Schema(description = "생성 요구사항", example = "젊은 고객층을 타겟으로 한 재미있는 콘텐츠") + private String requirement; + + @Schema(description = "톤앤매너", example = "친근하고 활발한") + private String toneAndManner; + + @Schema(description = "감정 강도", example = "보통") + private String emotionIntensity; + + @Schema(description = "이벤트명", example = "신메뉴 출시 이벤트") + private String eventName; + + @Schema(description = "시작일") + private LocalDate startDate; + + @Schema(description = "종료일") + private LocalDate endDate; + + @Schema(description = "사진 스타일", example = "모던하고 깔끔한") + private String photoStyle; +} \ No newline at end of file diff --git a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/SnsContentCreateResponse.java b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/SnsContentCreateResponse.java index ce5ee97..0acf9ec 100644 --- a/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/SnsContentCreateResponse.java +++ b/smarketing-java/marketing-content/src/main/java/com/won/smarketing/content/presentation/dto/SnsContentCreateResponse.java @@ -306,7 +306,7 @@ public class SnsContentCreateResponse { // 생성 조건 정보 설정 if (content.getCreationConditions() != null) { builder.generationConditions(GenerationConditionsDto.builder() - .targetAudience(content.getCreationConditions().getTargetAudience()) + //.targetAudience(content.getCreationConditions().getTargetAudience()) .eventName(content.getCreationConditions().getEventName()) .toneAndManner(content.getCreationConditions().getToneAndManner()) .promotionType(content.getCreationConditions().getPromotionType()) diff --git a/smarketing-java/marketing-content/src/main/resources/application.yml b/smarketing-java/marketing-content/src/main/resources/application.yml index 9f7259f..0e9e68c 100644 --- a/smarketing-java/marketing-content/src/main/resources/application.yml +++ b/smarketing-java/marketing-content/src/main/resources/application.yml @@ -11,8 +11,8 @@ spring: driver-class-name: org.postgresql.Driver jpa: hibernate: - ddl-auto: ${JPA_DDL_AUTO:update} - show-sql: ${JPA_SHOW_SQL:true} + ddl-auto: ${DDL_AUTO:update} + show-sql: ${SHOW_SQL:true} properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect