From 53ee22be19d060bd882b7160273cfce3130c472d Mon Sep 17 00:00:00 2001 From: Jihwan Jung Date: Mon, 22 Apr 2024 21:50:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?#25=20feat:=20quiz=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20rds=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capstone/content/domain/Content.java | 4 -- .../project/capstone/quiz/domain/Quiz.java | 41 +++++++++++++++++++ .../capstone/quiz/domain/QuizType.java | 27 ++++++++++++ backend/src/main/resources/application.yml | 8 ++-- 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java diff --git a/backend/src/main/java/com/project/capstone/content/domain/Content.java b/backend/src/main/java/com/project/capstone/content/domain/Content.java index fcddadaf73..d253becc2f 100644 --- a/backend/src/main/java/com/project/capstone/content/domain/Content.java +++ b/backend/src/main/java/com/project/capstone/content/domain/Content.java @@ -21,10 +21,6 @@ public class Content { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String type; - @Column(name = "quiz_type") - private String quizType; - @Column(name = "quiz_answer") - private String quizAnswer; private String title; private String body; private String likes; diff --git a/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java b/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java new file mode 100644 index 0000000000..912b2cee1d --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java @@ -0,0 +1,41 @@ +package com.project.capstone.quiz.domain; + +import com.project.capstone.book.domain.Book; +import com.project.capstone.club.domain.Club; +import com.project.capstone.member.domain.Member; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@EntityListeners(AuditingEntityListener.class) +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class Quiz { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private QuizType type; + private String description; + private String answer; + private String example1; + private String example2; + private String example3; + private String example4; + + @ManyToOne + private Member member; + + @ManyToOne + private Book book; + + @ManyToOne + private Club club; + +} diff --git a/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java b/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java new file mode 100644 index 0000000000..a484b6155c --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java @@ -0,0 +1,27 @@ +package com.project.capstone.quiz.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.project.capstone.club.domain.PublicStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum QuizType { + Multiple_Choice("객관식"), + Short_Answer("단답식"), + OX("OX") + ; + + private final String type; + + @JsonCreator + public static QuizType from(String type) { + for (QuizType quizType : QuizType.values()) { + if (quizType.getType().equals(type)) { + return quizType; + } + } + throw new RuntimeException("잘못된 퀴즈 타입 입니다."); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 96d105d2a2..98c1b8ba6c 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -1,9 +1,9 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/bm?serverTimezone=UTC&characterEncoding=UTF-8 - username: root - password: 12345678 + url: jdbc:mysql://${DB_ENDPOINT}:${DB_PORT}/${DB_NAME}?serverTimezone=UTC&characterEncoding=UTF-8 + username: ${DB_USERNAME} + password: ${DB_PASSWORD} jpa: hibernate: @@ -15,4 +15,4 @@ spring: mode: always jwt: - secret: gDpXHGuTSnwn6IkHoQE0TyrHT4qGDsbAm6L21qSbzUe8s/Nvo2JsiJyawX8fvUD6 Nh4CdIeQxAqnAzysgk+nUw== \ No newline at end of file + secret: ${JWT_SECRET} \ No newline at end of file From 593b454713604faa655b77aafb1fbff8cbd45005 Mon Sep 17 00:00:00 2001 From: Jihwan Jung Date: Thu, 25 Apr 2024 11:11:33 +0900 Subject: [PATCH 2/4] =?UTF-8?q?#25=20feat:=20content=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/capstone/book/domain/Book.java | 8 +++ .../capstone/book/domain/BookRepository.java | 9 +++ .../book/exception/BookException.java | 10 +++ .../book/exception/BookExceptionType.java | 33 +++++++++ .../capstone/club/domain/PublicStatus.java | 2 +- .../content/controller/ContentController.java | 25 +++++++ .../controller/dto/ContentCreateRequest.java | 10 +++ .../capstone/content/domain/Content.java | 7 +- .../content/domain/ContentRepository.java | 7 ++ .../capstone/content/domain/ContentType.java | 28 ++++++++ .../content/service/ContentService.java | 72 +++++++++++++++++++ 11 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/project/capstone/book/domain/BookRepository.java create mode 100644 backend/src/main/java/com/project/capstone/book/exception/BookException.java create mode 100644 backend/src/main/java/com/project/capstone/book/exception/BookExceptionType.java create mode 100644 backend/src/main/java/com/project/capstone/content/controller/ContentController.java create mode 100644 backend/src/main/java/com/project/capstone/content/controller/dto/ContentCreateRequest.java create mode 100644 backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java create mode 100644 backend/src/main/java/com/project/capstone/content/domain/ContentType.java create mode 100644 backend/src/main/java/com/project/capstone/content/service/ContentService.java diff --git a/backend/src/main/java/com/project/capstone/book/domain/Book.java b/backend/src/main/java/com/project/capstone/book/domain/Book.java index 8e92b888b0..17e849b33a 100644 --- a/backend/src/main/java/com/project/capstone/book/domain/Book.java +++ b/backend/src/main/java/com/project/capstone/book/domain/Book.java @@ -1,6 +1,8 @@ package com.project.capstone.book.domain; import com.project.capstone.club.domain.Club; +import com.project.capstone.content.domain.Content; +import com.project.capstone.quiz.domain.Quiz; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -32,4 +34,10 @@ public class Book { @OneToMany(mappedBy = "book") private List clubs = new ArrayList<>(); + + @OneToMany(mappedBy = "book") + private List contents = new ArrayList<>(); + + @OneToMany(mappedBy = "book") + private List quizzes = new ArrayList<>(); } diff --git a/backend/src/main/java/com/project/capstone/book/domain/BookRepository.java b/backend/src/main/java/com/project/capstone/book/domain/BookRepository.java new file mode 100644 index 0000000000..6da8fcbdde --- /dev/null +++ b/backend/src/main/java/com/project/capstone/book/domain/BookRepository.java @@ -0,0 +1,9 @@ +package com.project.capstone.book.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface BookRepository extends JpaRepository { + Optional findBookById(Long id); +} diff --git a/backend/src/main/java/com/project/capstone/book/exception/BookException.java b/backend/src/main/java/com/project/capstone/book/exception/BookException.java new file mode 100644 index 0000000000..85e77763b5 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/book/exception/BookException.java @@ -0,0 +1,10 @@ +package com.project.capstone.book.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class BookException extends BaseException { + public BookException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/book/exception/BookExceptionType.java b/backend/src/main/java/com/project/capstone/book/exception/BookExceptionType.java new file mode 100644 index 0000000000..545afef3a0 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/book/exception/BookExceptionType.java @@ -0,0 +1,33 @@ +package com.project.capstone.book.exception; + +import com.project.capstone.common.exception.ExceptionType; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@AllArgsConstructor +public enum BookExceptionType implements ExceptionType { + + BOOK_NOT_FOUND(NOT_FOUND, 801, "해당 책을 찾을 수 없습니다.") + ; + + private final HttpStatus status; + private final int exceptionCode; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public int exceptionCode() { + return exceptionCode; + } + + @Override + public String message() { + return message; + } +} diff --git a/backend/src/main/java/com/project/capstone/club/domain/PublicStatus.java b/backend/src/main/java/com/project/capstone/club/domain/PublicStatus.java index 4e47ba4bf5..037b091cae 100644 --- a/backend/src/main/java/com/project/capstone/club/domain/PublicStatus.java +++ b/backend/src/main/java/com/project/capstone/club/domain/PublicStatus.java @@ -13,7 +13,7 @@ public enum PublicStatus { private final String description; - @JsonCreator + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static PublicStatus from(String status) { for (PublicStatus publicStatus : PublicStatus.values()) { if (publicStatus.getDescription().equals(status)) { diff --git a/backend/src/main/java/com/project/capstone/content/controller/ContentController.java b/backend/src/main/java/com/project/capstone/content/controller/ContentController.java new file mode 100644 index 0000000000..615f658e6a --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/controller/ContentController.java @@ -0,0 +1,25 @@ +package com.project.capstone.content.controller; + +import com.project.capstone.auth.domain.PrincipalDetails; +import com.project.capstone.content.controller.dto.ContentCreateRequest; +import com.project.capstone.content.service.ContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/content") +public class ContentController { + + private final ContentService contentService; + + @PostMapping("/create") + public ResponseEntity createContent(@AuthenticationPrincipal PrincipalDetails details, + @RequestBody ContentCreateRequest request, + @RequestParam Long bookId, @RequestParam(required = false) Long clubId) { + contentService.createContent(details.getUserId(), request, bookId, clubId); + return ResponseEntity.ok().body("컨텐츠 생성 완료"); + } +} diff --git a/backend/src/main/java/com/project/capstone/content/controller/dto/ContentCreateRequest.java b/backend/src/main/java/com/project/capstone/content/controller/dto/ContentCreateRequest.java new file mode 100644 index 0000000000..f35b74de83 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/controller/dto/ContentCreateRequest.java @@ -0,0 +1,10 @@ +package com.project.capstone.content.controller.dto; + +import com.project.capstone.content.domain.ContentType; + +public record ContentCreateRequest( + ContentType contentType, + String title, + String body +) { +} diff --git a/backend/src/main/java/com/project/capstone/content/domain/Content.java b/backend/src/main/java/com/project/capstone/content/domain/Content.java index d253becc2f..80ce953717 100644 --- a/backend/src/main/java/com/project/capstone/content/domain/Content.java +++ b/backend/src/main/java/com/project/capstone/content/domain/Content.java @@ -20,10 +20,13 @@ public class Content { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String type; + + @Enumerated(EnumType.STRING) + @Column(name = "content_type") + private ContentType type; private String title; private String body; - private String likes; + private int likes; @ManyToOne private Member member; diff --git a/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java b/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java new file mode 100644 index 0000000000..e9e3bb3baf --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java @@ -0,0 +1,7 @@ +package com.project.capstone.content.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContentRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/com/project/capstone/content/domain/ContentType.java b/backend/src/main/java/com/project/capstone/content/domain/ContentType.java new file mode 100644 index 0000000000..52faacdcc4 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/domain/ContentType.java @@ -0,0 +1,28 @@ +package com.project.capstone.content.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@AllArgsConstructor +@Getter +@Slf4j +public enum ContentType { + + Review("독후감"), + Quotation("인용구"), + ShortReview("한줄평"); + + private final String type; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ContentType from(String type) { + for (ContentType contentType : ContentType.values()) { + if (contentType.getType().equals(type)) { + return contentType; + } + } + throw new RuntimeException("잘못된 컨텐츠 타입 입니다."); + } +} diff --git a/backend/src/main/java/com/project/capstone/content/service/ContentService.java b/backend/src/main/java/com/project/capstone/content/service/ContentService.java new file mode 100644 index 0000000000..3bd7c76703 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/service/ContentService.java @@ -0,0 +1,72 @@ +package com.project.capstone.content.service; + +import com.project.capstone.book.domain.Book; +import com.project.capstone.book.domain.BookRepository; +import com.project.capstone.book.exception.BookException; +import com.project.capstone.book.exception.BookExceptionType; +import com.project.capstone.club.domain.Club; +import com.project.capstone.club.domain.ClubRepository; +import com.project.capstone.club.exception.ClubException; +import com.project.capstone.club.exception.ClubExceptionType; +import com.project.capstone.content.controller.dto.ContentCreateRequest; +import com.project.capstone.content.domain.Content; +import com.project.capstone.content.domain.ContentRepository; +import com.project.capstone.member.domain.Member; +import com.project.capstone.member.domain.MemberRepository; +import com.project.capstone.member.exception.MemberException; +import com.project.capstone.member.exception.MemberExceptionType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +import static com.project.capstone.book.exception.BookExceptionType.BOOK_NOT_FOUND; +import static com.project.capstone.club.exception.ClubExceptionType.CLUB_NOT_FOUND; +import static com.project.capstone.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ContentService { + + private final ContentRepository contentRepository; + private final BookRepository bookRepository; + private final ClubRepository clubRepository; + private final MemberRepository memberRepository; + + public void createContent(String userId, ContentCreateRequest request, Long bookId, Long clubId) { + Member member = memberRepository.findMemberById(UUID.fromString(userId)).orElseThrow( + () -> new MemberException(MEMBER_NOT_FOUND) + ); + Book book = bookRepository.findBookById(bookId).orElseThrow( + () -> new BookException(BOOK_NOT_FOUND) + ); + Club club; + if (clubId == null) { + club = null; + } + else { + club = clubRepository.findClubById(clubId).orElseThrow( + () -> new ClubException(CLUB_NOT_FOUND) + ); + } + Content saved = contentRepository.save( + Content.builder() + .type(request.contentType()) + .title(request.title()) + .body(request.body()) + .likes(0) + .member(member) + .book(book) + .club(club) + .build() + ); + + member.getContents().add(saved); + book.getContents().add(saved); + if (club != null) { + club.getContents().add(saved); + } + } +} From 86820cdbdf36eea8e7517c63308d66454b52b35f Mon Sep 17 00:00:00 2001 From: Jihwan Jung Date: Thu, 25 Apr 2024 15:43:57 +0900 Subject: [PATCH 3/4] =?UTF-8?q?#25=20feat:=20content=EB=8B=A8=EA=B1=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C,=20=EC=A2=85=EB=A5=98=EB=B3=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/controller/ContentController.java | 18 ++++++++++ .../controller/dto/ContentResponse.java | 22 ++++++++++++ .../capstone/content/domain/Content.java | 4 +++ .../content/domain/ContentRepository.java | 6 +++- .../content/exception/ContentException.java | 10 ++++++ .../exception/ContentExceptionType.java | 34 +++++++++++++++++++ .../content/service/ContentService.java | 29 ++++++++++++++++ 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/project/capstone/content/controller/dto/ContentResponse.java create mode 100644 backend/src/main/java/com/project/capstone/content/exception/ContentException.java create mode 100644 backend/src/main/java/com/project/capstone/content/exception/ContentExceptionType.java diff --git a/backend/src/main/java/com/project/capstone/content/controller/ContentController.java b/backend/src/main/java/com/project/capstone/content/controller/ContentController.java index 615f658e6a..92b6c3b014 100644 --- a/backend/src/main/java/com/project/capstone/content/controller/ContentController.java +++ b/backend/src/main/java/com/project/capstone/content/controller/ContentController.java @@ -2,12 +2,15 @@ import com.project.capstone.auth.domain.PrincipalDetails; import com.project.capstone.content.controller.dto.ContentCreateRequest; +import com.project.capstone.content.controller.dto.ContentResponse; import com.project.capstone.content.service.ContentService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/content") @@ -15,6 +18,7 @@ public class ContentController { private final ContentService contentService; + // 컨텐츠 생성 @PostMapping("/create") public ResponseEntity createContent(@AuthenticationPrincipal PrincipalDetails details, @RequestBody ContentCreateRequest request, @@ -22,4 +26,18 @@ public ResponseEntity createContent(@AuthenticationPrincipal PrincipalDetails contentService.createContent(details.getUserId(), request, bookId, clubId); return ResponseEntity.ok().body("컨텐츠 생성 완료"); } + + // 단일 컨텐츠 조회 + @GetMapping("/{id}") + public ResponseEntity getContent(@PathVariable Long id) { + ContentResponse contentResponse = contentService.getContent(id); + return ResponseEntity.ok().body(contentResponse); + } + + // 컨텐츠 종류별 조회 + @GetMapping("/get") + public ResponseEntity> getContents(@RequestParam String type) { + List contentResponseList = contentService.getContents(type); + return ResponseEntity.ok().body(contentResponseList); + } } diff --git a/backend/src/main/java/com/project/capstone/content/controller/dto/ContentResponse.java b/backend/src/main/java/com/project/capstone/content/controller/dto/ContentResponse.java new file mode 100644 index 0000000000..13352f609e --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/controller/dto/ContentResponse.java @@ -0,0 +1,22 @@ +package com.project.capstone.content.controller.dto; + +import com.project.capstone.content.domain.Content; +import com.project.capstone.content.domain.ContentType; + +import java.util.UUID; + +public record ContentResponse( + Long id, + UUID memberId, + Long bookId, + Long clubId, + ContentType type, + String title, + String body, + int likes +) { + public ContentResponse(Content content) { + this(content.getId(), content.getMember().getId(), content.getBook().getId(), + content.getClub() == null ? null : content.getClub().getId(), content.getType(), content.getTitle(), content.getBody(), content.getLikes()); + } +} diff --git a/backend/src/main/java/com/project/capstone/content/domain/Content.java b/backend/src/main/java/com/project/capstone/content/domain/Content.java index 80ce953717..b042f06fe1 100644 --- a/backend/src/main/java/com/project/capstone/content/domain/Content.java +++ b/backend/src/main/java/com/project/capstone/content/domain/Content.java @@ -1,5 +1,6 @@ package com.project.capstone.content.domain; +import com.fasterxml.jackson.annotation.JsonBackReference; import com.project.capstone.book.domain.Book; import com.project.capstone.club.domain.Club; import com.project.capstone.member.domain.Member; @@ -28,12 +29,15 @@ public class Content { private String body; private int likes; + @JsonBackReference @ManyToOne private Member member; + @JsonBackReference @ManyToOne private Book book; + @JsonBackReference @ManyToOne private Club club; } diff --git a/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java b/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java index e9e3bb3baf..cf9c9190cc 100644 --- a/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java +++ b/backend/src/main/java/com/project/capstone/content/domain/ContentRepository.java @@ -2,6 +2,10 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface ContentRepository extends JpaRepository { +import java.util.List; +import java.util.Optional; +public interface ContentRepository extends JpaRepository { + Optional findContentById(Long id); + List findContentsByType(ContentType type); } diff --git a/backend/src/main/java/com/project/capstone/content/exception/ContentException.java b/backend/src/main/java/com/project/capstone/content/exception/ContentException.java new file mode 100644 index 0000000000..255c7a321a --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/exception/ContentException.java @@ -0,0 +1,10 @@ +package com.project.capstone.content.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class ContentException extends BaseException { + public ContentException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/content/exception/ContentExceptionType.java b/backend/src/main/java/com/project/capstone/content/exception/ContentExceptionType.java new file mode 100644 index 0000000000..ca4f3e9cae --- /dev/null +++ b/backend/src/main/java/com/project/capstone/content/exception/ContentExceptionType.java @@ -0,0 +1,34 @@ +package com.project.capstone.content.exception; + +import com.project.capstone.common.exception.ExceptionType; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@AllArgsConstructor +public enum ContentExceptionType implements ExceptionType { + CONTENT_NOT_FOUND(NOT_FOUND, 901, "해당 컨텐츠를 찾을 수 없습니다."), + TYPE_NOT_FOUND(NOT_FOUND, 902, "해당 타입을 찾을 수 없습니다.") + ; + + + private final HttpStatus status; + private final int exceptionCode; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public int exceptionCode() { + return exceptionCode; + } + + @Override + public String message() { + return message; + } +} diff --git a/backend/src/main/java/com/project/capstone/content/service/ContentService.java b/backend/src/main/java/com/project/capstone/content/service/ContentService.java index 3bd7c76703..3efb4238b3 100644 --- a/backend/src/main/java/com/project/capstone/content/service/ContentService.java +++ b/backend/src/main/java/com/project/capstone/content/service/ContentService.java @@ -9,8 +9,12 @@ import com.project.capstone.club.exception.ClubException; import com.project.capstone.club.exception.ClubExceptionType; import com.project.capstone.content.controller.dto.ContentCreateRequest; +import com.project.capstone.content.controller.dto.ContentResponse; import com.project.capstone.content.domain.Content; import com.project.capstone.content.domain.ContentRepository; +import com.project.capstone.content.domain.ContentType; +import com.project.capstone.content.exception.ContentException; +import com.project.capstone.content.exception.ContentExceptionType; import com.project.capstone.member.domain.Member; import com.project.capstone.member.domain.MemberRepository; import com.project.capstone.member.exception.MemberException; @@ -19,10 +23,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import static com.project.capstone.book.exception.BookExceptionType.BOOK_NOT_FOUND; import static com.project.capstone.club.exception.ClubExceptionType.CLUB_NOT_FOUND; +import static com.project.capstone.content.exception.ContentExceptionType.CONTENT_NOT_FOUND; +import static com.project.capstone.content.exception.ContentExceptionType.TYPE_NOT_FOUND; import static com.project.capstone.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; @Service @@ -69,4 +78,24 @@ public void createContent(String userId, ContentCreateRequest request, Long book club.getContents().add(saved); } } + + public ContentResponse getContent(Long id) { + Content content = contentRepository.findContentById(id).orElseThrow( + () -> new ContentException(CONTENT_NOT_FOUND) + ); + + return new ContentResponse(content); + } + + public List getContents(String type) { + for (ContentType contentType : ContentType.values()) { + if (contentType.equals(ContentType.valueOf(type))) { + List contentsByType = contentRepository.findContentsByType(ContentType.valueOf(type)); + return contentsByType.stream() + .map(ContentResponse::new) + .toList(); + } + } + throw new ContentException(TYPE_NOT_FOUND); + } } From 8f0472ff414fc5637c7296dce6ca33a158d5b37b Mon Sep 17 00:00:00 2001 From: Jihwan Jung Date: Thu, 25 Apr 2024 16:21:02 +0900 Subject: [PATCH 4/4] =?UTF-8?q?#25=20feat:=20quiz=20=EC=83=9D=EC=84=B1,=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/capstone/club/domain/Club.java | 5 ++ .../capstone/member/domain/Member.java | 7 +- .../quiz/controller/QuizController.java | 34 +++++++ .../controller/dto/CreateQuizRequest.java | 14 +++ .../quiz/controller/dto/QuizResponse.java | 25 ++++++ .../project/capstone/quiz/domain/Quiz.java | 2 + .../capstone/quiz/domain/QuizRepository.java | 9 ++ .../capstone/quiz/domain/QuizType.java | 6 +- .../quiz/exception/QuizException.java | 10 +++ .../quiz/exception/QuizExceptionType.java | 32 +++++++ .../capstone/quiz/service/QuizService.java | 89 +++++++++++++++++++ 11 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/project/capstone/quiz/controller/QuizController.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/controller/dto/CreateQuizRequest.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/controller/dto/QuizResponse.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/domain/QuizRepository.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/exception/QuizException.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/exception/QuizExceptionType.java create mode 100644 backend/src/main/java/com/project/capstone/quiz/service/QuizService.java diff --git a/backend/src/main/java/com/project/capstone/club/domain/Club.java b/backend/src/main/java/com/project/capstone/club/domain/Club.java index fc63b9b0d6..deb76b3cd4 100644 --- a/backend/src/main/java/com/project/capstone/club/domain/Club.java +++ b/backend/src/main/java/com/project/capstone/club/domain/Club.java @@ -6,6 +6,7 @@ import com.project.capstone.memberclub.domain.MemberClub; import com.project.capstone.content.domain.Content; import com.project.capstone.post.domain.Post; +import com.project.capstone.quiz.domain.Quiz; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -54,6 +55,10 @@ public class Club { @OneToMany(mappedBy = "club") private List contents = new ArrayList<>(); + @JsonManagedReference + @OneToMany(mappedBy = "club") + private List quizzes = new ArrayList<>(); + @ManyToOne private Book book; diff --git a/backend/src/main/java/com/project/capstone/member/domain/Member.java b/backend/src/main/java/com/project/capstone/member/domain/Member.java index 74fa70fe69..5f926c1ed1 100644 --- a/backend/src/main/java/com/project/capstone/member/domain/Member.java +++ b/backend/src/main/java/com/project/capstone/member/domain/Member.java @@ -6,6 +6,7 @@ import com.project.capstone.memberclub.domain.MemberClub; import com.project.capstone.content.domain.Content; import com.project.capstone.post.domain.Post; +import com.project.capstone.quiz.domain.Quiz; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -55,8 +56,12 @@ public class Member { @OneToMany(mappedBy = "member") private List contents = new ArrayList<>(); + @JsonManagedReference + @OneToMany(mappedBy = "member") + private List quizzes = new ArrayList<>(); + public Member(SignupRequest request) { this(null, request.email(), request.name(), request.age(), request.gender(), null, - new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); } } diff --git a/backend/src/main/java/com/project/capstone/quiz/controller/QuizController.java b/backend/src/main/java/com/project/capstone/quiz/controller/QuizController.java new file mode 100644 index 0000000000..d8c9b0bd36 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/controller/QuizController.java @@ -0,0 +1,34 @@ +package com.project.capstone.quiz.controller; + + +import com.project.capstone.auth.domain.PrincipalDetails; +import com.project.capstone.quiz.controller.dto.CreateQuizRequest; +import com.project.capstone.quiz.controller.dto.QuizResponse; +import com.project.capstone.quiz.service.QuizService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/quiz") +public class QuizController { + private final QuizService quizService; + + // 퀴즈 생성 + @PostMapping("/create") + public ResponseEntity createQuiz(@AuthenticationPrincipal PrincipalDetails details, + @RequestBody CreateQuizRequest request, + @RequestParam Long bookId, @RequestParam(required = false) Long clubId) { + quizService.createQuiz(details.getUserId(), request, bookId, clubId); + return ResponseEntity.ok().body("퀴즈 생성 완료"); + } + + // 단건 조회 + @GetMapping("/{id}") + public ResponseEntity getQuiz(@PathVariable Long id) { + QuizResponse quizResponse = quizService.getQuiz(id); + return ResponseEntity.ok().body(quizResponse); + } +} diff --git a/backend/src/main/java/com/project/capstone/quiz/controller/dto/CreateQuizRequest.java b/backend/src/main/java/com/project/capstone/quiz/controller/dto/CreateQuizRequest.java new file mode 100644 index 0000000000..15b091bc78 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/controller/dto/CreateQuizRequest.java @@ -0,0 +1,14 @@ +package com.project.capstone.quiz.controller.dto; + +import com.project.capstone.quiz.domain.QuizType; + +public record CreateQuizRequest( + QuizType type, + String description, + String answer, + String example1, + String example2, + String example3, + String example4 +) { +} diff --git a/backend/src/main/java/com/project/capstone/quiz/controller/dto/QuizResponse.java b/backend/src/main/java/com/project/capstone/quiz/controller/dto/QuizResponse.java new file mode 100644 index 0000000000..13a7484279 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/controller/dto/QuizResponse.java @@ -0,0 +1,25 @@ +package com.project.capstone.quiz.controller.dto; + +import com.project.capstone.quiz.domain.Quiz; +import com.project.capstone.quiz.domain.QuizType; + +import java.util.UUID; + +public record QuizResponse ( + Long id, + UUID memberId, + Long bookId, + Long clubId, + QuizType type, + String description, + String answer, + String example1, + String example2, + String example3, + String example4 +) { + public QuizResponse(Quiz quiz) { + this(quiz.getId(), quiz.getMember().getId(), quiz.getBook().getId(), quiz.getClub() == null ? null : quiz.getClub().getId(), quiz.getType(), + quiz.getDescription(), quiz.getAnswer(), quiz.getExample1(), quiz.getExample2(), quiz.getExample3(), quiz.getExample4()); + } +} diff --git a/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java b/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java index 912b2cee1d..d3849b80e8 100644 --- a/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java +++ b/backend/src/main/java/com/project/capstone/quiz/domain/Quiz.java @@ -21,6 +21,8 @@ public class Quiz { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Enumerated(EnumType.STRING) private QuizType type; private String description; private String answer; diff --git a/backend/src/main/java/com/project/capstone/quiz/domain/QuizRepository.java b/backend/src/main/java/com/project/capstone/quiz/domain/QuizRepository.java new file mode 100644 index 0000000000..7d0568b8bc --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/domain/QuizRepository.java @@ -0,0 +1,9 @@ +package com.project.capstone.quiz.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface QuizRepository extends JpaRepository { + Optional findQuizById(Long id); +} diff --git a/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java b/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java index a484b6155c..3b7ea7f7d6 100644 --- a/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java +++ b/backend/src/main/java/com/project/capstone/quiz/domain/QuizType.java @@ -8,14 +8,14 @@ @AllArgsConstructor @Getter public enum QuizType { - Multiple_Choice("객관식"), - Short_Answer("단답식"), + MultipleChoice("객관식"), + ShortAnswer("단답식"), OX("OX") ; private final String type; - @JsonCreator + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static QuizType from(String type) { for (QuizType quizType : QuizType.values()) { if (quizType.getType().equals(type)) { diff --git a/backend/src/main/java/com/project/capstone/quiz/exception/QuizException.java b/backend/src/main/java/com/project/capstone/quiz/exception/QuizException.java new file mode 100644 index 0000000000..c5fc2a5d13 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/exception/QuizException.java @@ -0,0 +1,10 @@ +package com.project.capstone.quiz.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class QuizException extends BaseException { + public QuizException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/quiz/exception/QuizExceptionType.java b/backend/src/main/java/com/project/capstone/quiz/exception/QuizExceptionType.java new file mode 100644 index 0000000000..96b7fd32b2 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/exception/QuizExceptionType.java @@ -0,0 +1,32 @@ +package com.project.capstone.quiz.exception; + +import com.project.capstone.common.exception.ExceptionType; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@AllArgsConstructor +public enum QuizExceptionType implements ExceptionType { + QUIZ_NOT_FOUND(NOT_FOUND, 701, "해당 퀴즈를 찾을 수 없습니다.") + ; + + private final HttpStatus status; + private final int exceptionCode; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public int exceptionCode() { + return exceptionCode; + } + + @Override + public String message() { + return message; + } +} diff --git a/backend/src/main/java/com/project/capstone/quiz/service/QuizService.java b/backend/src/main/java/com/project/capstone/quiz/service/QuizService.java new file mode 100644 index 0000000000..4579b9d9cd --- /dev/null +++ b/backend/src/main/java/com/project/capstone/quiz/service/QuizService.java @@ -0,0 +1,89 @@ +package com.project.capstone.quiz.service; + +import com.project.capstone.book.domain.Book; +import com.project.capstone.book.domain.BookRepository; +import com.project.capstone.book.exception.BookException; +import com.project.capstone.book.exception.BookExceptionType; +import com.project.capstone.club.domain.Club; +import com.project.capstone.club.domain.ClubRepository; +import com.project.capstone.club.exception.ClubException; +import com.project.capstone.club.exception.ClubExceptionType; +import com.project.capstone.member.domain.Member; +import com.project.capstone.member.domain.MemberRepository; +import com.project.capstone.member.exception.MemberException; +import com.project.capstone.member.exception.MemberExceptionType; +import com.project.capstone.quiz.controller.dto.CreateQuizRequest; +import com.project.capstone.quiz.controller.dto.QuizResponse; +import com.project.capstone.quiz.domain.Quiz; +import com.project.capstone.quiz.domain.QuizRepository; +import com.project.capstone.quiz.exception.QuizException; +import com.project.capstone.quiz.exception.QuizExceptionType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +import static com.project.capstone.book.exception.BookExceptionType.BOOK_NOT_FOUND; +import static com.project.capstone.club.exception.ClubExceptionType.CLUB_NOT_FOUND; +import static com.project.capstone.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static com.project.capstone.quiz.exception.QuizExceptionType.QUIZ_NOT_FOUND; + +@RequiredArgsConstructor +@Service +@Slf4j +public class QuizService { + + private final QuizRepository quizRepository; + private final MemberRepository memberRepository; + private final BookRepository bookRepository; + private final ClubRepository clubRepository; + + public void createQuiz(String userId, CreateQuizRequest request, Long bookId, Long clubId) { + Member member = memberRepository.findMemberById(UUID.fromString(userId)).orElseThrow( + () -> new MemberException(MEMBER_NOT_FOUND) + ); + + Book book = bookRepository.findBookById(bookId).orElseThrow( + () -> new BookException(BOOK_NOT_FOUND) + ); + Club club; + if (clubId == null) { + club = null; + } + else { + club = clubRepository.findClubById(clubId).orElseThrow( + ()-> new ClubException(CLUB_NOT_FOUND) + ); + } + + Quiz saved = quizRepository.save( + Quiz.builder() + .type(request.type()) + .description(request.description()) + .answer(request.answer()) + .example1(request.example1()) + .example2(request.example2()) + .example3(request.example3()) + .example4(request.example4()) + .member(member) + .book(book) + .club(club) + .build() + ); + + member.getQuizzes().add(saved); + book.getQuizzes().add(saved); + if (club != null) { + club.getQuizzes().add(saved); + } + } + + + public QuizResponse getQuiz(Long id) { + Quiz quiz = quizRepository.findQuizById(id).orElseThrow( + () -> new QuizException(QUIZ_NOT_FOUND) + ); + return new QuizResponse(quiz); + } +}