Skip to content

Commit aa26439

Browse files
authored
Merge pull request #47 from DropThe8bit/feature/story
[refactor] 스토리 조회/삭제/대표 이미지 설정 리팩토링
2 parents d7d30a4 + 1103d93 commit aa26439

4 files changed

Lines changed: 72 additions & 51 deletions

File tree

src/main/java/everTale/everTale_be/domain/story/controller/StoryController.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class StoryController {
2929
@GetMapping("/{storyId}/scenes/{pageNum}")
3030
public ApiResponse<SceneResponseDTO> getSceneBySceneNum(
3131
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
32-
@Parameter(description = "장면 번호") @PathVariable int pageNum
32+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum
3333
) {
3434
SceneResponseDTO scene = storyService.getSceneBySceneNum(storyId, pageNum);
3535
return ApiResponse.onSuccess(scene);
@@ -48,18 +48,18 @@ public ApiResponse<String> updateStoryTitle(
4848
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
4949
@Parameter(description = "스토리 제목") @RequestParam String title
5050
) {
51-
storyService.updateStoryTitle(storyId, title);
51+
storyService.updateStoryTitleAndMainImage(storyId, title);
5252
return ApiResponse.onSuccess(title);
5353
}
5454

5555
@Operation(summary = "줄거리 수정 API", description = "특정 장면의 줄거리를 사용자가 수정한 내용으로 업데이트한다.")
56-
@PatchMapping("/{storyId}/scenes/{sceneNum}")
56+
@PatchMapping("/{storyId}/scenes/{pageNum}")
5757
public ApiResponse<String> updateSceneContent(
5858
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
59-
@Parameter(description = "장면 번호") @PathVariable int sceneNum,
59+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum,
6060
@RequestBody StoryRequestDTO.StoryUpdateRequestDTO request
6161
) {
62-
String updatedContent = storyService.updateSceneContent(storyId, sceneNum, request.getUpdatedContent());
62+
String updatedContent = storyService.updateSceneContent(storyId, pageNum, request.getUpdatedContent());
6363
return ApiResponse.onSuccess(updatedContent);
6464
}
6565

@@ -101,55 +101,55 @@ public ApiResponse<String> createInitStory(
101101
}
102102

103103
@Operation(summary = "다음 줄거리 생성 API", description = "이전 줄거리를 기반으로 다음 줄거리를 생성한다.")
104-
@PostMapping("/{storyId}/scenes/{sceneNum}")
104+
@PostMapping("/{storyId}/scenes/{pageNum}")
105105
public ApiResponse<String> createNextStory(
106106
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
107-
@Parameter(description = "장면 번호") @PathVariable int sceneNum) {
108-
String nextStory = storyService.generateNextScene(storyId, sceneNum);
107+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum) {
108+
String nextStory = storyService.generateNextScene(storyId, pageNum);
109109
return ApiResponse.onSuccess(nextStory);
110110
}
111111

112112
@Operation(summary = "질문 생성 API", description = "이전 줄거리를 기반으로 아이에게 던질 질문을 생성한다.")
113-
@PostMapping("/{storyId}/scenes/{sceneNum}/question")
113+
@PostMapping("/{storyId}/scenes/{pageNum}/question")
114114
public ApiResponse<String> createQuestionFromPrevScene(
115115
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
116-
@Parameter(description = "장면 번호") @PathVariable int sceneNum) {
117-
String question = storyService.generateQuestionFromPreviousScene(storyId, sceneNum);
116+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum) {
117+
String question = storyService.generateQuestionFromPreviousScene(storyId, pageNum);
118118
return ApiResponse.onSuccess(question);
119119
}
120120

121121
@Operation(summary = "답변 기반 다음 줄거리 생성 API", description = "아이의 답변을 기반으로 다음 줄거리를 생성한다.")
122-
@PostMapping("/{storyId}/scenes/{sceneNum}/next-from-answer")
122+
@PostMapping("/{storyId}/scenes/{pageNum}/next-from-answer")
123123
public ApiResponse<String> createNextSceneFromAnswer(
124124
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
125-
@Parameter(description = "장면 번호") @PathVariable int sceneNum,
125+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum,
126126
@RequestBody StoryRequestDTO.StoryAnswerRequestDTO request
127127
) {
128-
String nextStory = storyService.generateNextSceneWithAnswer(storyId, sceneNum, request.getAnswer());
128+
String nextStory = storyService.generateNextSceneWithAnswer(storyId, pageNum, request.getAnswer());
129129
return ApiResponse.onSuccess(nextStory);
130130
}
131131
@Operation(summary = "스케치 기반 이미지 생성 API", description = "아이의 스케치 이미지와 줄거리를 기반으로 이미지를 생성합니다.")
132132
@PostMapping(
133-
value = "/{storyId}/scenes/{sceneNum}/controlnet",
133+
value = "/{storyId}/scenes/{pageNum}/controlnet",
134134
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
135135
)
136136
public ApiResponse<String> createImageFromSketch(
137137
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
138-
@Parameter(description = "장면 번호") @PathVariable int sceneNum,
138+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum,
139139
@Parameter(description = "스케치 이미지 파일", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE))
140140
@RequestPart("sketch") MultipartFile sketch
141141
) {
142-
String image = storyService.generateImageFromSketch(storyId, sceneNum, sketch);
142+
String image = storyService.generateImageFromSketch(storyId, pageNum, sketch);
143143
return ApiResponse.onSuccess(image);
144144
}
145145

146146
@Operation(summary = "줄거리 기반 이미지 생성 API", description = "줄거리 텍스트만을 기반으로 이미지를 생성합니다.")
147-
@PostMapping( "/{storyId}/scenes/{sceneNum}/dalle")
147+
@PostMapping( "/{storyId}/scenes/{pageNum}/dalle")
148148
public ApiResponse<String> createImageFromPrompt(
149149
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
150-
@Parameter(description = "장면 번호") @PathVariable int sceneNum
150+
@Parameter(description = "페이지 번호(1~8)") @PathVariable int pageNum
151151
) {
152-
String image = storyService.generateImageFromPrompt(storyId, sceneNum);
152+
String image = storyService.generateImageFromPrompt(storyId, pageNum);
153153
return ApiResponse.onSuccess(image);
154154
}
155155

src/main/java/everTale/everTale_be/domain/story/dto/StoryRequestDTO.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,12 @@ public static class NextStoryGenerateRequestDTO {
3737
@Schema(description = "이전 줄거리", example = "여행을 좋아하는 토토로는 숲으로 향했어요. 숲에서는 신비한 친구가 기다리고 있었어요.")
3838
private String previous;
3939

40-
@Schema(description = "장면 번호", example = "2")
41-
private int sceneNum;
40+
@Schema(description = "페이지 번호(1~8)", example = "2")
41+
private int pageNum;
4242

4343
@Schema(description = "장르", example = "ADVENTURE")
4444
private String genre;
4545

46-
@Schema(description = "동화 제목", example = "토토로의 모험 여행")
47-
private String title;
48-
4946
@Schema(description = "주인공 이름", example = "토토로")
5047
private String name;
5148

@@ -90,7 +87,6 @@ public static class StoryUpdateRequestDTO {
9087
@Getter
9188
@Builder
9289
public static class FastApiInitStoryRequestDTO {
93-
private String title;
9490
private String genre;
9591
private String worldView;
9692
private String name;

src/main/java/everTale/everTale_be/domain/story/entity/Story.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public void updateTitle(String title) {
5858
this.title = title;
5959
}
6060

61+
public void updateImageUrl(String imageUrl) {
62+
if (imageUrl != null && !imageUrl.isBlank()) {
63+
this.imageUrl = imageUrl;
64+
}
65+
}
66+
6167
public void addEasterEggLetter(EasterEggLetter letter) {
6268
this.easterEggLetter = letter;
6369
letter.updateStory(this);

src/main/java/everTale/everTale_be/domain/story/service/StoryService.java

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import everTale.everTale_be.domain.character.entity.enums.Gender;
66
import everTale.everTale_be.domain.character.repository.PersonalityRepository;
77
import everTale.everTale_be.domain.character.repository.StoryCharacterRepository;
8+
import everTale.everTale_be.domain.easterEgg.entity.EasterEggVoice;
89
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
910
import everTale.everTale_be.domain.profile.entity.Profile;
1011
import everTale.everTale_be.domain.profile.repository.ProfileRepository;
@@ -31,6 +32,7 @@
3132

3233
import java.util.HashSet;
3334
import java.util.List;
35+
import java.util.Random;
3436
import java.util.Set;
3537
import java.util.stream.Collectors;
3638

@@ -65,15 +67,24 @@ public StoryResponseDTO.StoryScenesResponseDTO getScenesOfStory(Long storyId) {
6567
}
6668

6769
@Transactional
68-
public void updateStoryTitle(Long storyId, String title) {
70+
public void updateStoryTitleAndMainImage(Long storyId, String title) {
6971
Story story = findMyStory(storyId);
70-
story.updateTitle(title);
71-
}
7272

73+
List<Scene> scenesWithImage = sceneRepository.findByStoryIdOrderByPageAsc(storyId)
74+
.stream()
75+
.filter(s -> s.getImageUrl() != null && !s.getImageUrl().isBlank())
76+
.collect(Collectors.toList());
77+
78+
if (!scenesWithImage.isEmpty()) {
79+
Scene randomScene = scenesWithImage.get(new Random().nextInt(scenesWithImage.size()));
80+
story.updateImageUrl(randomScene.getImageUrl());
81+
}
7382

83+
story.updateTitle(title);
84+
}
7485
@Transactional
75-
public String updateSceneContent(Long storyId, int sceneNum, String updatedContent) {
76-
Scene scene = findMyScene(storyId, sceneNum);
86+
public String updateSceneContent(Long storyId, int pageNum, String updatedContent) {
87+
Scene scene = findMyScene(storyId, pageNum);
7788
scene.updateContent(updatedContent);
7889
return updatedContent;
7990
}
@@ -84,7 +95,6 @@ public String updateSceneContent(Long storyId, int sceneNum, String updatedConte
8495
public void deleteStoryWithScenes(Long storyId) {
8596
Story story = findMyStory(storyId);
8697
deleteS3AssetsOf(story);
87-
// TODO: 여기에 EasterEggVoice S3 파일 삭제 로직 추가 필요
8898
storyRepository.delete(story);
8999
}
90100

@@ -103,18 +113,29 @@ private void deleteS3AssetsOf(Story story) {
103113
}
104114
}
105115

106-
// 씬 이미지들 삭제
116+
// 씬 이미지 및 보이스파일 삭제
107117
List<Scene> scenes = sceneRepository.findByStoryIdOrderByPageAsc(story.getId());
108118
Set<String> sceneImageUrls = new HashSet<>();
119+
Set<String> voiceFiles = new HashSet<>();
109120
for (Scene s : scenes) {
110121
if (s.getImageUrl() != null && !s.getImageUrl().isBlank()) {
111122
sceneImageUrls.add(s.getImageUrl());
112123
}
113-
124+
EasterEggVoice voice = s.getEasterEggVoice();
125+
if (voice != null) {
126+
String voiceFile = voice.getVoiceFile();
127+
if (voiceFile != null && !voiceFile.isBlank()) {
128+
voiceFiles.add(voiceFile);
129+
}
130+
}
114131
}
115132
for (String url : sceneImageUrls) {
116133
s3Manager.deleteFileByS3Url(url);
117134
}
135+
136+
for( String file : voiceFiles){
137+
s3Manager.deleteFile(file);
138+
}
118139
}
119140

120141

@@ -182,7 +203,6 @@ public String generateInitScene(Long storyId, StoryRequestDTO.StoryWorldViewRequ
182203

183204
// 3. FastAPI 요청용 JSON 만들기
184205
StoryRequestDTO.FastApiInitStoryRequestDTO requestDto = StoryRequestDTO.FastApiInitStoryRequestDTO.builder()
185-
.title(story.getTitle())
186206
.genre(request.getGenre().name())
187207
.worldView(request.getWorldView())
188208
.name(storyCharacter.getName())
@@ -214,9 +234,9 @@ public String generateInitScene(Long storyId, StoryRequestDTO.StoryWorldViewRequ
214234

215235
// 이전 장면 기반 다음 줄거리 생성
216236
@Transactional
217-
public String generateNextScene(Long storyId, int sceneNum) {
237+
public String generateNextScene(Long storyId, int pageNum) {
218238
// 1. 이전 줄거리 조회
219-
Scene prevScene = findMyScene(storyId, sceneNum-1);
239+
Scene prevScene = findMyScene(storyId, pageNum-1);
220240
String previousContent = prevScene.getContent();
221241

222242
// 2. Story & Character 조회
@@ -232,9 +252,8 @@ public String generateNextScene(Long storyId, int sceneNum) {
232252
StoryRequestDTO.NextStoryGenerateRequestDTO dto =
233253
StoryRequestDTO.NextStoryGenerateRequestDTO.builder()
234254
.previous(previousContent)
235-
.sceneNum(sceneNum)
255+
.pageNum(pageNum)
236256
.genre(story.getGenre().name())
237-
.title(story.getTitle())
238257
.name(character.getName())
239258
.age(character.getAge())
240259
.gender(character.getGender().name())
@@ -246,7 +265,7 @@ public String generateNextScene(Long storyId, int sceneNum) {
246265

247266
// 6. 새 Scene 저장
248267
Scene newScene = Scene.builder()
249-
.page(sceneNum)
268+
.page(pageNum)
250269
.content(nextContent)
251270
.build();
252271
story.addScene(newScene);
@@ -256,31 +275,31 @@ public String generateNextScene(Long storyId, int sceneNum) {
256275

257276
// 이전 장면 기반 질문 생성
258277
@Transactional(readOnly = true)
259-
public String generateQuestionFromPreviousScene(Long storyId, int sceneNum) {
260-
Scene prevScene = findMyScene(storyId, sceneNum - 1);
278+
public String generateQuestionFromPreviousScene(Long storyId, int pageNum) {
279+
Scene prevScene = findMyScene(storyId, pageNum - 1);
261280
return storyApiClient.callFastApiForQuestion(prevScene.getContent());
262281
}
263282

264283
// 아이의 대답 기반 다음 줄거리 생성
265284
@Transactional
266-
public String generateNextSceneWithAnswer(Long storyId, int sceneNum, String answer) {
267-
Scene prevScene = findMyScene(storyId, sceneNum - 1);
285+
public String generateNextSceneWithAnswer(Long storyId, int pageNum, String answer) {
286+
Scene prevScene = findMyScene(storyId, pageNum - 1);
268287

269288
String nextContent = storyApiClient.callFastApiForNextStoryWithAnswer(prevScene.getContent(), answer);
270289

271290
Story story = prevScene.getStory();
272291

273292
Scene newScene = Scene.builder()
274-
.page(sceneNum)
293+
.page(pageNum)
275294
.content(nextContent)
276295
.build();
277296
story.addScene(newScene);
278297
return nextContent;
279298
}
280299
// 줄거리 및 아이그림 기반 그림 생성
281300
@Transactional
282-
public String generateImageFromSketch(Long storyId, int sceneNum, MultipartFile sketch) {
283-
Scene scene = findMyScene(storyId, sceneNum);
301+
public String generateImageFromSketch(Long storyId, int pageNum, MultipartFile sketch) {
302+
Scene scene = findMyScene(storyId, pageNum);
284303

285304
String prompt = scene.getContent();
286305
String imageUrl = storyApiClient.callFastApiForImageFromSketch(sketch, prompt, scene.getStory().getGenre().name());
@@ -291,8 +310,8 @@ public String generateImageFromSketch(Long storyId, int sceneNum, MultipartFile
291310

292311
// 줄거리 기반 그림 생성
293312
@Transactional
294-
public String generateImageFromPrompt(Long storyId, int sceneNum) {
295-
Scene scene = findMyScene(storyId, sceneNum);
313+
public String generateImageFromPrompt(Long storyId, int pageNum) {
314+
Scene scene = findMyScene(storyId, pageNum);
296315

297316
String prompt = scene.getContent();
298317
String imageUrl = storyApiClient.callFastApiForImageFromPrompt(prompt, scene.getStory().getGenre().name());
@@ -301,9 +320,9 @@ public String generateImageFromPrompt(Long storyId, int sceneNum) {
301320
return imageUrl;
302321
}
303322

304-
private Scene findMyScene(Long storyId, int sceneNum){
323+
private Scene findMyScene(Long storyId, int pageNum){
305324
Long profileId = profileHelper.getAuthenticatedProfileId();
306-
return sceneRepository.findByStoryIdAndPageAndStoryProfileId(storyId, sceneNum, profileId)
325+
return sceneRepository.findByStoryIdAndPageAndStoryProfileId(storyId, pageNum, profileId)
307326
.orElseThrow(() -> new NotFoundHandler(ErrorStatus.SCENE_NOT_FOUND));
308327
}
309328
private Story findMyStory(Long storyId) {

0 commit comments

Comments
 (0)