diff --git a/.gradle/8.10/executionHistory/executionHistory.bin b/.gradle/8.10/executionHistory/executionHistory.bin index 2177cdd..650db3c 100644 Binary files a/.gradle/8.10/executionHistory/executionHistory.bin and b/.gradle/8.10/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.10/executionHistory/executionHistory.lock b/.gradle/8.10/executionHistory/executionHistory.lock index 0ce4c96..0563a0b 100644 Binary files a/.gradle/8.10/executionHistory/executionHistory.lock and b/.gradle/8.10/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.10/fileHashes/fileHashes.bin b/.gradle/8.10/fileHashes/fileHashes.bin index 8088fbb..9d44eb8 100644 Binary files a/.gradle/8.10/fileHashes/fileHashes.bin and b/.gradle/8.10/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock index 340e0dd..27a2f9c 100644 Binary files a/.gradle/8.10/fileHashes/fileHashes.lock and b/.gradle/8.10/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.10/fileHashes/resourceHashesCache.bin b/.gradle/8.10/fileHashes/resourceHashesCache.bin index 3d21896..3d54f6a 100644 Binary files a/.gradle/8.10/fileHashes/resourceHashesCache.bin and b/.gradle/8.10/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 0350ff2..62ce6b8 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 4ed6f06..16d0a6e 100644 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index ac4beb4..c69a406 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/content-service/src/main/java/com/kt/event/content/biz/domain/Content.java b/content-service/src/main/java/com/kt/event/content/biz/domain/Content.java new file mode 100644 index 0000000..278c110 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/domain/Content.java @@ -0,0 +1,99 @@ +package com.kt.event.content.biz.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 콘텐츠 도메인 모델 + * 이벤트에 대한 전체 콘텐츠 정보 (이미지 목록 포함) + */ +@Getter +@Builder +@AllArgsConstructor +public class Content { + + /** + * 콘텐츠 ID + */ + private final Long id; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + private final Long eventDraftId; + + /** + * 이벤트 제목 + */ + private final String eventTitle; + + /** + * 이벤트 설명 + */ + private final String eventDescription; + + /** + * 생성된 이미지 목록 + */ + @Builder.Default + private final List images = new ArrayList<>(); + + /** + * 생성일시 + */ + private final LocalDateTime createdAt; + + /** + * 수정일시 + */ + private final LocalDateTime updatedAt; + + /** + * 이미지 추가 + * + * @param image 생성된 이미지 + */ + public void addImage(GeneratedImage image) { + this.images.add(image); + } + + /** + * 선택된 이미지 조회 + * + * @return 선택된 이미지 목록 + */ + public List getSelectedImages() { + return images.stream() + .filter(GeneratedImage::isSelected) + .toList(); + } + + /** + * 특정 스타일의 이미지 조회 + * + * @param style 이미지 스타일 + * @return 해당 스타일의 이미지 목록 + */ + public List getImagesByStyle(ImageStyle style) { + return images.stream() + .filter(image -> image.getStyle() == style) + .toList(); + } + + /** + * 특정 플랫폼의 이미지 조회 + * + * @param platform 플랫폼 + * @return 해당 플랫폼의 이미지 목록 + */ + public List getImagesByPlatform(Platform platform) { + return images.stream() + .filter(image -> image.getPlatform() == platform) + .toList(); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/domain/GeneratedImage.java b/content-service/src/main/java/com/kt/event/content/biz/domain/GeneratedImage.java new file mode 100644 index 0000000..2d08b1e --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/domain/GeneratedImage.java @@ -0,0 +1,76 @@ +package com.kt.event.content.biz.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + * 생성된 이미지 도메인 모델 + * AI가 생성한 이미지의 비즈니스 정보 + */ +@Getter +@Builder +@AllArgsConstructor +public class GeneratedImage { + + /** + * 이미지 ID + */ + private final Long id; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + private final Long eventDraftId; + + /** + * 이미지 스타일 + */ + private final ImageStyle style; + + /** + * 플랫폼 + */ + private final Platform platform; + + /** + * CDN URL (Azure Blob Storage) + */ + private final String cdnUrl; + + /** + * 프롬프트 + */ + private final String prompt; + + /** + * 선택 여부 + */ + private boolean selected; + + /** + * 생성일시 + */ + private LocalDateTime createdAt; + + /** + * 수정일시 + */ + private LocalDateTime updatedAt; + + /** + * 이미지 선택 + */ + public void select() { + this.selected = true; + } + + /** + * 이미지 선택 해제 + */ + public void deselect() { + this.selected = false; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/domain/ImageStyle.java b/content-service/src/main/java/com/kt/event/content/biz/domain/ImageStyle.java new file mode 100644 index 0000000..dbcb715 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/domain/ImageStyle.java @@ -0,0 +1,32 @@ +package com.kt.event.content.biz.domain; + +/** + * 이미지 스타일 enum + * AI가 생성하는 이미지의 스타일 유형 + */ +public enum ImageStyle { + /** + * 심플 스타일 - 깔끔하고 미니멀한 디자인 + */ + SIMPLE("심플"), + + /** + * 화려한 스타일 - 화려하고 풍부한 디자인 + */ + FANCY("화려한"), + + /** + * 트렌디 스타일 - 최신 트렌드를 반영한 디자인 + */ + TRENDY("트렌디"); + + private final String displayName; + + ImageStyle(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/domain/Job.java b/content-service/src/main/java/com/kt/event/content/biz/domain/Job.java new file mode 100644 index 0000000..cc67600 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/domain/Job.java @@ -0,0 +1,140 @@ +package com.kt.event.content.biz.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + * Job 도메인 모델 + * 이미지 생성 작업의 비즈니스 정보 + */ +@Getter +@Builder +@AllArgsConstructor +public class Job { + + /** + * Job 상태 enum + */ + public enum Status { + PENDING, // 대기 중 + PROCESSING, // 처리 중 + COMPLETED, // 완료 + FAILED // 실패 + } + + /** + * Job ID + */ + private final String id; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + private final Long eventDraftId; + + /** + * Job 타입 (image-generation) + */ + private final String jobType; + + /** + * Job 상태 + */ + private Status status; + + /** + * 진행률 (0-100) + */ + private int progress; + + /** + * 결과 메시지 + */ + private String resultMessage; + + /** + * 에러 메시지 + */ + private String errorMessage; + + /** + * 생성일시 + */ + private final LocalDateTime createdAt; + + /** + * 수정일시 + */ + private final LocalDateTime updatedAt; + + /** + * Job 시작 + */ + public void start() { + this.status = Status.PROCESSING; + this.progress = 0; + } + + /** + * 진행률 업데이트 + * + * @param progress 진행률 (0-100) + */ + public void updateProgress(int progress) { + if (progress < 0 || progress > 100) { + throw new IllegalArgumentException("진행률은 0-100 사이여야 합니다"); + } + this.progress = progress; + } + + /** + * Job 완료 처리 + * + * @param resultMessage 결과 메시지 + */ + public void complete(String resultMessage) { + this.status = Status.COMPLETED; + this.progress = 100; + this.resultMessage = resultMessage; + } + + /** + * Job 실패 처리 + * + * @param errorMessage 에러 메시지 + */ + public void fail(String errorMessage) { + this.status = Status.FAILED; + this.errorMessage = errorMessage; + } + + /** + * Job 진행 중 여부 + * + * @return 진행 중이면 true + */ + public boolean isProcessing() { + return status == Status.PROCESSING; + } + + /** + * Job 완료 여부 + * + * @return 완료되었으면 true + */ + public boolean isCompleted() { + return status == Status.COMPLETED; + } + + /** + * Job 실패 여부 + * + * @return 실패했으면 true + */ + public boolean isFailed() { + return status == Status.FAILED; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/domain/Platform.java b/content-service/src/main/java/com/kt/event/content/biz/domain/Platform.java new file mode 100644 index 0000000..d308f16 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/domain/Platform.java @@ -0,0 +1,53 @@ +package com.kt.event.content.biz.domain; + +/** + * 플랫폼 enum + * 이미지가 배포될 SNS 플랫폼 유형 + */ +public enum Platform { + /** + * Instagram - 1080x1080 정사각형 + */ + INSTAGRAM("Instagram", 1080, 1080), + + /** + * 네이버 블로그 - 800x600 + */ + NAVER("네이버 블로그", 800, 600), + + /** + * 카카오 채널 - 800x800 정사각형 + */ + KAKAO("카카오 채널", 800, 800); + + private final String displayName; + private final int width; + private final int height; + + Platform(String displayName, int width, int height) { + this.displayName = displayName; + this.width = width; + this.height = height; + } + + public String getDisplayName() { + return displayName; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + /** + * 이미지 크기 문자열 반환 + * + * @return 가로x세로 형식 (예: 1080x1080) + */ + public String getSizeString() { + return width + "x" + height; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/dto/ContentCommand.java b/content-service/src/main/java/com/kt/event/content/biz/dto/ContentCommand.java new file mode 100644 index 0000000..a017182 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/dto/ContentCommand.java @@ -0,0 +1,40 @@ +package com.kt.event.content.biz.dto; + +import com.kt.event.content.biz.domain.ImageStyle; +import com.kt.event.content.biz.domain.Platform; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +/** + * 콘텐츠 관련 커맨드 DTO + */ +public class ContentCommand { + + /** + * 이미지 생성 요청 커맨드 + */ + @Getter + @Builder + @AllArgsConstructor + public static class GenerateImages { + private Long eventDraftId; + private String eventTitle; + private String eventDescription; + private List styles; + private List platforms; + } + + /** + * 이미지 재생성 요청 커맨드 + */ + @Getter + @Builder + @AllArgsConstructor + public static class RegenerateImage { + private Long imageId; + private String newPrompt; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/dto/ContentInfo.java b/content-service/src/main/java/com/kt/event/content/biz/dto/ContentInfo.java new file mode 100644 index 0000000..727b9ec --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/dto/ContentInfo.java @@ -0,0 +1,47 @@ +package com.kt.event.content.biz.dto; + +import com.kt.event.content.biz.domain.Content; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 콘텐츠 정보 DTO + */ +@Getter +@Builder +@AllArgsConstructor +public class ContentInfo { + + private Long id; + private Long eventDraftId; + private String eventTitle; + private String eventDescription; + private List images; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + /** + * 도메인 모델로부터 생성 + * + * @param content 콘텐츠 도메인 모델 + * @return ContentInfo + */ + public static ContentInfo from(Content content) { + return ContentInfo.builder() + .id(content.getId()) + .eventDraftId(content.getEventDraftId()) + .eventTitle(content.getEventTitle()) + .eventDescription(content.getEventDescription()) + .images(content.getImages().stream() + .map(ImageInfo::from) + .collect(Collectors.toList())) + .createdAt(content.getCreatedAt()) + .updatedAt(content.getUpdatedAt()) + .build(); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/dto/ImageInfo.java b/content-service/src/main/java/com/kt/event/content/biz/dto/ImageInfo.java new file mode 100644 index 0000000..5aed268 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/dto/ImageInfo.java @@ -0,0 +1,49 @@ +package com.kt.event.content.biz.dto; + +import com.kt.event.content.biz.domain.GeneratedImage; +import com.kt.event.content.biz.domain.ImageStyle; +import com.kt.event.content.biz.domain.Platform; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + * 이미지 정보 DTO + */ +@Getter +@Builder +@AllArgsConstructor +public class ImageInfo { + + private Long id; + private Long eventDraftId; + private ImageStyle style; + private Platform platform; + private String cdnUrl; + private String prompt; + private boolean selected; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + /** + * 도메인 모델로부터 생성 + * + * @param image 이미지 도메인 모델 + * @return ImageInfo + */ + public static ImageInfo from(GeneratedImage image) { + return ImageInfo.builder() + .id(image.getId()) + .eventDraftId(image.getEventDraftId()) + .style(image.getStyle()) + .platform(image.getPlatform()) + .cdnUrl(image.getCdnUrl()) + .prompt(image.getPrompt()) + .selected(image.isSelected()) + .createdAt(image.getCreatedAt()) + .updatedAt(image.getUpdatedAt()) + .build(); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/dto/JobInfo.java b/content-service/src/main/java/com/kt/event/content/biz/dto/JobInfo.java new file mode 100644 index 0000000..48e4909 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/dto/JobInfo.java @@ -0,0 +1,47 @@ +package com.kt.event.content.biz.dto; + +import com.kt.event.content.biz.domain.Job; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + * Job 정보 DTO + */ +@Getter +@Builder +@AllArgsConstructor +public class JobInfo { + + private String id; + private Long eventDraftId; + private String jobType; + private Job.Status status; + private int progress; + private String resultMessage; + private String errorMessage; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + /** + * 도메인 모델로부터 생성 + * + * @param job Job 도메인 모델 + * @return JobInfo + */ + public static JobInfo from(Job job) { + return JobInfo.builder() + .id(job.getId()) + .eventDraftId(job.getEventDraftId()) + .jobType(job.getJobType()) + .status(job.getStatus()) + .progress(job.getProgress()) + .resultMessage(job.getResultMessage()) + .errorMessage(job.getErrorMessage()) + .createdAt(job.getCreatedAt()) + .updatedAt(job.getUpdatedAt()) + .build(); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/service/GetEventContentService.java b/content-service/src/main/java/com/kt/event/content/biz/service/GetEventContentService.java new file mode 100644 index 0000000..8ac84bb --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/service/GetEventContentService.java @@ -0,0 +1,32 @@ +package com.kt.event.content.biz.service; + +import com.kt.event.common.exception.BusinessException; +import com.kt.event.common.exception.ErrorCode; +import com.kt.event.content.biz.domain.Content; +import com.kt.event.content.biz.dto.ContentInfo; +import com.kt.event.content.biz.usecase.in.GetEventContentUseCase; +import com.kt.event.content.biz.usecase.out.ContentReader; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 이벤트 콘텐츠 조회 서비스 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class GetEventContentService implements GetEventContentUseCase { + + private final ContentReader contentReader; + + @Override + public ContentInfo execute(Long eventDraftId) { + Content content = contentReader.findByEventDraftIdWithImages(eventDraftId) + .orElseThrow(() -> new BusinessException(ErrorCode.COMMON_001, "콘텐츠를 찾을 수 없습니다")); + + return ContentInfo.from(content); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/service/GetImageDetailService.java b/content-service/src/main/java/com/kt/event/content/biz/service/GetImageDetailService.java new file mode 100644 index 0000000..4465679 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/service/GetImageDetailService.java @@ -0,0 +1,32 @@ +package com.kt.event.content.biz.service; + +import com.kt.event.common.exception.BusinessException; +import com.kt.event.common.exception.ErrorCode; +import com.kt.event.content.biz.domain.GeneratedImage; +import com.kt.event.content.biz.dto.ImageInfo; +import com.kt.event.content.biz.usecase.in.GetImageDetailUseCase; +import com.kt.event.content.biz.usecase.out.ContentReader; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 이미지 상세 조회 서비스 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class GetImageDetailService implements GetImageDetailUseCase { + + private final ContentReader contentReader; + + @Override + public ImageInfo execute(Long imageId) { + GeneratedImage image = contentReader.findImageById(imageId) + .orElseThrow(() -> new BusinessException(ErrorCode.COMMON_001, "이미지를 찾을 수 없습니다")); + + return ImageInfo.from(image); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/service/GetImageListService.java b/content-service/src/main/java/com/kt/event/content/biz/service/GetImageListService.java new file mode 100644 index 0000000..7d65e44 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/service/GetImageListService.java @@ -0,0 +1,33 @@ +package com.kt.event.content.biz.service; + +import com.kt.event.content.biz.domain.GeneratedImage; +import com.kt.event.content.biz.dto.ImageInfo; +import com.kt.event.content.biz.usecase.in.GetImageListUseCase; +import com.kt.event.content.biz.usecase.out.ContentReader; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 이미지 목록 조회 서비스 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class GetImageListService implements GetImageListUseCase { + + private final ContentReader contentReader; + + @Override + public List execute(Long eventDraftId) { + List images = contentReader.findImagesByEventDraftId(eventDraftId); + return images.stream() + .map(ImageInfo::from) + .collect(Collectors.toList()); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/service/JobManagementService.java b/content-service/src/main/java/com/kt/event/content/biz/service/JobManagementService.java new file mode 100644 index 0000000..9c27dc8 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/service/JobManagementService.java @@ -0,0 +1,33 @@ +package com.kt.event.content.biz.service; + +import com.kt.event.common.exception.BusinessException; +import com.kt.event.common.exception.ErrorCode; +import com.kt.event.content.biz.domain.Job; +import com.kt.event.content.biz.dto.JobInfo; +import com.kt.event.content.biz.usecase.in.GetJobStatusUseCase; +import com.kt.event.content.biz.usecase.out.JobReader; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Job 관리 서비스 + * Job 상태 조회 기능 제공 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class JobManagementService implements GetJobStatusUseCase { + + private final JobReader jobReader; + + @Override + public JobInfo execute(String jobId) { + Job job = jobReader.findById(jobId) + .orElseThrow(() -> new BusinessException(ErrorCode.COMMON_001, "Job을 찾을 수 없습니다")); + + return JobInfo.from(job); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GenerateImagesUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GenerateImagesUseCase.java new file mode 100644 index 0000000..70d89d2 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GenerateImagesUseCase.java @@ -0,0 +1,19 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.ContentCommand; +import com.kt.event.content.biz.dto.JobInfo; + +/** + * 이미지 생성 UseCase + * 비동기로 이미지 생성 작업을 시작 + */ +public interface GenerateImagesUseCase { + + /** + * 이미지 생성 요청 + * + * @param command 이미지 생성 커맨드 + * @return Job 정보 + */ + JobInfo execute(ContentCommand.GenerateImages command); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetEventContentUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetEventContentUseCase.java new file mode 100644 index 0000000..9b29d21 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetEventContentUseCase.java @@ -0,0 +1,17 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.ContentInfo; + +/** + * 이벤트 콘텐츠 조회 UseCase + */ +public interface GetEventContentUseCase { + + /** + * 이벤트 전체 콘텐츠 조회 (이미지 목록 포함) + * + * @param eventDraftId 이벤트 초안 ID + * @return 콘텐츠 정보 + */ + ContentInfo execute(Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageDetailUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageDetailUseCase.java new file mode 100644 index 0000000..d30af23 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageDetailUseCase.java @@ -0,0 +1,17 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.ImageInfo; + +/** + * 이미지 상세 조회 UseCase + */ +public interface GetImageDetailUseCase { + + /** + * 이미지 상세 정보 조회 + * + * @param imageId 이미지 ID + * @return 이미지 정보 + */ + ImageInfo execute(Long imageId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageListUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageListUseCase.java new file mode 100644 index 0000000..80f7cfd --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageListUseCase.java @@ -0,0 +1,19 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.ImageInfo; + +import java.util.List; + +/** + * 이미지 목록 조회 UseCase + */ +public interface GetImageListUseCase { + + /** + * 이벤트의 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return 이미지 정보 목록 + */ + List execute(Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetJobStatusUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetJobStatusUseCase.java new file mode 100644 index 0000000..97831b2 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetJobStatusUseCase.java @@ -0,0 +1,17 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.JobInfo; + +/** + * Job 상태 조회 UseCase + */ +public interface GetJobStatusUseCase { + + /** + * Job 상태 조회 + * + * @param jobId Job ID + * @return Job 정보 + */ + JobInfo execute(String jobId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/in/RegenerateImageUseCase.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/RegenerateImageUseCase.java new file mode 100644 index 0000000..712e73e --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/in/RegenerateImageUseCase.java @@ -0,0 +1,18 @@ +package com.kt.event.content.biz.usecase.in; + +import com.kt.event.content.biz.dto.ContentCommand; +import com.kt.event.content.biz.dto.JobInfo; + +/** + * 이미지 재생성 UseCase + */ +public interface RegenerateImageUseCase { + + /** + * 이미지 재생성 요청 + * + * @param command 이미지 재생성 커맨드 + * @return Job 정보 + */ + JobInfo execute(ContentCommand.RegenerateImage command); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/CDNUploader.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/CDNUploader.java new file mode 100644 index 0000000..79b56ca --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/CDNUploader.java @@ -0,0 +1,17 @@ +package com.kt.event.content.biz.usecase.out; + +/** + * CDN 업로드 포트 + * Azure Blob Storage에 이미지 업로드 + */ +public interface CDNUploader { + + /** + * 이미지 업로드 + * + * @param imageData 이미지 바이트 데이터 + * @param fileName 파일명 + * @return CDN URL + */ + String upload(byte[] imageData, String fileName); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentReader.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentReader.java new file mode 100644 index 0000000..1847e1d --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentReader.java @@ -0,0 +1,37 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.Content; +import com.kt.event.content.biz.domain.GeneratedImage; + +import java.util.List; +import java.util.Optional; + +/** + * 콘텐츠 조회 포트 + */ +public interface ContentReader { + + /** + * 이벤트 초안 ID로 콘텐츠 조회 (이미지 목록 포함) + * + * @param eventDraftId 이벤트 초안 ID + * @return 콘텐츠 도메인 모델 + */ + Optional findByEventDraftIdWithImages(Long eventDraftId); + + /** + * 이미지 ID로 이미지 조회 + * + * @param imageId 이미지 ID + * @return 이미지 도메인 모델 + */ + Optional findImageById(Long imageId); + + /** + * 이벤트 초안 ID로 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return 이미지 도메인 모델 목록 + */ + List findImagesByEventDraftId(Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentWriter.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentWriter.java new file mode 100644 index 0000000..3994efa --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentWriter.java @@ -0,0 +1,26 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.Content; +import com.kt.event.content.biz.domain.GeneratedImage; + +/** + * 콘텐츠 저장 포트 + */ +public interface ContentWriter { + + /** + * 콘텐츠 저장 + * + * @param content 콘텐츠 도메인 모델 + * @return 저장된 콘텐츠 + */ + Content save(Content content); + + /** + * 이미지 저장 + * + * @param image 이미지 도메인 모델 + * @return 저장된 이미지 + */ + GeneratedImage saveImage(GeneratedImage image); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ImageGeneratorCaller.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ImageGeneratorCaller.java new file mode 100644 index 0000000..a14210d --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/ImageGeneratorCaller.java @@ -0,0 +1,21 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.ImageStyle; +import com.kt.event.content.biz.domain.Platform; + +/** + * 이미지 생성 API 호출 포트 + * Stable Diffusion, DALL-E 등 외부 이미지 생성 API 호출 + */ +public interface ImageGeneratorCaller { + + /** + * 이미지 생성 + * + * @param prompt 프롬프트 + * @param style 이미지 스타일 + * @param platform 플랫폼 (이미지 크기 결정) + * @return 생성된 이미지 바이트 데이터 + */ + byte[] generateImage(String prompt, ImageStyle style, Platform platform); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobReader.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobReader.java new file mode 100644 index 0000000..976ff90 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobReader.java @@ -0,0 +1,19 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.Job; + +import java.util.Optional; + +/** + * Job 조회 포트 + */ +public interface JobReader { + + /** + * Job ID로 조회 + * + * @param jobId Job ID + * @return Job 도메인 모델 + */ + Optional findById(String jobId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobWriter.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobWriter.java new file mode 100644 index 0000000..b39404a --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobWriter.java @@ -0,0 +1,17 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.Job; + +/** + * Job 저장 포트 + */ +public interface JobWriter { + + /** + * Job 저장 + * + * @param job Job 도메인 모델 + * @return 저장된 Job + */ + Job save(Job job); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisAIDataReader.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisAIDataReader.java new file mode 100644 index 0000000..ee66f12 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisAIDataReader.java @@ -0,0 +1,19 @@ +package com.kt.event.content.biz.usecase.out; + +import java.util.Map; +import java.util.Optional; + +/** + * Redis AI 데이터 조회 포트 + * Event Service가 저장한 AI 추천 데이터를 읽음 + */ +public interface RedisAIDataReader { + + /** + * AI 추천 데이터 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return AI 추천 데이터 (JSON 형태의 Map) + */ + Optional> getAIRecommendation(Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisImageWriter.java b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisImageWriter.java new file mode 100644 index 0000000..2ccd7ba --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisImageWriter.java @@ -0,0 +1,21 @@ +package com.kt.event.content.biz.usecase.out; + +import com.kt.event.content.biz.domain.GeneratedImage; + +import java.util.List; + +/** + * Redis 이미지 데이터 저장 포트 + * 생성된 이미지 정보를 Redis에 캐싱 + */ +public interface RedisImageWriter { + + /** + * 이미지 목록 캐싱 + * + * @param eventDraftId 이벤트 초안 ID + * @param images 이미지 목록 + * @param ttlSeconds TTL (초) + */ + void cacheImages(Long eventDraftId, List images, long ttlSeconds); +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/ContentApplication.java b/content-service/src/main/java/com/kt/event/content/infra/ContentApplication.java new file mode 100644 index 0000000..616f4aa --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/ContentApplication.java @@ -0,0 +1,27 @@ +package com.kt.event.content.infra; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +/** + * Content Service Application + */ +@SpringBootApplication(scanBasePackages = { + "com.kt.event.content", + "com.kt.event.common" +}) +@EntityScan(basePackages = { + "com.kt.event.content.infra.gateway.entity", + "com.kt.event.common.entity" +}) +@EnableJpaRepositories(basePackages = "com.kt.event.content.infra.gateway.repository") +@EnableJpaAuditing +public class ContentApplication { + + public static void main(String[] args) { + SpringApplication.run(ContentApplication.class, args); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/ContentEntity.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/ContentEntity.java new file mode 100644 index 0000000..f877c86 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/ContentEntity.java @@ -0,0 +1,98 @@ +package com.kt.event.content.infra.gateway.entity; + +import com.kt.event.common.entity.BaseTimeEntity; +import com.kt.event.content.biz.domain.Content; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 콘텐츠 엔티티 + * 이벤트에 대한 전체 콘텐츠 정보를 저장 + */ +@Entity +@Table(name = "contents") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ContentEntity extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + @Column(name = "event_draft_id", nullable = false) + private Long eventDraftId; + + /** + * 이벤트 제목 + */ + @Column(name = "event_title", nullable = false, length = 200) + private String eventTitle; + + /** + * 이벤트 설명 + */ + @Column(name = "event_description", columnDefinition = "TEXT") + private String eventDescription; + + /** + * 생성된 이미지 목록 + */ + @OneToMany(mappedBy = "content", cascade = CascadeType.ALL, orphanRemoval = true) + private List images = new ArrayList<>(); + + /** + * 정적 팩토리 메서드: 새 콘텐츠 생성 + * + * @param eventDraftId 이벤트 초안 ID + * @param eventTitle 이벤트 제목 + * @param eventDescription 이벤트 설명 + * @return ContentEntity + */ + public static ContentEntity create(Long eventDraftId, String eventTitle, String eventDescription) { + ContentEntity entity = new ContentEntity(); + entity.eventDraftId = eventDraftId; + entity.eventTitle = eventTitle; + entity.eventDescription = eventDescription; + return entity; + } + + /** + * 도메인 모델로 변환 + * + * @return Content 도메인 모델 + */ + public Content toDomain() { + return Content.builder() + .id(id) + .eventDraftId(eventDraftId) + .eventTitle(eventTitle) + .eventDescription(eventDescription) + .images(images.stream() + .map(GeneratedImageEntity::toDomain) + .collect(Collectors.toList())) + .createdAt(getCreatedAt()) + .updatedAt(getUpdatedAt()) + .build(); + } + + /** + * 이미지 추가 + * + * @param image 생성된 이미지 엔티티 + */ + public void addImage(GeneratedImageEntity image) { + images.add(image); + image.assignContent(this); + } +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/GeneratedImageEntity.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/GeneratedImageEntity.java new file mode 100644 index 0000000..b90e75b --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/GeneratedImageEntity.java @@ -0,0 +1,139 @@ +package com.kt.event.content.infra.gateway.entity; + +import com.kt.event.common.entity.BaseTimeEntity; +import com.kt.event.content.biz.domain.GeneratedImage; +import com.kt.event.content.biz.domain.ImageStyle; +import com.kt.event.content.biz.domain.Platform; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 생성된 이미지 엔티티 + * AI가 생성한 이미지 정보를 저장 + */ +@Entity +@Table(name = "generated_images", indexes = { + @Index(name = "idx_event_draft_id", columnList = "event_draft_id"), + @Index(name = "idx_style_platform", columnList = "style,platform") +}) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class GeneratedImageEntity extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 콘텐츠 (양방향 관계) + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "content_id") + private ContentEntity content; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + @Column(name = "event_draft_id", nullable = false) + private Long eventDraftId; + + /** + * 이미지 스타일 + */ + @Enumerated(EnumType.STRING) + @Column(name = "style", nullable = false, length = 20) + private ImageStyle style; + + /** + * 플랫폼 + */ + @Enumerated(EnumType.STRING) + @Column(name = "platform", nullable = false, length = 20) + private Platform platform; + + /** + * CDN URL (Azure Blob Storage) + */ + @Column(name = "cdn_url", nullable = false, length = 500) + private String cdnUrl; + + /** + * 프롬프트 + */ + @Column(name = "prompt", columnDefinition = "TEXT") + private String prompt; + + /** + * 선택 여부 + */ + @Column(name = "selected", nullable = false) + private boolean selected; + + /** + * 정적 팩토리 메서드: 새 이미지 생성 + * + * @param eventDraftId 이벤트 초안 ID + * @param style 이미지 스타일 + * @param platform 플랫폼 + * @param cdnUrl CDN URL + * @param prompt 프롬프트 + * @return GeneratedImageEntity + */ + public static GeneratedImageEntity create(Long eventDraftId, ImageStyle style, Platform platform, + String cdnUrl, String prompt) { + GeneratedImageEntity entity = new GeneratedImageEntity(); + entity.eventDraftId = eventDraftId; + entity.style = style; + entity.platform = platform; + entity.cdnUrl = cdnUrl; + entity.prompt = prompt; + entity.selected = false; + return entity; + } + + /** + * 도메인 모델로 변환 + * + * @return GeneratedImage 도메인 모델 + */ + public GeneratedImage toDomain() { + return GeneratedImage.builder() + .id(id) + .eventDraftId(eventDraftId) + .style(style) + .platform(platform) + .cdnUrl(cdnUrl) + .prompt(prompt) + .selected(selected) + .createdAt(getCreatedAt()) + .updatedAt(getUpdatedAt()) + .build(); + } + + /** + * 콘텐츠 할당 (양방향 관계 설정용) + * + * @param content 콘텐츠 엔티티 + */ + protected void assignContent(ContentEntity content) { + this.content = content; + } + + /** + * 이미지 선택 + */ + public void select() { + this.selected = true; + } + + /** + * 이미지 선택 해제 + */ + public void deselect() { + this.selected = false; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/JobEntity.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/JobEntity.java new file mode 100644 index 0000000..496f880 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/entity/JobEntity.java @@ -0,0 +1,143 @@ +package com.kt.event.content.infra.gateway.entity; + +import com.kt.event.common.entity.BaseTimeEntity; +import com.kt.event.content.biz.domain.Job; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Job 엔티티 + * 이미지 생성 작업 정보를 저장 + */ +@Entity +@Table(name = "jobs", indexes = { + @Index(name = "idx_event_draft_id", columnList = "event_draft_id"), + @Index(name = "idx_status", columnList = "status") +}) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class JobEntity extends BaseTimeEntity { + + @Id + @Column(name = "id", length = 36) + private String id; + + /** + * 이벤트 ID (이벤트 초안 ID) + */ + @Column(name = "event_draft_id", nullable = false) + private Long eventDraftId; + + /** + * Job 타입 + */ + @Column(name = "job_type", nullable = false, length = 50) + private String jobType; + + /** + * Job 상태 + */ + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false, length = 20) + private Job.Status status; + + /** + * 진행률 (0-100) + */ + @Column(name = "progress", nullable = false) + private int progress; + + /** + * 결과 메시지 + */ + @Column(name = "result_message", columnDefinition = "TEXT") + private String resultMessage; + + /** + * 에러 메시지 + */ + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + /** + * 정적 팩토리 메서드: 새 Job 생성 + * + * @param id Job ID (UUID) + * @param eventDraftId 이벤트 초안 ID + * @param jobType Job 타입 + * @return JobEntity + */ + public static JobEntity create(String id, Long eventDraftId, String jobType) { + JobEntity entity = new JobEntity(); + entity.id = id; + entity.eventDraftId = eventDraftId; + entity.jobType = jobType; + entity.status = Job.Status.PENDING; + entity.progress = 0; + return entity; + } + + /** + * 도메인 모델로 변환 + * + * @return Job 도메인 모델 + */ + public Job toDomain() { + return Job.builder() + .id(id) + .eventDraftId(eventDraftId) + .jobType(jobType) + .status(status) + .progress(progress) + .resultMessage(resultMessage) + .errorMessage(errorMessage) + .createdAt(getCreatedAt()) + .updatedAt(getUpdatedAt()) + .build(); + } + + /** + * Job 시작 + */ + public void start() { + this.status = Job.Status.PROCESSING; + this.progress = 0; + } + + /** + * 진행률 업데이트 + * + * @param progress 진행률 (0-100) + */ + public void updateProgress(int progress) { + if (progress < 0 || progress > 100) { + throw new IllegalArgumentException("진행률은 0-100 사이여야 합니다"); + } + this.progress = progress; + } + + /** + * Job 완료 처리 + * + * @param resultMessage 결과 메시지 + */ + public void complete(String resultMessage) { + this.status = Job.Status.COMPLETED; + this.progress = 100; + this.resultMessage = resultMessage; + } + + /** + * Job 실패 처리 + * + * @param errorMessage 에러 메시지 + */ + public void fail(String errorMessage) { + this.status = Job.Status.FAILED; + this.errorMessage = errorMessage; + } +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/ContentJpaRepository.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/ContentJpaRepository.java new file mode 100644 index 0000000..927c63d --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/ContentJpaRepository.java @@ -0,0 +1,41 @@ +package com.kt.event.content.infra.gateway.repository; + +import com.kt.event.content.infra.gateway.entity.ContentEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +/** + * 콘텐츠 JPA 리포지토리 + */ +public interface ContentJpaRepository extends JpaRepository { + + /** + * 이벤트 초안 ID로 콘텐츠 조회 (이미지 목록 포함) + * + * @param eventDraftId 이벤트 초안 ID + * @return 콘텐츠 엔티티 + */ + @Query("SELECT DISTINCT c FROM ContentEntity c " + + "LEFT JOIN FETCH c.images " + + "WHERE c.eventDraftId = :eventDraftId") + Optional findByEventDraftIdWithImages(@Param("eventDraftId") Long eventDraftId); + + /** + * 이벤트 초안 ID로 콘텐츠 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return 콘텐츠 엔티티 + */ + Optional findByEventDraftId(Long eventDraftId); + + /** + * 이벤트 초안 ID로 콘텐츠 존재 여부 확인 + * + * @param eventDraftId 이벤트 초안 ID + * @return 존재 여부 + */ + boolean existsByEventDraftId(Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/GeneratedImageJpaRepository.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/GeneratedImageJpaRepository.java new file mode 100644 index 0000000..9156916 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/GeneratedImageJpaRepository.java @@ -0,0 +1,68 @@ +package com.kt.event.content.infra.gateway.repository; + +import com.kt.event.content.biz.domain.ImageStyle; +import com.kt.event.content.biz.domain.Platform; +import com.kt.event.content.infra.gateway.entity.GeneratedImageEntity; +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; + +/** + * 생성된 이미지 JPA 리포지토리 + */ +public interface GeneratedImageJpaRepository extends JpaRepository { + + /** + * 이벤트 초안 ID로 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return 이미지 엔티티 목록 + */ + List findByEventDraftId(Long eventDraftId); + + /** + * 이벤트 초안 ID와 스타일로 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @param style 이미지 스타일 + * @return 이미지 엔티티 목록 + */ + List findByEventDraftIdAndStyle(Long eventDraftId, ImageStyle style); + + /** + * 이벤트 초안 ID와 플랫폼으로 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @param platform 플랫폼 + * @return 이미지 엔티티 목록 + */ + List findByEventDraftIdAndPlatform(Long eventDraftId, Platform platform); + + /** + * 이벤트 초안 ID와 선택 여부로 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @param selected 선택 여부 + * @return 이미지 엔티티 목록 + */ + List findByEventDraftIdAndSelected(Long eventDraftId, boolean selected); + + /** + * 이벤트 초안 ID로 선택된 이미지 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return 선택된 이미지 엔티티 목록 + */ + @Query("SELECT i FROM GeneratedImageEntity i WHERE i.eventDraftId = :eventDraftId AND i.selected = true") + List findSelectedImages(@Param("eventDraftId") Long eventDraftId); + + /** + * 이벤트 초안 ID로 모든 이미지 선택 해제 + * + * @param eventDraftId 이벤트 초안 ID + */ + @Query("UPDATE GeneratedImageEntity i SET i.selected = false WHERE i.eventDraftId = :eventDraftId") + void deselectAllByEventDraftId(@Param("eventDraftId") Long eventDraftId); +} diff --git a/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/JobJpaRepository.java b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/JobJpaRepository.java new file mode 100644 index 0000000..2001f36 --- /dev/null +++ b/content-service/src/main/java/com/kt/event/content/infra/gateway/repository/JobJpaRepository.java @@ -0,0 +1,40 @@ +package com.kt.event.content.infra.gateway.repository; + +import com.kt.event.content.biz.domain.Job; +import com.kt.event.content.infra.gateway.entity.JobEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * Job JPA 리포지토리 + */ +public interface JobJpaRepository extends JpaRepository { + + /** + * 이벤트 초안 ID로 Job 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @return Job 엔티티 목록 + */ + List findByEventDraftId(Long eventDraftId); + + /** + * 이벤트 초안 ID와 상태로 Job 목록 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @param status Job 상태 + * @return Job 엔티티 목록 + */ + List findByEventDraftIdAndStatus(Long eventDraftId, Job.Status status); + + /** + * 이벤트 초안 ID와 Job 타입으로 최신 Job 조회 + * + * @param eventDraftId 이벤트 초안 ID + * @param jobType Job 타입 + * @return Job 엔티티 + */ + Optional findFirstByEventDraftIdAndJobTypeOrderByCreatedAtDesc(Long eventDraftId, String jobType); +} diff --git a/content-service/src/main/resources/application.yml b/content-service/src/main/resources/application.yml new file mode 100644 index 0000000..4c5ada1 --- /dev/null +++ b/content-service/src/main/resources/application.yml @@ -0,0 +1,50 @@ +spring: + application: + name: content-service + + datasource: + url: jdbc:postgresql://4.217.131.139:5432/contentdb + username: eventuser + password: Hi5Jessica! + driver-class-name: org.postgresql.Driver + + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + use_sql_comments: true + + data: + redis: + host: 4.217.131.139 + port: 6379 + + kafka: + bootstrap-servers: 20.249.125.115:9092 + consumer: + group-id: content-service-consumers + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + +server: + port: 8084 + +jwt: + secret: kt-event-marketing-jwt-secret-key-for-authentication-and-authorization-2025 + access-token-validity: 3600000 + refresh-token-validity: 604800000 + +azure: + storage: + connection-string: ${AZURE_STORAGE_CONNECTION_STRING:} + container-name: event-images + +logging: + level: + com.kt.event: DEBUG + org.hibernate.SQL: DEBUG