Skip to content

Commit a5f497e

Browse files
Merge pull request #121 from prgrms-web-devcourse-final-project/feature/EA3-143-ai-quiz
[EA3-143] Ai 퀴즈 생성 및 DB에 저장 로직
2 parents 70e6f1f + c5e2b9c commit a5f497e

30 files changed

+448
-115
lines changed

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ dependencies {
7878
// swagger
7979
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
8080

81+
// gemini
82+
implementation 'dev.langchain4j:langchain4j-spring-boot-starter:1.0.0-beta3'
83+
implementation 'dev.langchain4j:langchain4j-google-ai-gemini-spring-boot-starter:1.0.0-beta3'
84+
85+
8186
// 배포 관련 의존성
8287
// runtimeOnly 'org.postgresql:postgresql'
8388
// implementation 'org.springframework.boot:spring-boot-devtools'

src/main/java/grep/neogul_coder/domain/prtemplate/controller/PrTemplateController.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import grep.neogul_coder.domain.prtemplate.controller.dto.request.IntroductionUpdateRequest;
44
import grep.neogul_coder.domain.prtemplate.controller.dto.request.PrUpdateRequest;
55
import grep.neogul_coder.domain.prtemplate.controller.dto.response.PrPageResponse;
6+
import grep.neogul_coder.domain.prtemplate.exception.TemplateNotFoundException;
7+
import grep.neogul_coder.domain.prtemplate.exception.code.PrTemplateErrorCode;
8+
import grep.neogul_coder.domain.prtemplate.repository.PrTemplateRepository;
69
import grep.neogul_coder.domain.prtemplate.service.LinkService;
710
import grep.neogul_coder.domain.prtemplate.service.PrTemplateService;
11+
import grep.neogul_coder.global.auth.Principal;
812
import grep.neogul_coder.global.response.ApiResponse;
913
import lombok.RequiredArgsConstructor;
14+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1015
import org.springframework.web.bind.annotation.GetMapping;
1116
import org.springframework.web.bind.annotation.PathVariable;
1217
import org.springframework.web.bind.annotation.PutMapping;
@@ -16,30 +21,36 @@
1621

1722
@RestController
1823
@RequiredArgsConstructor
19-
@RequestMapping("/api/prtemplate")
24+
@RequestMapping("/api/template")
2025
public class PrTemplateController implements PrTemplateSpecification {
2126

2227
private final PrTemplateService prTemplateService;
2328
private final LinkService linkService;
2429

25-
@GetMapping("{id}")
26-
public ApiResponse<PrPageResponse> get(@PathVariable("id") Long id) {
27-
PrPageResponse prPageResponse = prTemplateService.toResponse(id);
30+
@GetMapping("/mine")
31+
public ApiResponse<PrPageResponse> get(@AuthenticationPrincipal Principal principal) {
32+
PrPageResponse prPageResponse = prTemplateService.toResponse(principal.getUserId());
2833
return ApiResponse.success(prPageResponse);
2934
}
3035

31-
@PutMapping("/{id}")
32-
public ApiResponse<Void> update(@PathVariable("id") Long id,
36+
@GetMapping("/{userid}")
37+
public ApiResponse<PrPageResponse> get(@PathVariable("userid") Long userId) {
38+
PrPageResponse prPageResponse = prTemplateService.toResponse(userId);
39+
return ApiResponse.success(prPageResponse);
40+
}
41+
42+
@PutMapping("/update/template")
43+
public ApiResponse<Void> update(@AuthenticationPrincipal Principal principal,
3344
@RequestBody PrUpdateRequest request) {
34-
prTemplateService.update(id, request.getLocation());
35-
linkService.update(id, request.getPrUrls());
45+
prTemplateService.update(principal.getUserId(), request.getLocation());
46+
linkService.update(principal.getUserId(), request.getPrUrls());
3647
return ApiResponse.noContent();
3748
}
3849

39-
@PutMapping("/introduction/{id}")
40-
public ApiResponse<Void> updateIntroduction(@PathVariable("id") Long id,
50+
@PutMapping("/update/introduction")
51+
public ApiResponse<Void> updateIntroduction(@AuthenticationPrincipal Principal principal,
4152
@RequestBody IntroductionUpdateRequest request) {
42-
prTemplateService.updateIntroduction(id, request.getIntroduction());
53+
prTemplateService.updateIntroduction(principal.getUserId(), request.getIntroduction());
4354
return ApiResponse.noContent();
4455
}
4556
}

src/main/java/grep/neogul_coder/domain/prtemplate/controller/PrTemplateSpecification.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@
44
import grep.neogul_coder.domain.prtemplate.controller.dto.request.PrUpdateRequest;
55
import grep.neogul_coder.domain.prtemplate.controller.dto.response.PrPageResponse;
66
import grep.neogul_coder.domain.prtemplate.entity.PrTemplate;
7+
import grep.neogul_coder.global.auth.Principal;
78
import grep.neogul_coder.global.response.ApiResponse;
89
import io.swagger.v3.oas.annotations.Operation;
910
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1012
import org.springframework.web.bind.annotation.PathVariable;
1113
import org.springframework.web.bind.annotation.RequestBody;
1214

1315
@Tag(name = "PR_Template", description = "PR 템플릿 API")
1416
public interface PrTemplateSpecification {
1517

1618
@Operation(summary = "PR 템플릿 조회", description = "PR 템플릿을 조회합니다.")
17-
ApiResponse<PrPageResponse> get(@PathVariable("id") Long id);
19+
ApiResponse<PrPageResponse> get(@AuthenticationPrincipal Principal principal);
1820

1921
@Operation(summary = "PR 정보 수정", description = "PR 정보를 수정합니다.")
20-
ApiResponse<Void> update(@PathVariable("id") Long id, @RequestBody PrUpdateRequest request);
22+
ApiResponse<Void> update(@AuthenticationPrincipal Principal principal, @RequestBody PrUpdateRequest request);
2123

2224
@Operation(summary = "PR 소개글 수정", description = "PR 소개글을 수정합니다.")
23-
ApiResponse<Void> updateIntroduction(@PathVariable("id") Long id, @RequestBody IntroductionUpdateRequest request);
25+
ApiResponse<Void> updateIntroduction(@AuthenticationPrincipal Principal principal, @RequestBody IntroductionUpdateRequest request);
2426

2527
}

src/main/java/grep/neogul_coder/domain/prtemplate/entity/Link.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package grep.neogul_coder.domain.prtemplate.entity;
22

3+
import grep.neogul_coder.domain.prtemplate.controller.dto.request.LinkUpdateRequest;
34
import grep.neogul_coder.global.entity.BaseEntity;
45
import jakarta.persistence.Entity;
56
import jakarta.persistence.GeneratedValue;
@@ -16,7 +17,7 @@ public class Link extends BaseEntity {
1617
@GeneratedValue(strategy = GenerationType.IDENTITY)
1718
private Long id;
1819

19-
private Long prId;
20+
private Long userId;
2021

2122
private String prUrl;
2223

@@ -25,7 +26,7 @@ public class Link extends BaseEntity {
2526
@Builder
2627
private Link(Long id, Long prId, String prUrl, String urlName, Boolean activated) {
2728
this.id = id;
28-
this.prId = prId;
29+
this.userId = prId;
2930
this.prUrl = prUrl;
3031
this.urlName = urlName;
3132
this.activated = activated;
@@ -40,10 +41,27 @@ public static Link LinkInit(Long prId, String prUrl, String urlName) {
4041
.build();
4142
}
4243

44+
public static boolean isRequestLinkEmpty(LinkUpdateRequest request) {
45+
String prUrl = request.getPrUrl();
46+
return prUrl == null || prUrl.isBlank();
47+
}
48+
49+
protected Link() {
50+
}
51+
4352
public void delete() {
4453
this.activated = false;
4554
}
4655

47-
protected Link() {
56+
public void reactivate() {
57+
this.activated = true;
58+
}
59+
60+
public void updateUrlName(String urlName) {
61+
this.urlName = urlName;
62+
}
63+
64+
public void updatePrUrl(String prUrl) {
65+
this.prUrl = prUrl;
4866
}
4967
}

src/main/java/grep/neogul_coder/domain/prtemplate/repository/LinkRepository.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
@Repository
99
public interface LinkRepository extends JpaRepository<Link, Long> {
10-
Link findByPrId(Long prId);
11-
List<Link> findAllByPrIdAndActivatedTrue(Long prId);
12-
List<Link> findAllByPrId(Long prId);
10+
Link findByUserId(Long prId);
11+
List<Link> findAllByUserIdAndActivatedTrue(Long prId);
12+
List<Link> findAllByUserId(Long userId);
1313
}

src/main/java/grep/neogul_coder/domain/prtemplate/service/LinkService.java

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,47 @@
33
import grep.neogul_coder.domain.prtemplate.controller.dto.request.LinkUpdateRequest;
44
import grep.neogul_coder.domain.prtemplate.entity.Link;
55
import grep.neogul_coder.domain.prtemplate.repository.LinkRepository;
6-
import grep.neogul_coder.domain.prtemplate.repository.PrTemplateRepository;
6+
import grep.neogul_coder.domain.quiz.exception.code.QuizErrorCode;
7+
import grep.neogul_coder.global.exception.business.BusinessException;
78
import jakarta.transaction.Transactional;
8-
import java.util.Optional;
9+
import java.util.List;
910
import lombok.RequiredArgsConstructor;
1011
import org.springframework.stereotype.Service;
1112

12-
import java.util.List;
13-
1413
@Service
1514
@RequiredArgsConstructor
1615
@Transactional
1716
public class LinkService {
1817

1918
private final LinkRepository linkRepository;
20-
private final PrTemplateRepository prTemplateRepository;
2119

22-
public void deleteByPrId(Long prId) {
23-
Optional.ofNullable(linkRepository.findByPrId(prId))
24-
.ifPresent(Link::delete);
20+
public void deleteByUserId(Long userId) {
21+
List<Link> links = linkRepository.findAllByUserId(userId);
22+
for (Link link : links) {
23+
link.delete();
24+
}
2525
}
2626

27-
public void update(Long prId, List<LinkUpdateRequest> prUrls) {
28-
List<Link> existingLinks = linkRepository.findAllByPrId(prId);
29-
for (Link link : existingLinks) {
30-
link.delete();
27+
public void update(Long userId, List<LinkUpdateRequest> requests) {
28+
List<Link> links = linkRepository.findAllByUserId(userId);
29+
30+
if (requests.size() != 2) {
31+
throw new BusinessException(QuizErrorCode.NEED_CORRECT_COUNT);
3132
}
32-
linkRepository.saveAll(existingLinks);
3333

34-
for (LinkUpdateRequest request : prUrls) {
35-
Link link = Link.builder()
36-
.prId(prId)
37-
.urlName(request.getUrlName())
38-
.prUrl(request.getPrUrl())
39-
.activated(true)
40-
.build();
34+
for (int i = 0; i < links.size(); i++) {
35+
applyRequestToLink(links.get(i), requests.get(i));
36+
}
37+
}
4138

42-
linkRepository.save(link);
39+
private void applyRequestToLink(Link link, LinkUpdateRequest request) {
40+
if (Link.isRequestLinkEmpty(request)) {
41+
link.delete();
42+
} else {
43+
link.updateUrlName(request.getUrlName());
44+
link.updatePrUrl(request.getPrUrl());
45+
link.reactivate();
4346
}
4447
}
45-
}
48+
49+
}

src/main/java/grep/neogul_coder/domain/prtemplate/service/PrTemplateService.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,26 @@ public class PrTemplateService {
3636
private final UserRepository userRepository;
3737

3838
public void deleteByUserId(Long userId) {
39-
PrTemplate prTemplate = prTemplateRepository.findByUserId(userId)
40-
.orElseThrow(() -> new TemplateNotFoundException(PrTemplateErrorCode.TEMPLATE_NOT_FOUND));
39+
PrTemplate prTemplate = getPrTemplateByUserId(userId);
4140
prTemplate.delete();
4241
}
4342

44-
public void update(Long id, String location) {
45-
PrTemplate prTemplate = prTemplateRepository.findById(id).orElseThrow(
46-
() -> new NotFoundException(PrTemplateErrorCode.TEMPLATE_NOT_FOUND));
43+
public void update(Long userId, String location) {
44+
PrTemplate prTemplate = getPrTemplateByUserId(userId);
4745
prTemplate.update(location);
4846
}
4947

50-
public void updateIntroduction(Long id, String introduction) {
51-
PrTemplate prTemplate = prTemplateRepository.findById(id).orElseThrow(
52-
() -> new NotFoundException(PrTemplateErrorCode.TEMPLATE_NOT_FOUND));
48+
public void updateIntroduction(Long userId, String introduction) {
49+
PrTemplate prTemplate = getPrTemplateByUserId(userId);
5350
prTemplate.updateIntroduction(introduction);
5451
}
5552

5653
public PrPageResponse toResponse(Long userId) {
5754

5855
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException(
5956
UserErrorCode.USER_NOT_FOUND));
60-
PrTemplate prTemplate = prTemplateRepository.findByUserId(userId)
61-
.orElseThrow(() -> new TemplateNotFoundException(PrTemplateErrorCode.TEMPLATE_NOT_FOUND));
62-
List<Link> links = linkRepository.findAllByPrIdAndActivatedTrue(prTemplate.getId());
57+
PrTemplate prTemplate = getPrTemplateByUserId(userId);
58+
List<Link> links = linkRepository.findAllByUserIdAndActivatedTrue(userId);
6359
List<ReviewEntity> reviews = reviewRepository.findAllByTargetUserId(userId);
6460

6561
List<PrPageResponse.UserProfileDto> userProfiles = List.of(
@@ -70,6 +66,7 @@ public PrPageResponse toResponse(Long userId) {
7066
);
7167

7268
List<PrPageResponse.UserLocationAndLink> userLocationAndLinks = links.stream()
69+
.filter(Link::getActivated)
7370
.map(link -> PrPageResponse.UserLocationAndLink.builder()
7471
.location(prTemplate.getLocation())
7572
.linkName(link.getUrlName())
@@ -111,10 +108,15 @@ public PrPageResponse toResponse(Long userId) {
111108
return PrPageResponse.builder()
112109
.userProfiles(userProfiles)
113110
.userLocationAndLinks(userLocationAndLinks)
114-
.buddyEnergy(50)
111+
.buddyEnergy(buddyEnergy)
115112
.reviewTags(reviewTags)
116113
.reviewContents(reviewContents)
117114
.introduction(prTemplate.getIntroduction())
118115
.build();
119116
}
117+
118+
public PrTemplate getPrTemplateByUserId(Long userId) {
119+
return prTemplateRepository.findByUserId(userId).orElseThrow(
120+
() -> new NotFoundException(PrTemplateErrorCode.TEMPLATE_NOT_FOUND));
121+
}
120122
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package grep.neogul_coder.domain.quiz.controller;
2+
3+
import grep.neogul_coder.domain.quiz.controller.dto.response.QuizResponse;
4+
import grep.neogul_coder.domain.quiz.entity.Quiz;
5+
import grep.neogul_coder.domain.quiz.exception.PostNotFreeException;
6+
import grep.neogul_coder.domain.quiz.exception.code.QuizErrorCode;
7+
import grep.neogul_coder.domain.quiz.repository.AiQuizRepository;
8+
import grep.neogul_coder.domain.quiz.service.AiQuizServiceImpl;
9+
import grep.neogul_coder.domain.studypost.Category;
10+
import grep.neogul_coder.domain.studypost.StudyPost;
11+
import grep.neogul_coder.domain.studypost.service.StudyPostService;
12+
import grep.neogul_coder.global.response.ApiResponse;
13+
import java.util.Optional;
14+
import lombok.RequiredArgsConstructor;
15+
import org.springframework.web.bind.annotation.GetMapping;
16+
import org.springframework.web.bind.annotation.PathVariable;
17+
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RestController;
19+
20+
@RestController
21+
@RequiredArgsConstructor
22+
@RequestMapping("/api/post/ai")
23+
public class AiQuizController implements AiQuizSpecification {
24+
25+
private final StudyPostService studyPostService;
26+
private final AiQuizServiceImpl aiQuizServiceImpl;
27+
private final AiQuizRepository aiQuizRepository;
28+
29+
@GetMapping("/{postId}")
30+
public ApiResponse<QuizResponse> get(@PathVariable("postId") Long postId) {
31+
32+
StudyPost post = studyPostService.findById(postId);
33+
34+
if (!Category.FREE.equals((post.getCategory()))) {
35+
throw new PostNotFreeException(QuizErrorCode.POST_NOT_FREE_ERROR);
36+
}
37+
38+
Optional<Quiz> quiz = aiQuizRepository.findByPostId(postId);
39+
if (quiz.isPresent()) {
40+
Quiz q = quiz.get();
41+
QuizResponse quizResponse = QuizResponse.toResponse(q.getQuizContent(), q.isQuizAnswer());
42+
return ApiResponse.success(quizResponse);
43+
}
44+
45+
QuizResponse quizResponse = aiQuizServiceImpl.createAndSaveQuiz(postId, post.getContent());
46+
return ApiResponse.success(quizResponse);
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package grep.neogul_coder.domain.quiz.controller;
22

3-
import grep.neogul_coder.domain.quiz.controller.dto.request.QuizRequest;
43
import grep.neogul_coder.domain.quiz.controller.dto.response.QuizResponse;
54
import grep.neogul_coder.global.response.ApiResponse;
65
import io.swagger.v3.oas.annotations.Operation;
76
import io.swagger.v3.oas.annotations.tags.Tag;
87
import org.springframework.web.bind.annotation.PathVariable;
9-
import org.springframework.web.bind.annotation.RequestBody;
108

119
@Tag(name = "Ai_Quiz", description = "Ai 퀴즈 API")
12-
public interface QuizSpecification {
10+
public interface AiQuizSpecification {
1311

14-
@Operation(summary = "Ai 퀴즈 가져오기", description = "Ai퀴즈를 가져옵니다.")
15-
ApiResponse<QuizResponse> get(@PathVariable("id") Long postId, @RequestBody QuizRequest request);
12+
@Operation(summary = "Ai 퀴즈 가져오기", description = "Ai퀴즈를 저장하고 가져옵니다.")
13+
ApiResponse<QuizResponse> get(@PathVariable("id") Long postId);
1614

1715
}

0 commit comments

Comments
 (0)