From 3d1dbda74b6e6773b1008b262b2d494c05820f64 Mon Sep 17 00:00:00 2001 From: cherry2250 Date: Thu, 23 Oct 2025 20:48:56 +0900 Subject: [PATCH] =?UTF-8?q?content-service=20=ED=95=B5=EC=8B=AC=20?= =?UTF-8?q?=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=81=EC=86=8D=EC=84=B1=20=EA=B3=84=EC=B8=B5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Domain 모델 구현 (ImageStyle, Platform, GeneratedImage, Content, Job) - JPA Entity 및 Repository 구현 (3개 엔티티, 3개 리포지토리) - UseCase 인터페이스 정의 (Inbound 6개, Outbound 8개) - Service 구현 (JobManagement, GetEventContent, GetImageList, GetImageDetail) - DTO 구현 (ContentCommand, ContentInfo, ImageInfo, JobInfo) - Application 설정 (ContentApplication, application.yml) - 컴파일 오류 수정 및 검증 완료 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../executionHistory/executionHistory.bin | Bin 85985 -> 232869 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes .gradle/8.10/fileHashes/fileHashes.bin | Bin 20297 -> 24397 bytes .gradle/8.10/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.10/fileHashes/resourceHashesCache.bin | Bin 19075 -> 21013 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .gradle/buildOutputCleanup/outputFiles.bin | Bin 18965 -> 19307 bytes .gradle/file-system.probe | Bin 8 -> 8 bytes .../kt/event/content/biz/domain/Content.java | 99 ++++++++++++ .../content/biz/domain/GeneratedImage.java | 76 ++++++++++ .../event/content/biz/domain/ImageStyle.java | 32 ++++ .../com/kt/event/content/biz/domain/Job.java | 140 +++++++++++++++++ .../kt/event/content/biz/domain/Platform.java | 53 +++++++ .../event/content/biz/dto/ContentCommand.java | 40 +++++ .../kt/event/content/biz/dto/ContentInfo.java | 47 ++++++ .../kt/event/content/biz/dto/ImageInfo.java | 49 ++++++ .../com/kt/event/content/biz/dto/JobInfo.java | 47 ++++++ .../biz/service/GetEventContentService.java | 32 ++++ .../biz/service/GetImageDetailService.java | 32 ++++ .../biz/service/GetImageListService.java | 33 ++++ .../biz/service/JobManagementService.java | 33 ++++ .../biz/usecase/in/GenerateImagesUseCase.java | 19 +++ .../usecase/in/GetEventContentUseCase.java | 17 +++ .../biz/usecase/in/GetImageDetailUseCase.java | 17 +++ .../biz/usecase/in/GetImageListUseCase.java | 19 +++ .../biz/usecase/in/GetJobStatusUseCase.java | 17 +++ .../usecase/in/RegenerateImageUseCase.java | 18 +++ .../content/biz/usecase/out/CDNUploader.java | 17 +++ .../biz/usecase/out/ContentReader.java | 37 +++++ .../biz/usecase/out/ContentWriter.java | 26 ++++ .../biz/usecase/out/ImageGeneratorCaller.java | 21 +++ .../content/biz/usecase/out/JobReader.java | 19 +++ .../content/biz/usecase/out/JobWriter.java | 17 +++ .../biz/usecase/out/RedisAIDataReader.java | 19 +++ .../biz/usecase/out/RedisImageWriter.java | 21 +++ .../content/infra/ContentApplication.java | 27 ++++ .../infra/gateway/entity/ContentEntity.java | 98 ++++++++++++ .../gateway/entity/GeneratedImageEntity.java | 139 +++++++++++++++++ .../infra/gateway/entity/JobEntity.java | 143 ++++++++++++++++++ .../repository/ContentJpaRepository.java | 41 +++++ .../GeneratedImageJpaRepository.java | 68 +++++++++ .../gateway/repository/JobJpaRepository.java | 40 +++++ .../src/main/resources/application.yml | 50 ++++++ 43 files changed, 1603 insertions(+) create mode 100644 content-service/src/main/java/com/kt/event/content/biz/domain/Content.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/domain/GeneratedImage.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/domain/ImageStyle.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/domain/Job.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/domain/Platform.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/dto/ContentCommand.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/dto/ContentInfo.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/dto/ImageInfo.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/dto/JobInfo.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/service/GetEventContentService.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/service/GetImageDetailService.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/service/GetImageListService.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/service/JobManagementService.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/GenerateImagesUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetEventContentUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageDetailUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetImageListUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/GetJobStatusUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/in/RegenerateImageUseCase.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/CDNUploader.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentReader.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/ContentWriter.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/ImageGeneratorCaller.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobReader.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/JobWriter.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisAIDataReader.java create mode 100644 content-service/src/main/java/com/kt/event/content/biz/usecase/out/RedisImageWriter.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/ContentApplication.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/entity/ContentEntity.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/entity/GeneratedImageEntity.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/entity/JobEntity.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/repository/ContentJpaRepository.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/repository/GeneratedImageJpaRepository.java create mode 100644 content-service/src/main/java/com/kt/event/content/infra/gateway/repository/JobJpaRepository.java create mode 100644 content-service/src/main/resources/application.yml diff --git a/.gradle/8.10/executionHistory/executionHistory.bin b/.gradle/8.10/executionHistory/executionHistory.bin index 2177cdd01b3d65d3f655cadb7b28c6362b23ce72..650db3cc566738d265ce4b755cf0734219420375 100644 GIT binary patch literal 232869 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%0D=+dAg`5+CWeFnytV1bPkRF5P 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 M!Xgk3`j5{40DML?p#T5? delta 141 zcmZ2FoA2R#)(KMV4Do3U3}sg~Dhf+5GEMfCFyQ$Q1q=)fjvEbMh;MYbDL&DGf3u^9 zj<{>tVW8^6vs03alPc5F)2gykjPo*$N{urTixaDgGYS(0EVHEe4A0ED`&QLJ;ke$s a%!_OoLfdr%82Q*YPr2)nwwdGX%_abhlQ`c1 diff --git a/.gradle/8.10/executionHistory/executionHistory.lock b/.gradle/8.10/executionHistory/executionHistory.lock index 0ce4c9646ca85d214092028f7db63bee6e79e803..0563a0be7e7100fb33330285bf952971b8c40a8a 100644 GIT binary patch literal 17 UcmZRsbes@(Px9DK1_%%U05TB-G5`Po literal 17 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 delta 257 zcmX@RkMZO@#tkMCj4G3TB|IjFN=i(A3dF8b5|c}TSXNqMa;mh!WL_DG$)P~pD&sNv zt*pf4wQ@i)c_3e2a`HAH{Y*Z7a;$>Hw8T zR9S-OKNK)9Fc)rA{2{*4V1>wJM-OFY+jrY1r+erF>F<+gg6Q{#lTQO_m#3#D{{_+x z+MJX1JwbHJncNGe V=T6=arhiWU2&Qc|D|@T4004^fUhDt> diff --git a/.gradle/8.10/fileHashes/fileHashes.lock b/.gradle/8.10/fileHashes/fileHashes.lock index 340e0dd0673653407cd5d6c667877cdda9e87606..27a2f9c90ca082e67e1cfb114981d6d4be3e7e8d 100644 GIT binary patch literal 17 UcmZS9`MA7`o8<*R0|dMU04#e1MF0Q* literal 17 UcmZS9`MA7`o8<*R0|dAO04vo4p8x;= diff --git a/.gradle/8.10/fileHashes/resourceHashesCache.bin b/.gradle/8.10/fileHashes/resourceHashesCache.bin index 3d2189638c23437e4cea73219677f8823a00620a..3d54f6a9080e8a7db20dc9688604ef9bba15e1d9 100644 GIT binary patch literal 21013 zcmeI3`8QO5AHYX7hO{D76j39JLcWATiZP2uswZTLC}b=xBHxm1iR{_pTN6>T-7eqQI!ogdt*M<9qTnZ!53Kijf@ zcF`UxfC``jr~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oQ}3jDtm*gLh5`PgE_ zHq7DfEIvgbY+5k2W9Z{v)?(r{c^Uh&!6)$l17C$LHYGk$b{MCv1U$&hx_hmbMJ>iD z_on$(i={say4x^Lmj^s7>dl4H@GIOynA&!W0v-`}KSVLx-W=nUalm80T6!?FcCNxX z6YzvH)jP(sfBk`R>TAFg2gY=aiufykb0bVpbm=S(rKn3Fzcfd;{ ze#_9cYIHG9H3IwrEpLwE+Xx{`j15z$+qe3=+txsTgN;170;+8#>@;VUKYp8SuJ-(2WQ8R2;*&fgj)vyqcyl z@{Ss~KL|L-a`o_qD7y}f?=AzpDc831;~q)S#~biYQ{fdw{dEGE&o~QsS0^i|g4yyM ze_x$x-uIo%FFcosajGle-Fo@ADTWSIjMF{<-Xm9_a5nb&VvN%p0Ut23?ymUF*N<_g zBjBHVRyK;p`$$bZ#_24; zh39N+$nku&5aWj1r@47vX>K!BALERJfQvl0?T~kHvckC05a7!%e62~89>71RlsD7- zNw^t-^Gq7^X-@%P*;}pQqVr=rK7Tpjt8`z^UzAXqj`7`QfU7uPy-g1rQNuX(C*WIM z4meVd%)!r38XItp(f36u`Ink7pArqYW@KW)?UO>r7&jsTuH&!D5Ogr}!??jS!1a5@ z_H44!{LfX7K%i~}oR-FErTO@n;d7{f)BAhIn#peXb&O^SI75D(wDu==N!(u#xRGnA zY<-4_FvfQ$18zLgG%WE_(Fyk(05{RO<*<0K+Gm^}1KgB4=Gu_^3-4^00=T95Z>=qT zuKAd6C^5|w^@Dk@Yd^*~;~L;rt?fcqtUYZQXVwF57g&E$Hbm$KKF1nxdqSDw%sNxWglAS^|;cjQIvP0e1@U)XPZG8^Y&@0M2Tf5Ge1}!OvUz2;eTH;C$=H0{DDt z9^h{JyDpK>j45Gr7>@xzZP5|$<3?TYCzw`fnrbfpvd&6Ddb4EMeiFHuKi5q>Bb|S( zV9N(`$;DaT<+$0S_hZF@0rqvVy?z}9g76Ge>HQl{kGOd6JnMK-U-&#X!Hm@=M+RRz zZm`Xgw3dBqd-aiLut9rz&OQ2!Qr?}`qJT$HNle%vGB!;_$!0IDWMtNTl*t)@4aNQ1 zFL?YqSc)Vyk)_UQp0J_aCFG&EMuko7s5iJt%+!L7Xm|M{PYWKhf2HJ2g>uTuS&U~_ z&T~#%gtT><8V0hHW|@&=FBn~&u}igh!Hxv6t*sZ}8HKKL?ayTS<<(R|Rnt0Haj@|; zwHhgYX;3y@zkS?U`$I9Hr|m+%ZjIf}lQKJP(_jC{bX;T(nk?P}W^I%({AI@iwa%*x=Ni@UyS| zMI%28l3G)xmR`k z6iDdXc)ZIM{0lbh0{M%@+|QjLYbj@)o)DVl8YD_BRF>F~%-LDiT$I{cxB{MGdG58; z6;e=_z`oo24rmZ1V52DR{cvGsc=lVz@Qe4Dht9wTSvF_H-?J|!Sjda>F8fCsY_wlb z2{it|J8ot7ys_=(6Yi5FGm7!Hh~LgBJvR7yNYiZdm|X*G>>qbIC~fMsAloq~GDl?o zELZ-^jps_#_=U43;@#!)nuqA{4BwH}Vu4vXW%fJ!eZz|W+zcCoqi&9FA#3>rleItf zblgaUjWzQq46BD0$?5#0Q{}(+xWL9j;+M?OjJIur>Gj5vzgEk^#)?>%f9YYu`-Ko@h!xP%K=I?h;jbWo#J@tT!$#!Gq4cnHr*Z6XufCm!zVDbQN_aJP`3O$iPRy_{jiZn{_;Ye(fhScBW%p3 zm~WQzL&wldyo6n98#GR*mc&#af@l0t-7I0bW<9HmS$*H6pgjUMY}35III>&J?-DP4 zdF~yv7&ZhdEjxn{wg6cxm8kPva6KD literal 17 UcmZQ(PG7Ze^~s_h1_&?)05hosdjJ3c diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 4ed6f06d6395816365de6075047efe060d9cdefb..16d0a6ea2d63c7811eb4fa1b4c6d7769cd497f1c 100644 GIT binary patch delta 520 zcmbO_h4J+?#tkMC^}0>={^8Pn%2Etq;HCZ_3R(~Ex%G>QNd+oC87AJX{OL=v@1_+H zac@(Ycu$#g z{ZJ(*C-+Lsk)nr)DXuy@V=!J2_R-qMqB|F536}s_9Ub zkuVFyRUf?grNna)s@M`Hu4464TYa(id#E@}_`jOM&E~l8B0W&?Mwo=oBBxCc6F-VW z%@>4;>lvqeFD*h1PXmCMzv!llq#>xJk$0qOhFr4h?VFtvS E0CjTXc>n+a delta 71 zcmaDojdAJ}#tkMCj8c-rUHc9u%qEZHv_X07iw8UgzAg-16n0yyV YtI7m1F*R;f{2{*4V20>sM~^Fv0BSH8LI3~& diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index ac4beb46220d110a11f9e5f196fa452a079e920d..c69a406cb2f7304e67d85feaea66d3cfb070935f 100644 GIT binary patch literal 8 PcmZQzV4NkeA+8Ak2A%>u literal 8 PcmZQzV4Nl3y6qtV25bU| 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