Skip to content

Commit 6c08fd0

Browse files
authored
Merge pull request #49 from DropThe8bit/feat/easterEgg
[feature] easterEgg 책장 조회 API 구현
2 parents aa26439 + 220254c commit 6c08fd0

File tree

11 files changed

+270
-31
lines changed

11 files changed

+270
-31
lines changed

src/main/java/everTale/everTale_be/auth/service/AuthService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
public class AuthService {
2626

2727
private final JwtUtil jwtUtil;
28-
private final JwtProvider jwtProvider;
2928
private final TokenAuthService tokenAuthService;
3029
private final PasswordEncoder passwordEncoder;
3130
private final UserRepository userRepository;

src/main/java/everTale/everTale_be/domain/easterEgg/controller/EasterEggLetterController.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22

33
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterRequestDTO;
44
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterResponseDTO;
5+
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterStoriesResponseDto;
56
import everTale.everTale_be.domain.easterEgg.service.EasterEggLetterService;
67
import everTale.everTale_be.global.apiPayload.ApiResponse;
78
import io.swagger.v3.oas.annotations.Operation;
89
import io.swagger.v3.oas.annotations.Parameter;
910
import io.swagger.v3.oas.annotations.tags.Tag;
1011
import jakarta.validation.Valid;
1112
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Pageable;
14+
import org.springframework.data.web.PageableDefault;
1215
import org.springframework.web.bind.annotation.*;
1316

1417
@RestController
15-
@RequestMapping("/eastereggs/{storyId}/letters")
18+
@RequestMapping("/eastereggs")
1619
@Tag(name = "EasterEggLetter", description = "이스터에그 편지 관리 API")
1720
@RequiredArgsConstructor
1821
public class EasterEggLetterController {
1922

2023
private final EasterEggLetterService easterEggLetterService;
2124

2225
@Operation(summary = "이스터에그 편지 생성 API", description = "storyId를 받아 해당 스토리의 이스터에그 편지를 저장한다.")
23-
@PostMapping
26+
@PostMapping("/{storyId}/letters")
2427
public ApiResponse<String> saveEasterEggLetter(
2528
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
2629
@Valid @RequestBody EasterEggLetterRequestDTO.EasterEggLetterCreateRequestDTO request
@@ -31,7 +34,7 @@ public ApiResponse<String> saveEasterEggLetter(
3134

3235

3336
@Operation(summary = "이스터에그 편지 조회 API", description = "스토리 ID에 해당하는 이스터에그 편지 내용을 조회한다.")
34-
@GetMapping
37+
@GetMapping("/{storyId}/letters")
3538
public ApiResponse<EasterEggLetterResponseDTO> getEasterEggLetter(
3639
@Parameter(description = "스토리 ID") @PathVariable Long storyId
3740
) {
@@ -40,7 +43,7 @@ public ApiResponse<EasterEggLetterResponseDTO> getEasterEggLetter(
4043
}
4144

4245
@Operation(summary = "이스터에그 편지 수정 API", description = "스토리 ID를 기준으로 이스터에그 편지를 수정한다.")
43-
@PutMapping
46+
@PutMapping("/{storyId}/letters")
4447
public ApiResponse<String> updateEasterEggLetter(
4548
@Parameter(description = "스토리 ID") @PathVariable Long storyId,
4649
@Valid @RequestBody EasterEggLetterRequestDTO.EasterEggLetterUpdateRequestDTO request
@@ -51,12 +54,27 @@ public ApiResponse<String> updateEasterEggLetter(
5154

5255

5356
@Operation(summary = "이스터에그 편지 삭제 API", description = "스토리 ID를 기준으로 해당 스토리의 이스터에그 편지를 삭제한다.")
54-
@DeleteMapping
57+
@DeleteMapping("/{storyId}/letters")
5558
public ApiResponse<String> deleteEasterEggLetter(
5659
@Parameter(description = "스토리 ID") @PathVariable Long storyId
5760
) {
5861
easterEggLetterService.deleteEasterEggLetter(storyId);
5962
return ApiResponse.onSuccess("이스터에그 편지가 성공적으로 삭제되었습니다.");
6063
}
6164

65+
@Operation(summary = "이스터에그 편지 책장 조회", description = "선택한 아이 프로필에서 이스터에그를 만들 수 있는 스토리 리스트를 반환합니다.")
66+
@GetMapping("/letters/{profileId}/created")
67+
public ApiResponse<EasterEggLetterStoriesResponseDto> getStoriesWithEasterEggVoice(@Parameter(description = "자녀 프로필 ID") @PathVariable Long profileId,
68+
@PageableDefault(size = 4) Pageable pageable){
69+
EasterEggLetterStoriesResponseDto responseDto = easterEggLetterService.getStoriesWithEasterEggLetter(profileId, pageable);
70+
return ApiResponse.onSuccess(responseDto);
71+
}
72+
73+
@Operation(summary = "이스터에그 편지 책장 조회", description = "선택한 아이 프로필에서 이스터에그가 이미 만들어진 스토리 리스트를 반환합니다.")
74+
@GetMapping("/letters/{profileId}/creatable")
75+
public ApiResponse<EasterEggLetterStoriesResponseDto> getStoriesWithoutEasterEggVoice(@Parameter(description = "자녀 프로필 ID") @PathVariable Long profileId,
76+
@PageableDefault(size = 4) Pageable pageable){
77+
EasterEggLetterStoriesResponseDto responseDto = easterEggLetterService.getStoriesWithoutEasterEggLetter(profileId, pageable);
78+
return ApiResponse.onSuccess(responseDto);
79+
}
6280
}

src/main/java/everTale/everTale_be/domain/easterEgg/controller/EasterEggVoiceController.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.request.EasterEggVoiceRegisterRequestDto;
44
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.request.EasterEggVoiceRequestDto;
5+
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.response.EasterEggVoiceStoriesResponseDto;
56
import everTale.everTale_be.domain.easterEgg.service.EasterEggVoiceService;
67
import everTale.everTale_be.global.apiPayload.ApiResponse;
78
import io.swagger.v3.oas.annotations.Operation;
89
import io.swagger.v3.oas.annotations.Parameter;
910
import io.swagger.v3.oas.annotations.tags.Tag;
1011
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.web.PageableDefault;
1114
import org.springframework.web.bind.annotation.*;
1215
import org.springframework.web.multipart.MultipartFile;
1316

@@ -45,4 +48,20 @@ public ApiResponse<String> getEasterEggVoiceUrl(@Parameter(description = "장면
4548
}
4649
return ApiResponse.onSuccess(voiceUrl);
4750
}
51+
52+
@Operation(summary = "이스터에그 음성 책장 조회", description = "선택한 아이 프로필에서 이스터에그를 만들 수 있는 스토리 리스트를 반환합니다.")
53+
@GetMapping("/voices/{profileId}/created")
54+
public ApiResponse<EasterEggVoiceStoriesResponseDto> getStoriesWithEasterEggVoice(@Parameter(description = "자녀 프로필 ID") @PathVariable Long profileId,
55+
@PageableDefault(size = 4) Pageable pageable){
56+
EasterEggVoiceStoriesResponseDto responseDto = easterEggVoiceService.getStoriesWithEasterEggVoice(profileId, pageable);
57+
return ApiResponse.onSuccess(responseDto);
58+
}
59+
60+
@Operation(summary = "이스터에그 음성 책장 조회", description = "선택한 아이 프로필에서 이스터에그가 이미 만들어진 스토리 리스트를 반환합니다.")
61+
@GetMapping("/voices/{profileId}/creatable")
62+
public ApiResponse<EasterEggVoiceStoriesResponseDto> getStoriesWithoutEasterEggVoice(@Parameter(description = "자녀 프로필 ID") @PathVariable Long profileId,
63+
@PageableDefault(size = 4) Pageable pageable){
64+
EasterEggVoiceStoriesResponseDto responseDto = easterEggVoiceService.getStoriesWithoutEasterEggVoice(profileId, pageable);
65+
return ApiResponse.onSuccess(responseDto);
66+
}
4867
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package everTale.everTale_be.domain.easterEgg.dto.easterEggLetter;
2+
3+
import everTale.everTale_be.domain.story.dto.StoryCollectionResponseDto;
4+
import everTale.everTale_be.domain.story.entity.Story;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import org.springframework.data.domain.Page;
9+
10+
@Getter
11+
@Builder
12+
@Schema(description = "이스터에그 편지 책장 응답 DTO")
13+
public class EasterEggLetterStoriesResponseDto {
14+
15+
@Schema(description = "이스터에그 편지 책장 스토리 리스트")
16+
private StoryCollectionResponseDto easterEggVoiceStories;
17+
18+
public static EasterEggLetterStoriesResponseDto of(Page<Story> stories) {
19+
return EasterEggLetterStoriesResponseDto.builder()
20+
.easterEggVoiceStories(StoryCollectionResponseDto.from(stories))
21+
.build();
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.response;
2+
3+
import everTale.everTale_be.domain.story.dto.StoryCollectionResponseDto;
4+
import everTale.everTale_be.domain.story.entity.Story;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import org.springframework.data.domain.Page;
9+
10+
@Getter
11+
@Builder
12+
@Schema(description = "이스터에그 음성 책장 응답 DTO")
13+
public class EasterEggVoiceStoriesResponseDto {
14+
15+
@Schema(description = "이스터에그 음성 책장 스토리 리스트")
16+
private StoryCollectionResponseDto easterEggVoiceStories;
17+
18+
public static EasterEggVoiceStoriesResponseDto of(Page<Story> stories) {
19+
return EasterEggVoiceStoriesResponseDto.builder()
20+
.easterEggVoiceStories(StoryCollectionResponseDto.from(stories))
21+
.build();
22+
}
23+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package everTale.everTale_be.domain.easterEgg.repository;
2+
3+
import everTale.everTale_be.domain.easterEgg.entity.EasterEggLetter;
4+
import everTale.everTale_be.domain.story.entity.Story;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
7+
import org.springframework.data.jpa.repository.JpaRepository;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
10+
11+
public interface EasterEggLetterRepository extends JpaRepository<EasterEggLetter, Long> {
12+
13+
@Query(
14+
value = """
15+
SELECT DISTINCT s
16+
FROM Story s
17+
JOIN s.easterEggLetter el
18+
WHERE s.profile.id = :profileId
19+
""",
20+
countQuery = """
21+
SELECT COUNT(DISTINCT s.id)
22+
FROM Story s
23+
JOIN s.easterEggLetter el
24+
WHERE s.profile.id = :profileId
25+
"""
26+
)
27+
Page<Story> findStoriesWithEasterEggLetter(@Param("profileId") Long profileId, Pageable pageable);
28+
29+
@Query(
30+
value = """
31+
SELECT s
32+
FROM Story s
33+
WHERE s.profile.id = :profileId
34+
AND NOT EXISTS (
35+
SELECT 1
36+
FROM EasterEggLetter el
37+
WHERE el.story = s
38+
)
39+
""",
40+
countQuery = """
41+
SELECT COUNT(s.id)
42+
FROM Story s
43+
WHERE s.profile.id = :profileId
44+
AND NOT EXISTS (
45+
SELECT 1
46+
FROM EasterEggLetter el
47+
WHERE el.story = s
48+
)
49+
"""
50+
)
51+
Page<Story> findStoriesWithoutEasterEggLetter(@Param("profileId") Long profileId, Pageable pageable);
52+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
11
package everTale.everTale_be.domain.easterEgg.repository;
22

33
import everTale.everTale_be.domain.easterEgg.entity.EasterEggVoice;
4+
import everTale.everTale_be.domain.story.entity.Story;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
47
import org.springframework.data.jpa.repository.JpaRepository;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
510

611
import java.util.Optional;
712

813
public interface EasterEggVoiceRepository extends JpaRepository<EasterEggVoice, Long> {
914

1015
Optional<EasterEggVoice> findByScene_Id(Long sceneId);
16+
17+
@Query(
18+
value = """
19+
SELECT DISTINCT s
20+
FROM Story s
21+
JOIN s.storyScenes sc
22+
JOIN sc.easterEggVoice ev
23+
WHERE s.profile.id = :profileId
24+
""",
25+
countQuery = """
26+
SELECT COUNT(DISTINCT s.id)
27+
FROM Story s
28+
JOIN s.storyScenes sc
29+
JOIN sc.easterEggVoice ev
30+
WHERE s.profile.id = :profileId
31+
"""
32+
)
33+
Page<Story> findStoriesWithEasterEggVoice(@Param("profileId") Long profileId, Pageable pageable);
34+
35+
@Query(
36+
value = """
37+
SELECT s
38+
FROM Story s
39+
WHERE s.profile.id = :profileId
40+
AND NOT EXISTS (
41+
SELECT 1
42+
FROM Scene sc
43+
JOIN sc.easterEggVoice ev
44+
WHERE sc.story = s
45+
)
46+
""",
47+
countQuery = """
48+
SELECT COUNT(s.id)
49+
FROM Story s
50+
WHERE s.profile.id = :profileId
51+
AND NOT EXISTS (
52+
SELECT 1
53+
FROM Scene sc
54+
JOIN sc.easterEggVoice ev
55+
WHERE sc.story = s
56+
)
57+
"""
58+
)
59+
Page<Story> findStoriesWithoutEasterEggVoice(@Param("profileId") Long profileId, Pageable pageable);
1160
}

src/main/java/everTale/everTale_be/domain/easterEgg/service/EasterEggLetterService.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterRequestDTO;
44
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterResponseDTO;
5+
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterStoriesResponseDto;
56
import everTale.everTale_be.domain.easterEgg.entity.EasterEggLetter;
7+
import everTale.everTale_be.domain.easterEgg.repository.EasterEggLetterRepository;
68
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
79
import everTale.everTale_be.domain.profile.entity.Profile;
10+
import everTale.everTale_be.domain.profile.service.ProfileService;
811
import everTale.everTale_be.domain.profile.util.ProfileHelper;
912
import everTale.everTale_be.domain.story.entity.Story;
1013
import everTale.everTale_be.domain.story.repository.StoryRepository;
@@ -13,6 +16,8 @@
1316
import everTale.everTale_be.global.apiPayload.exception.handler.NotFoundHandler;
1417
import everTale.everTale_be.global.apiPayload.exception.handler.UnAuthorizedHandler;
1518
import lombok.RequiredArgsConstructor;
19+
import org.springframework.data.domain.Page;
20+
import org.springframework.data.domain.Pageable;
1621
import org.springframework.stereotype.Service;
1722
import org.springframework.transaction.annotation.Transactional;
1823

@@ -25,6 +30,8 @@ public class EasterEggLetterService {
2530

2631
private final StoryRepository storyRepository;
2732
private final ProfileHelper profileHelper;
33+
private final EasterEggLetterRepository easterEggLetterRepository;
34+
private final ProfileService profileService;
2835

2936
// 이스터에그 편지 생성
3037
@Transactional
@@ -126,6 +133,25 @@ private EasterEggLetter getExistingLetterOrThrow(Story story) {
126133
return letter;
127134
}
128135

136+
public EasterEggLetterStoriesResponseDto getStoriesWithEasterEggLetter(Long profileId, Pageable pageable){
137+
Profile profile = profileHelper.getAuthenticatedProfile();
138+
profileService.isParent(profile);
139+
profileService.validateParentProfileAccess(profile, profileId);
140+
141+
Page<Story> withLetter = easterEggLetterRepository.findStoriesWithEasterEggLetter(profileId, pageable);
142+
143+
return EasterEggLetterStoriesResponseDto.of(withLetter);
144+
}
145+
146+
public EasterEggLetterStoriesResponseDto getStoriesWithoutEasterEggLetter(Long profileId, Pageable pageable){
147+
Profile profile = profileHelper.getAuthenticatedProfile();
148+
profileService.isParent(profile);
149+
profileService.validateParentProfileAccess(profile, profileId);
150+
151+
Page<Story> withoutLetter = easterEggLetterRepository.findStoriesWithoutEasterEggLetter(profileId, pageable);
152+
153+
return EasterEggLetterStoriesResponseDto.of(withoutLetter);
154+
}
129155
}
130156

131157

0 commit comments

Comments
 (0)