mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 16:06:24 +00:00
Phase 3: content-service JPA 완전 제거 및 Redis 전용 전환
주요 변경사항: - JPA Entity 3개 삭제 (JobEntity, GeneratedImageEntity, ContentEntity) - JPA Repository 3개 삭제 (JobJpaRepository, GeneratedImageJpaRepository, ContentJpaRepository) - JPA Gateway 2개 삭제 (JobGateway, ContentGateway) - Port 인터페이스 정리: backward compatibility 메서드 제거 - Service 레이어 Redis DTO 전환 (JobManagementService, MockGenerateImagesService, MockRegenerateImageService) - MockRedisGateway에 ContentReader/ContentWriter 구현 추가 및 Immutable 패턴 처리 - application.yml에서 JPA/H2 설정 제거 - build.gradle에서 JPA 의존성 exclude 처리 - ContentApplication에서 JPA 어노테이션 제거 서비스는 이제 순수 Redis 기반 스토리지로 동작합니다. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5e9e1759ce
commit
ff83dca1a1
@ -1,3 +1,9 @@
|
||||
configurations {
|
||||
// Exclude JPA and PostgreSQL from inherited dependencies (Phase 3: Redis migration)
|
||||
implementation.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-data-jpa'
|
||||
implementation.exclude group: 'org.postgresql', module: 'postgresql'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Kafka Consumer
|
||||
implementation 'org.springframework.kafka:spring-kafka'
|
||||
@ -17,7 +23,4 @@ dependencies {
|
||||
|
||||
// Jackson for JSON
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
// H2 Database for local testing
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ 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.dto.RedisJobData;
|
||||
import com.kt.event.content.biz.usecase.in.GetJobStatusUseCase;
|
||||
import com.kt.event.content.biz.usecase.out.JobReader;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -25,9 +26,22 @@ public class JobManagementService implements GetJobStatusUseCase {
|
||||
|
||||
@Override
|
||||
public JobInfo execute(String jobId) {
|
||||
Job job = jobReader.findById(jobId)
|
||||
RedisJobData jobData = jobReader.getJob(jobId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.COMMON_001, "Job을 찾을 수 없습니다"));
|
||||
|
||||
// RedisJobData를 Job 도메인 객체로 변환
|
||||
Job job = Job.builder()
|
||||
.id(jobData.getId())
|
||||
.eventDraftId(jobData.getEventDraftId())
|
||||
.jobType(jobData.getJobType())
|
||||
.status(Job.Status.valueOf(jobData.getStatus()))
|
||||
.progress(jobData.getProgress())
|
||||
.resultMessage(jobData.getResultMessage())
|
||||
.errorMessage(jobData.getErrorMessage())
|
||||
.createdAt(jobData.getCreatedAt())
|
||||
.updatedAt(jobData.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
return JobInfo.from(job);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.domain.Platform;
|
||||
import com.kt.event.content.biz.dto.ContentCommand;
|
||||
import com.kt.event.content.biz.dto.JobInfo;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
import com.kt.event.content.biz.usecase.in.GenerateImagesUseCase;
|
||||
import com.kt.event.content.biz.usecase.out.ContentWriter;
|
||||
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||
@ -53,14 +54,24 @@ public class MockGenerateImagesService implements GenerateImagesUseCase {
|
||||
.updatedAt(java.time.LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// Job 저장
|
||||
Job savedJob = jobWriter.save(job);
|
||||
// Job 저장 (Job 도메인을 RedisJobData로 변환)
|
||||
RedisJobData jobData = RedisJobData.builder()
|
||||
.id(job.getId())
|
||||
.eventDraftId(job.getEventDraftId())
|
||||
.jobType(job.getJobType())
|
||||
.status(job.getStatus().name())
|
||||
.progress(job.getProgress())
|
||||
.createdAt(job.getCreatedAt())
|
||||
.updatedAt(job.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
jobWriter.saveJob(jobData, 3600); // TTL 1시간
|
||||
log.info("[MOCK] Job 생성 완료: jobId={}", jobId);
|
||||
|
||||
// 비동기로 이미지 생성 시뮬레이션
|
||||
processImageGeneration(jobId, command);
|
||||
|
||||
return JobInfo.from(savedJob);
|
||||
return JobInfo.from(job);
|
||||
}
|
||||
|
||||
@Async
|
||||
@ -128,36 +139,16 @@ public class MockGenerateImagesService implements GenerateImagesUseCase {
|
||||
}
|
||||
|
||||
// Job 상태 업데이트: COMPLETED
|
||||
Job completedJob = Job.builder()
|
||||
.id(jobId)
|
||||
.eventDraftId(command.getEventDraftId())
|
||||
.jobType("image-generation")
|
||||
.status(Job.Status.COMPLETED)
|
||||
.progress(100)
|
||||
.resultMessage(String.format("%d개의 이미지가 성공적으로 생성되었습니다.", images.size()))
|
||||
.createdAt(java.time.LocalDateTime.now())
|
||||
.updatedAt(java.time.LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
jobWriter.save(completedJob);
|
||||
String resultMessage = String.format("%d개의 이미지가 성공적으로 생성되었습니다.", images.size());
|
||||
jobWriter.updateJobStatus(jobId, "COMPLETED", 100);
|
||||
jobWriter.updateJobResult(jobId, resultMessage);
|
||||
log.info("[MOCK] Job 완료: jobId={}, 생성된 이미지 수={}", jobId, images.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] 이미지 생성 실패: jobId={}", jobId, e);
|
||||
|
||||
// Job 상태 업데이트: FAILED
|
||||
Job failedJob = Job.builder()
|
||||
.id(jobId)
|
||||
.eventDraftId(command.getEventDraftId())
|
||||
.jobType("image-generation")
|
||||
.status(Job.Status.FAILED)
|
||||
.progress(0)
|
||||
.errorMessage(e.getMessage())
|
||||
.createdAt(java.time.LocalDateTime.now())
|
||||
.updatedAt(java.time.LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
jobWriter.save(failedJob);
|
||||
jobWriter.updateJobError(jobId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package com.kt.event.content.biz.service.mock;
|
||||
import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.dto.ContentCommand;
|
||||
import com.kt.event.content.biz.dto.JobInfo;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
import com.kt.event.content.biz.usecase.in.RegenerateImageUseCase;
|
||||
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -41,11 +42,21 @@ public class MockRegenerateImageService implements RegenerateImageUseCase {
|
||||
.updatedAt(java.time.LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// Job 저장
|
||||
Job savedJob = jobWriter.save(job);
|
||||
// Job 저장 (Job 도메인을 RedisJobData로 변환)
|
||||
RedisJobData jobData = RedisJobData.builder()
|
||||
.id(job.getId())
|
||||
.eventDraftId(job.getEventDraftId())
|
||||
.jobType(job.getJobType())
|
||||
.status(job.getStatus().name())
|
||||
.progress(job.getProgress())
|
||||
.createdAt(job.getCreatedAt())
|
||||
.updatedAt(job.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
jobWriter.saveJob(jobData, 3600); // TTL 1시간
|
||||
|
||||
log.info("[MOCK] 재생성 Job 생성 완료: jobId={}", jobId);
|
||||
|
||||
return JobInfo.from(savedJob);
|
||||
return JobInfo.from(job);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.kt.event.content.biz.usecase.out;
|
||||
|
||||
import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
|
||||
import java.util.Optional;
|
||||
@ -17,13 +16,4 @@ public interface JobReader {
|
||||
* @return Job 데이터
|
||||
*/
|
||||
Optional<RedisJobData> getJob(String jobId);
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
* JPA 기반 JobGateway에서만 사용
|
||||
*
|
||||
* @param jobId Job ID
|
||||
* @return Job 도메인 객체
|
||||
*/
|
||||
Optional<Job> findById(String jobId);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.kt.event.content.biz.usecase.out;
|
||||
|
||||
import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
|
||||
/**
|
||||
@ -40,13 +39,4 @@ public interface JobWriter {
|
||||
* @param errorMessage 에러 메시지
|
||||
*/
|
||||
void updateJobError(String jobId, String errorMessage);
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
* JPA 기반 JobGateway에서만 사용
|
||||
*
|
||||
* @param job Job 도메인 객체
|
||||
* @return 저장된 Job
|
||||
*/
|
||||
Job save(Job job);
|
||||
}
|
||||
|
||||
@ -2,24 +2,16 @@ 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;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
/**
|
||||
* Content Service Application
|
||||
* Phase 3: JPA removed, using Redis for storage
|
||||
*/
|
||||
@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
|
||||
@EnableAsync
|
||||
public class ContentApplication {
|
||||
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
package com.kt.event.content.infra.gateway;
|
||||
|
||||
import com.kt.event.content.biz.domain.Content;
|
||||
import com.kt.event.content.biz.domain.GeneratedImage;
|
||||
import com.kt.event.content.biz.usecase.out.ContentReader;
|
||||
import com.kt.event.content.biz.usecase.out.ContentWriter;
|
||||
import com.kt.event.content.infra.gateway.entity.ContentEntity;
|
||||
import com.kt.event.content.infra.gateway.entity.GeneratedImageEntity;
|
||||
import com.kt.event.content.infra.gateway.repository.ContentJpaRepository;
|
||||
import com.kt.event.content.infra.gateway.repository.GeneratedImageJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Content 영속성 Gateway
|
||||
* ContentReader, ContentWriter outbound port 구현
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ContentGateway implements ContentReader, ContentWriter {
|
||||
|
||||
private final ContentJpaRepository contentRepository;
|
||||
private final GeneratedImageJpaRepository imageRepository;
|
||||
|
||||
// ========================================
|
||||
// ContentReader 구현
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<Content> findByEventDraftIdWithImages(Long eventDraftId) {
|
||||
log.debug("이벤트 콘텐츠 조회 (with images): eventDraftId={}", eventDraftId);
|
||||
return contentRepository.findByEventDraftIdWithImages(eventDraftId)
|
||||
.map(ContentEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<GeneratedImage> findImageById(Long imageId) {
|
||||
log.debug("이미지 조회: imageId={}", imageId);
|
||||
return imageRepository.findById(imageId)
|
||||
.map(GeneratedImageEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<GeneratedImage> findImagesByEventDraftId(Long eventDraftId) {
|
||||
log.debug("이미지 목록 조회: eventDraftId={}", eventDraftId);
|
||||
return imageRepository.findByEventDraftId(eventDraftId).stream()
|
||||
.map(GeneratedImageEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ContentWriter 구현
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Content save(Content content) {
|
||||
log.debug("콘텐츠 저장: eventDraftId={}", content.getEventDraftId());
|
||||
|
||||
// Content Entity 조회 또는 생성
|
||||
ContentEntity contentEntity = contentRepository.findByEventDraftId(content.getEventDraftId())
|
||||
.orElseGet(() -> ContentEntity.create(
|
||||
content.getEventDraftId(),
|
||||
content.getEventTitle(),
|
||||
content.getEventDescription()
|
||||
));
|
||||
|
||||
// Content 업데이트
|
||||
contentEntity.update(content.getEventTitle(), content.getEventDescription());
|
||||
|
||||
// 저장
|
||||
ContentEntity saved = contentRepository.save(contentEntity);
|
||||
|
||||
return saved.toDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public GeneratedImage saveImage(GeneratedImage image) {
|
||||
log.debug("이미지 저장: eventDraftId={}, style={}, platform={}",
|
||||
image.getEventDraftId(), image.getStyle(), image.getPlatform());
|
||||
|
||||
// Content Entity 조회
|
||||
ContentEntity contentEntity = contentRepository.findByEventDraftId(image.getEventDraftId())
|
||||
.orElseThrow(() -> new IllegalStateException("Content를 먼저 저장해야 합니다"));
|
||||
|
||||
// GeneratedImageEntity 생성
|
||||
GeneratedImageEntity imageEntity = GeneratedImageEntity.create(
|
||||
image.getEventDraftId(),
|
||||
image.getStyle(),
|
||||
image.getPlatform(),
|
||||
image.getCdnUrl(),
|
||||
image.getPrompt()
|
||||
);
|
||||
|
||||
// Content와 연결
|
||||
contentEntity.addImage(imageEntity);
|
||||
|
||||
// 선택 상태 설정
|
||||
if (image.isSelected()) {
|
||||
imageEntity.select();
|
||||
}
|
||||
|
||||
// 저장
|
||||
GeneratedImageEntity saved = imageRepository.save(imageEntity);
|
||||
|
||||
return saved.toDomain();
|
||||
}
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
package com.kt.event.content.infra.gateway;
|
||||
|
||||
import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
import com.kt.event.content.biz.usecase.out.JobReader;
|
||||
import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||
import com.kt.event.content.infra.gateway.entity.JobEntity;
|
||||
import com.kt.event.content.infra.gateway.repository.JobJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Job 영속성 Gateway
|
||||
* JobReader, JobWriter outbound port 구현
|
||||
* Phase 3에서 삭제 예정
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Primary
|
||||
@RequiredArgsConstructor
|
||||
public class JobGateway implements JobReader, JobWriter {
|
||||
|
||||
private final JobJpaRepository jobRepository;
|
||||
|
||||
// ========================================
|
||||
// JobReader 구현
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<RedisJobData> getJob(String jobId) {
|
||||
log.debug("[JPA] Job 조회: jobId={}", jobId);
|
||||
return jobRepository.findById(jobId)
|
||||
.map(entity -> RedisJobData.builder()
|
||||
.id(entity.getId())
|
||||
.eventDraftId(entity.getEventDraftId())
|
||||
.jobType(entity.getJobType())
|
||||
.status(entity.getStatus().name())
|
||||
.progress(entity.getProgress())
|
||||
.resultMessage(entity.getResultMessage())
|
||||
.errorMessage(entity.getErrorMessage())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.updatedAt(entity.getUpdatedAt())
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<Job> findById(String jobId) {
|
||||
log.debug("Job 조회: jobId={}", jobId);
|
||||
return jobRepository.findById(jobId)
|
||||
.map(JobEntity::toDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트별 Job 조회 (추가 메서드)
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<Job> findByEventDraftId(Long eventDraftId) {
|
||||
log.debug("이벤트별 Job 조회: eventDraftId={}", eventDraftId);
|
||||
return jobRepository.findByEventDraftId(eventDraftId).stream()
|
||||
.map(JobEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 최신 Job 조회 (추가 메서드)
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<Job> findLatestByEventDraftIdAndJobType(Long eventDraftId, String jobType) {
|
||||
log.debug("최신 Job 조회: eventDraftId={}, jobType={}", eventDraftId, jobType);
|
||||
return jobRepository.findFirstByEventDraftIdAndJobTypeOrderByCreatedAtDesc(eventDraftId, jobType)
|
||||
.map(JobEntity::toDomain);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// JobWriter 구현
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveJob(RedisJobData jobData, long ttlSeconds) {
|
||||
log.debug("[JPA] Job 저장: jobId={}, status={}, ttl={}초", jobData.getId(), jobData.getStatus(), ttlSeconds);
|
||||
|
||||
JobEntity entity = jobRepository.findById(jobData.getId())
|
||||
.orElseGet(() -> JobEntity.create(
|
||||
jobData.getId(),
|
||||
jobData.getEventDraftId(),
|
||||
jobData.getJobType()
|
||||
));
|
||||
|
||||
// Job 상태 업데이트
|
||||
entity.updateStatus(Job.Status.valueOf(jobData.getStatus()), jobData.getProgress());
|
||||
if (jobData.getResultMessage() != null) {
|
||||
entity.setResultMessage(jobData.getResultMessage());
|
||||
}
|
||||
if (jobData.getErrorMessage() != null) {
|
||||
entity.setErrorMessage(jobData.getErrorMessage());
|
||||
}
|
||||
|
||||
jobRepository.save(entity);
|
||||
// Note: TTL은 JPA에서는 무시됨 (Redis 전용)
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateJobStatus(String jobId, String status, Integer progress) {
|
||||
log.debug("[JPA] Job 상태 업데이트: jobId={}, status={}, progress={}", jobId, status, progress);
|
||||
jobRepository.findById(jobId).ifPresent(entity -> {
|
||||
entity.updateStatus(Job.Status.valueOf(status), progress);
|
||||
jobRepository.save(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateJobResult(String jobId, String resultMessage) {
|
||||
log.debug("[JPA] Job 결과 업데이트: jobId={}, resultMessage={}", jobId, resultMessage);
|
||||
jobRepository.findById(jobId).ifPresent(entity -> {
|
||||
entity.setResultMessage(resultMessage);
|
||||
jobRepository.save(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateJobError(String jobId, String errorMessage) {
|
||||
log.debug("[JPA] Job 에러 업데이트: jobId={}, errorMessage={}", jobId, errorMessage);
|
||||
jobRepository.findById(jobId).ifPresent(entity -> {
|
||||
entity.setErrorMessage(errorMessage);
|
||||
entity.updateStatus(Job.Status.FAILED, entity.getProgress());
|
||||
jobRepository.save(entity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
*/
|
||||
@Transactional
|
||||
public Job save(Job job) {
|
||||
log.debug("Job 저장: jobId={}, status={}", job.getId(), job.getStatus());
|
||||
|
||||
JobEntity entity = jobRepository.findById(job.getId())
|
||||
.orElseGet(() -> JobEntity.create(
|
||||
job.getId(),
|
||||
job.getEventDraftId(),
|
||||
job.getJobType()
|
||||
));
|
||||
|
||||
// Job 상태 업데이트
|
||||
entity.updateStatus(job.getStatus(), job.getProgress());
|
||||
if (job.getResultMessage() != null) {
|
||||
entity.setResultMessage(job.getResultMessage());
|
||||
}
|
||||
if (job.getErrorMessage() != null) {
|
||||
entity.setErrorMessage(job.getErrorMessage());
|
||||
}
|
||||
|
||||
JobEntity saved = jobRepository.save(entity);
|
||||
return saved.toDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Job 삭제 (추가 메서드)
|
||||
*/
|
||||
@Transactional
|
||||
public void delete(String jobId) {
|
||||
log.debug("Job 삭제: jobId={}", jobId);
|
||||
jobRepository.deleteById(jobId);
|
||||
}
|
||||
}
|
||||
@ -317,55 +317,6 @@ public class RedisGateway implements RedisAIDataReader, RedisImageWriter, ImageW
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
* Job 도메인 객체를 RedisJobData로 변환하여 저장
|
||||
*
|
||||
* @param job Job 도메인 객체
|
||||
* @return 저장된 Job
|
||||
*/
|
||||
@Override
|
||||
public Job save(Job job) {
|
||||
log.debug("[Redis] Job 저장 (호환성): jobId={}, status={}", job.getId(), job.getStatus());
|
||||
|
||||
RedisJobData jobData = RedisJobData.builder()
|
||||
.id(job.getId())
|
||||
.eventDraftId(job.getEventDraftId())
|
||||
.jobType(job.getJobType())
|
||||
.status(job.getStatus().name())
|
||||
.progress(job.getProgress())
|
||||
.resultMessage(job.getResultMessage())
|
||||
.errorMessage(job.getErrorMessage())
|
||||
.createdAt(job.getCreatedAt())
|
||||
.updatedAt(job.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
saveJob(jobData, 86400); // 24시간 TTL
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
*
|
||||
* @param jobId Job ID
|
||||
* @return Job 도메인 객체
|
||||
*/
|
||||
@Override
|
||||
public Optional<Job> findById(String jobId) {
|
||||
log.debug("[Redis] Job 조회 (호환성): jobId={}", jobId);
|
||||
return getJob(jobId).map(data -> Job.builder()
|
||||
.id(data.getId())
|
||||
.eventDraftId(data.getEventDraftId())
|
||||
.jobType(data.getJobType())
|
||||
.status(Job.Status.valueOf(data.getStatus()))
|
||||
.progress(data.getProgress())
|
||||
.resultMessage(data.getResultMessage())
|
||||
.errorMessage(data.getErrorMessage())
|
||||
.createdAt(data.getCreatedAt())
|
||||
.updatedAt(data.getUpdatedAt())
|
||||
.build());
|
||||
}
|
||||
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
private String getString(Map<Object, Object> map, String key) {
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
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<GeneratedImageEntity> 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 eventTitle 이벤트 제목
|
||||
* @param eventDescription 이벤트 설명
|
||||
*/
|
||||
public void update(String eventTitle, String eventDescription) {
|
||||
this.eventTitle = eventTitle;
|
||||
this.eventDescription = eventDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미지 추가
|
||||
*
|
||||
* @param image 생성된 이미지 엔티티
|
||||
*/
|
||||
public void addImage(GeneratedImageEntity image) {
|
||||
images.add(image);
|
||||
image.assignContent(this);
|
||||
}
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,172 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job 상태 업데이트
|
||||
*
|
||||
* @param status 새 상태
|
||||
* @param progress 진행률
|
||||
*/
|
||||
public void updateStatus(Job.Status status, int progress) {
|
||||
this.status = status;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 결과 메시지 설정
|
||||
*
|
||||
* @param resultMessage 결과 메시지
|
||||
*/
|
||||
public void setResultMessage(String resultMessage) {
|
||||
this.resultMessage = resultMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 메시지 설정
|
||||
*
|
||||
* @param errorMessage 에러 메시지
|
||||
*/
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,14 @@
|
||||
package com.kt.event.content.infra.gateway.mock;
|
||||
|
||||
import com.kt.event.content.biz.domain.Content;
|
||||
import com.kt.event.content.biz.domain.GeneratedImage;
|
||||
import com.kt.event.content.biz.domain.ImageStyle;
|
||||
import com.kt.event.content.biz.domain.Job;
|
||||
import com.kt.event.content.biz.domain.Platform;
|
||||
import com.kt.event.content.biz.dto.RedisImageData;
|
||||
import com.kt.event.content.biz.dto.RedisJobData;
|
||||
import com.kt.event.content.biz.usecase.out.ContentReader;
|
||||
import com.kt.event.content.biz.usecase.out.ContentWriter;
|
||||
import com.kt.event.content.biz.usecase.out.ImageReader;
|
||||
import com.kt.event.content.biz.usecase.out.ImageWriter;
|
||||
import com.kt.event.content.biz.usecase.out.JobReader;
|
||||
@ -13,6 +16,7 @@ import com.kt.event.content.biz.usecase.out.JobWriter;
|
||||
import com.kt.event.content.biz.usecase.out.RedisAIDataReader;
|
||||
import com.kt.event.content.biz.usecase.out.RedisImageWriter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -31,12 +35,15 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Primary
|
||||
@Profile({"local", "test"})
|
||||
public class MockRedisGateway implements RedisAIDataReader, RedisImageWriter, ImageWriter, ImageReader, JobWriter, JobReader {
|
||||
public class MockRedisGateway implements RedisAIDataReader, RedisImageWriter, ImageWriter, ImageReader, JobWriter, JobReader, ContentReader, ContentWriter {
|
||||
|
||||
private final Map<Long, Map<String, Object>> aiDataCache = new HashMap<>();
|
||||
|
||||
// In-memory storage for images and jobs
|
||||
// In-memory storage for contents, images, and jobs
|
||||
private final Map<Long, Content> contentStorage = new ConcurrentHashMap<>();
|
||||
private final Map<Long, GeneratedImage> imageByIdStorage = new ConcurrentHashMap<>();
|
||||
private final Map<String, RedisImageData> imageStorage = new ConcurrentHashMap<>();
|
||||
private final Map<String, RedisJobData> jobStorage = new ConcurrentHashMap<>();
|
||||
|
||||
@ -259,52 +266,136 @@ public class MockRedisGateway implements RedisAIDataReader, RedisImageWriter, Im
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== ContentReader 구현 ====================
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
* Job 도메인 객체를 RedisJobData로 변환하여 저장
|
||||
*
|
||||
* @param job Job 도메인 객체
|
||||
* @return 저장된 Job
|
||||
* 이벤트 초안 ID로 콘텐츠 조회 (이미지 목록 포함)
|
||||
*/
|
||||
@Override
|
||||
public Job save(Job job) {
|
||||
log.debug("[MOCK] Job 저장 (호환성): jobId={}, status={}", job.getId(), job.getStatus());
|
||||
public Optional<Content> findByEventDraftIdWithImages(Long eventDraftId) {
|
||||
try {
|
||||
Content content = contentStorage.get(eventDraftId);
|
||||
if (content == null) {
|
||||
log.warn("[MOCK] Content를 찾을 수 없음: eventDraftId={}", eventDraftId);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
RedisJobData jobData = RedisJobData.builder()
|
||||
.id(job.getId())
|
||||
.eventDraftId(job.getEventDraftId())
|
||||
.jobType(job.getJobType())
|
||||
.status(job.getStatus().name())
|
||||
.progress(job.getProgress())
|
||||
.resultMessage(job.getResultMessage())
|
||||
.errorMessage(job.getErrorMessage())
|
||||
.createdAt(job.getCreatedAt())
|
||||
.updatedAt(job.getUpdatedAt())
|
||||
.build();
|
||||
// 이미지 목록 조회 및 Content 재생성 (immutable pattern)
|
||||
List<GeneratedImage> images = findImagesByEventDraftId(eventDraftId);
|
||||
Content contentWithImages = Content.builder()
|
||||
.id(content.getId())
|
||||
.eventDraftId(content.getEventDraftId())
|
||||
.eventTitle(content.getEventTitle())
|
||||
.eventDescription(content.getEventDescription())
|
||||
.images(images)
|
||||
.createdAt(content.getCreatedAt())
|
||||
.updatedAt(content.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
saveJob(jobData, 86400); // 24시간 TTL
|
||||
return job;
|
||||
return Optional.of(contentWithImages);
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] Content 조회 실패: eventDraftId={}", eventDraftId, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 호환성을 위한 메서드 (Phase 3에서 삭제 예정)
|
||||
*
|
||||
* @param jobId Job ID
|
||||
* @return Job 도메인 객체
|
||||
* 이미지 ID로 이미지 조회
|
||||
*/
|
||||
@Override
|
||||
public Optional<Job> findById(String jobId) {
|
||||
log.debug("[MOCK] Job 조회 (호환성): jobId={}", jobId);
|
||||
return getJob(jobId).map(data -> Job.builder()
|
||||
.id(data.getId())
|
||||
.eventDraftId(data.getEventDraftId())
|
||||
.jobType(data.getJobType())
|
||||
.status(Job.Status.valueOf(data.getStatus()))
|
||||
.progress(data.getProgress())
|
||||
.resultMessage(data.getResultMessage())
|
||||
.errorMessage(data.getErrorMessage())
|
||||
.createdAt(data.getCreatedAt())
|
||||
.updatedAt(data.getUpdatedAt())
|
||||
.build());
|
||||
public Optional<GeneratedImage> findImageById(Long imageId) {
|
||||
try {
|
||||
GeneratedImage image = imageByIdStorage.get(imageId);
|
||||
if (image == null) {
|
||||
log.warn("[MOCK] 이미지를 찾을 수 없음: imageId={}", imageId);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(image);
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] 이미지 조회 실패: imageId={}", imageId, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 이미지 목록 조회
|
||||
*/
|
||||
@Override
|
||||
public List<GeneratedImage> findImagesByEventDraftId(Long eventDraftId) {
|
||||
try {
|
||||
return imageByIdStorage.values().stream()
|
||||
.filter(image -> image.getEventDraftId().equals(eventDraftId))
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] 이미지 목록 조회 실패: eventDraftId={}", eventDraftId, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== ContentWriter 구현 ====================
|
||||
|
||||
private static Long nextContentId = 1L;
|
||||
private static Long nextImageId = 1L;
|
||||
|
||||
/**
|
||||
* 콘텐츠 저장
|
||||
*/
|
||||
@Override
|
||||
public Content save(Content content) {
|
||||
try {
|
||||
// ID가 없으면 생성하여 새 Content 객체 생성 (immutable pattern)
|
||||
Long id = content.getId() != null ? content.getId() : nextContentId++;
|
||||
|
||||
Content savedContent = Content.builder()
|
||||
.id(id)
|
||||
.eventDraftId(content.getEventDraftId())
|
||||
.eventTitle(content.getEventTitle())
|
||||
.eventDescription(content.getEventDescription())
|
||||
.images(content.getImages())
|
||||
.createdAt(content.getCreatedAt())
|
||||
.updatedAt(content.getUpdatedAt())
|
||||
.build();
|
||||
|
||||
contentStorage.put(savedContent.getEventDraftId(), savedContent);
|
||||
log.info("[MOCK] Content 저장 완료: contentId={}, eventDraftId={}",
|
||||
savedContent.getId(), savedContent.getEventDraftId());
|
||||
|
||||
return savedContent;
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] Content 저장 실패: eventDraftId={}", content.getEventDraftId(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미지 저장
|
||||
*/
|
||||
@Override
|
||||
public GeneratedImage saveImage(GeneratedImage image) {
|
||||
try {
|
||||
// ID가 없으면 생성하여 새 GeneratedImage 객체 생성 (immutable pattern)
|
||||
Long id = image.getId() != null ? image.getId() : nextImageId++;
|
||||
|
||||
GeneratedImage savedImage = GeneratedImage.builder()
|
||||
.id(id)
|
||||
.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();
|
||||
|
||||
imageByIdStorage.put(savedImage.getId(), savedImage);
|
||||
log.info("[MOCK] 이미지 저장 완료: imageId={}, eventDraftId={}, style={}, platform={}",
|
||||
savedImage.getId(), savedImage.getEventDraftId(), savedImage.getStyle(), savedImage.getPlatform());
|
||||
|
||||
return savedImage;
|
||||
} catch (Exception e) {
|
||||
log.error("[MOCK] 이미지 저장 실패: eventDraftId={}", image.getEventDraftId(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
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<ContentEntity, Long> {
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 콘텐츠 조회 (이미지 목록 포함)
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @return 콘텐츠 엔티티
|
||||
*/
|
||||
@Query("SELECT DISTINCT c FROM ContentEntity c " +
|
||||
"LEFT JOIN FETCH c.images " +
|
||||
"WHERE c.eventDraftId = :eventDraftId")
|
||||
Optional<ContentEntity> findByEventDraftIdWithImages(@Param("eventDraftId") Long eventDraftId);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 콘텐츠 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @return 콘텐츠 엔티티
|
||||
*/
|
||||
Optional<ContentEntity> findByEventDraftId(Long eventDraftId);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 콘텐츠 존재 여부 확인
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @return 존재 여부
|
||||
*/
|
||||
boolean existsByEventDraftId(Long eventDraftId);
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
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<GeneratedImageEntity, Long> {
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 이미지 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @return 이미지 엔티티 목록
|
||||
*/
|
||||
List<GeneratedImageEntity> findByEventDraftId(Long eventDraftId);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID와 스타일로 이미지 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @param style 이미지 스타일
|
||||
* @return 이미지 엔티티 목록
|
||||
*/
|
||||
List<GeneratedImageEntity> findByEventDraftIdAndStyle(Long eventDraftId, ImageStyle style);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID와 플랫폼으로 이미지 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @param platform 플랫폼
|
||||
* @return 이미지 엔티티 목록
|
||||
*/
|
||||
List<GeneratedImageEntity> findByEventDraftIdAndPlatform(Long eventDraftId, Platform platform);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID와 선택 여부로 이미지 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @param selected 선택 여부
|
||||
* @return 이미지 엔티티 목록
|
||||
*/
|
||||
List<GeneratedImageEntity> 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<GeneratedImageEntity> 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);
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
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<JobEntity, String> {
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID로 Job 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @return Job 엔티티 목록
|
||||
*/
|
||||
List<JobEntity> findByEventDraftId(Long eventDraftId);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID와 상태로 Job 목록 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @param status Job 상태
|
||||
* @return Job 엔티티 목록
|
||||
*/
|
||||
List<JobEntity> findByEventDraftIdAndStatus(Long eventDraftId, Job.Status status);
|
||||
|
||||
/**
|
||||
* 이벤트 초안 ID와 Job 타입으로 최신 Job 조회
|
||||
*
|
||||
* @param eventDraftId 이벤트 초안 ID
|
||||
* @param jobType Job 타입
|
||||
* @return Job 엔티티
|
||||
*/
|
||||
Optional<JobEntity> findFirstByEventDraftIdAndJobTypeOrderByCreatedAtDesc(Long eventDraftId, String jobType);
|
||||
}
|
||||
@ -2,22 +2,6 @@ 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
|
||||
@ -47,4 +31,3 @@ azure:
|
||||
logging:
|
||||
level:
|
||||
com.kt.event: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user