From 20a103a159cbf9b0f9a0bf2dc5bd6235ebee57bc Mon Sep 17 00:00:00 2001 From: ji hwan Date: Sat, 20 Apr 2024 18:39:32 +0900 Subject: [PATCH 1/6] =?UTF-8?q?#18=20feat:=20post=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/controller/dto/ClubResponse.java | 9 ++- .../project/capstone/club/domain/Club.java | 5 ++ .../exception/MemberClubExceptionType.java | 2 +- .../post/controller/PostController.java | 36 ++++++++++ .../controller/dto/PostCreateRequest.java | 10 +++ .../post/controller/dto/PostResponse.java | 22 ++++++ .../project/capstone/post/domain/Post.java | 5 ++ .../capstone/post/domain/PostRepository.java | 9 +++ .../post/exception/PostException.java | 10 +++ .../post/exception/PostExceptionType.java | 33 +++++++++ .../capstone/post/service/PostService.java | 71 +++++++++++++++++++ backend/src/main/resources/data.sql | 5 ++ 12 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/project/capstone/post/controller/PostController.java create mode 100644 backend/src/main/java/com/project/capstone/post/controller/dto/PostCreateRequest.java create mode 100644 backend/src/main/java/com/project/capstone/post/controller/dto/PostResponse.java create mode 100644 backend/src/main/java/com/project/capstone/post/domain/PostRepository.java create mode 100644 backend/src/main/java/com/project/capstone/post/exception/PostException.java create mode 100644 backend/src/main/java/com/project/capstone/post/exception/PostExceptionType.java create mode 100644 backend/src/main/java/com/project/capstone/post/service/PostService.java diff --git a/backend/src/main/java/com/project/capstone/club/controller/dto/ClubResponse.java b/backend/src/main/java/com/project/capstone/club/controller/dto/ClubResponse.java index c47b916330..204b647f92 100644 --- a/backend/src/main/java/com/project/capstone/club/controller/dto/ClubResponse.java +++ b/backend/src/main/java/com/project/capstone/club/controller/dto/ClubResponse.java @@ -1,9 +1,12 @@ package com.project.capstone.club.controller.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.project.capstone.club.domain.Club; import com.project.capstone.club.domain.PublicStatus; +import com.project.capstone.post.domain.Post; import java.time.LocalDateTime; +import java.util.List; public record ClubResponse ( Long id, @@ -12,9 +15,11 @@ public record ClubResponse ( String name, LocalDateTime createdAt, int maximum, - PublicStatus publicstatus + PublicStatus publicstatus, + List posts + ) { public ClubResponse(Club club) { - this(club.getId(), club.getBook() == null ? null : club.getBook().getId(), club.getTopic(), club.getName(), club.getCreatedAt(), club.getMaximum(), club.getPublicStatus()); + this(club.getId(), club.getBook() == null ? null : club.getBook().getId(), club.getTopic(), club.getName(), club.getCreatedAt(), club.getMaximum(), club.getPublicStatus(), club.getPosts()); } } \ No newline at end of file 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 000cb159ed..fc63b9b0d6 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 @@ -1,5 +1,7 @@ package com.project.capstone.club.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; import com.project.capstone.book.domain.Book; import com.project.capstone.memberclub.domain.MemberClub; import com.project.capstone.content.domain.Content; @@ -40,12 +42,15 @@ public class Club { private PublicStatus publicStatus; private Integer password; + @JsonManagedReference @OneToMany(mappedBy = "club") private List posts = new ArrayList<>(); + @JsonManagedReference @OneToMany(mappedBy = "club") private List members = new ArrayList<>(); + @JsonManagedReference @OneToMany(mappedBy = "club") private List contents = new ArrayList<>(); diff --git a/backend/src/main/java/com/project/capstone/memberclub/exception/MemberClubExceptionType.java b/backend/src/main/java/com/project/capstone/memberclub/exception/MemberClubExceptionType.java index 147aa022ce..9c2feb635d 100644 --- a/backend/src/main/java/com/project/capstone/memberclub/exception/MemberClubExceptionType.java +++ b/backend/src/main/java/com/project/capstone/memberclub/exception/MemberClubExceptionType.java @@ -10,7 +10,7 @@ @AllArgsConstructor public enum MemberClubExceptionType implements ExceptionType { ALREADY_JOIN(BAD_REQUEST, 301, "이미 가입한 모임입니다."), - MEMBERCLUB_NOT_FOUND(NOT_FOUND, 302, "위임 또는 추방하려는 멤버가 모임 구성원이 아닙니다."); + MEMBERCLUB_NOT_FOUND(NOT_FOUND, 302, "모임에서 멤버를 찾을 수 없습니다."); ; private final HttpStatus status; diff --git a/backend/src/main/java/com/project/capstone/post/controller/PostController.java b/backend/src/main/java/com/project/capstone/post/controller/PostController.java new file mode 100644 index 0000000000..ece40121e5 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/controller/PostController.java @@ -0,0 +1,36 @@ +package com.project.capstone.post.controller; + +import com.project.capstone.auth.domain.PrincipalDetails; +import com.project.capstone.post.controller.dto.PostCreateRequest; +import com.project.capstone.post.controller.dto.PostResponse; +import com.project.capstone.post.service.PostService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/post") +public class PostController { + + private final PostService postService; + + // 게시글 작성하기 + @PostMapping("/create") + public ResponseEntity createPost(@AuthenticationPrincipal PrincipalDetails details, + @RequestBody PostCreateRequest request, @RequestParam Long clubId) { + postService.createPost(details.getUserId(), request, clubId); + return ResponseEntity.ok().body("게시글 생성"); + } + + // 게시글 조회 + @GetMapping("/{postId}") + public ResponseEntity getPost(@AuthenticationPrincipal PrincipalDetails details, + @PathVariable Long postId, @RequestParam Long clubId) { + PostResponse postResponse = postService.getPost(details.getUserId(), postId, clubId); + return ResponseEntity.ok().body(postResponse); + } + + +} diff --git a/backend/src/main/java/com/project/capstone/post/controller/dto/PostCreateRequest.java b/backend/src/main/java/com/project/capstone/post/controller/dto/PostCreateRequest.java new file mode 100644 index 0000000000..8659e882bd --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/controller/dto/PostCreateRequest.java @@ -0,0 +1,10 @@ +package com.project.capstone.post.controller.dto; + +import java.util.UUID; + +public record PostCreateRequest( + String title, + String body, + boolean isSticky +) { +} diff --git a/backend/src/main/java/com/project/capstone/post/controller/dto/PostResponse.java b/backend/src/main/java/com/project/capstone/post/controller/dto/PostResponse.java new file mode 100644 index 0000000000..cf0f388231 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/controller/dto/PostResponse.java @@ -0,0 +1,22 @@ +package com.project.capstone.post.controller.dto; + +import com.project.capstone.comment.domain.Comment; +import com.project.capstone.post.domain.Post; + +import java.util.List; +import java.util.UUID; + +public record PostResponse( + Long id, + UUID memberId, + Long clubId, + String title, + String body, + boolean isSticky, + List comments + +) { + public PostResponse(Post post) { + this(post.getId(), post.getMember().getId(), post.getClub().getId(), post.getTitle(), post.getBody(), post.isSticky(), post.getComments()); + } +} diff --git a/backend/src/main/java/com/project/capstone/post/domain/Post.java b/backend/src/main/java/com/project/capstone/post/domain/Post.java index 18345c7e01..f8f21ad34e 100644 --- a/backend/src/main/java/com/project/capstone/post/domain/Post.java +++ b/backend/src/main/java/com/project/capstone/post/domain/Post.java @@ -1,5 +1,7 @@ package com.project.capstone.post.domain; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; import com.project.capstone.club.domain.Club; import com.project.capstone.comment.domain.Comment; import com.project.capstone.member.domain.Member; @@ -33,12 +35,15 @@ public class Post { @Column(name = "is_sticky") private boolean isSticky; + @JsonManagedReference @OneToMany(mappedBy = "post") private List comments = new ArrayList<>(); + @JsonBackReference @ManyToOne private Member member; + @JsonBackReference @ManyToOne private Club club; } diff --git a/backend/src/main/java/com/project/capstone/post/domain/PostRepository.java b/backend/src/main/java/com/project/capstone/post/domain/PostRepository.java new file mode 100644 index 0000000000..e0dada89b9 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/domain/PostRepository.java @@ -0,0 +1,9 @@ +package com.project.capstone.post.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface PostRepository extends JpaRepository { + Optional findPostById(Long id); +} diff --git a/backend/src/main/java/com/project/capstone/post/exception/PostException.java b/backend/src/main/java/com/project/capstone/post/exception/PostException.java new file mode 100644 index 0000000000..4168b5751c --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/exception/PostException.java @@ -0,0 +1,10 @@ +package com.project.capstone.post.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class PostException extends BaseException { + public PostException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/post/exception/PostExceptionType.java b/backend/src/main/java/com/project/capstone/post/exception/PostExceptionType.java new file mode 100644 index 0000000000..38e2b81447 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/exception/PostExceptionType.java @@ -0,0 +1,33 @@ +package com.project.capstone.post.exception; + +import com.project.capstone.common.exception.ExceptionType; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@AllArgsConstructor +public enum PostExceptionType implements ExceptionType { + POST_NOT_FOUND(NOT_FOUND, 401, "해당 게시글을 찾을 수 없습니다.") + ; + + + 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/post/service/PostService.java b/backend/src/main/java/com/project/capstone/post/service/PostService.java new file mode 100644 index 0000000000..8492fa9002 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/post/service/PostService.java @@ -0,0 +1,71 @@ +package com.project.capstone.post.service; + +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.member.domain.Member; +import com.project.capstone.member.domain.MemberRepository; +import com.project.capstone.member.exception.MemberException; +import com.project.capstone.memberclub.domain.MemberClubRepository; +import com.project.capstone.memberclub.exception.MemberClubException; +import com.project.capstone.memberclub.exception.MemberClubExceptionType; +import com.project.capstone.post.controller.dto.PostCreateRequest; +import com.project.capstone.post.controller.dto.PostResponse; +import com.project.capstone.post.domain.Post; +import com.project.capstone.post.domain.PostRepository; +import com.project.capstone.post.exception.PostException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +import static com.project.capstone.club.exception.ClubExceptionType.*; +import static com.project.capstone.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static com.project.capstone.memberclub.exception.MemberClubExceptionType.*; +import static com.project.capstone.post.exception.PostExceptionType.POST_NOT_FOUND; + +@Service +@RequiredArgsConstructor +@Slf4j +public class PostService { + + private final PostRepository postRepository; + private final MemberRepository memberRepository; + private final ClubRepository clubRepository; + private final MemberClubRepository memberClubRepository; + + public void createPost(String memberId, PostCreateRequest request, Long clubId) { + Member member = memberRepository.findMemberById(UUID.fromString(memberId)).orElseThrow( + () -> new MemberException(MEMBER_NOT_FOUND) + ); + Club club = clubRepository.findClubById(clubId).orElseThrow( + () -> new ClubException(CLUB_NOT_FOUND) + ); + + if (memberClubRepository.findMemberClubByMember_IdAndClub_Id(UUID.fromString(memberId), clubId).isEmpty()) { + throw new MemberClubException(MEMBERCLUB_NOT_FOUND); + } + + Post saved = postRepository.save(Post.builder() + .title(request.title()) + .body(request.body()) + .isSticky(request.isSticky()) + .member(member) + .club(club) + .build()); + + member.getPosts().add(saved); + club.getPosts().add(saved); + } + + public PostResponse getPost(String memberId, Long id, Long clubId) { + Post post = postRepository.findPostById(id).orElseThrow( + () -> new PostException(POST_NOT_FOUND) + ); + if (memberClubRepository.findMemberClubByMember_IdAndClub_Id(UUID.fromString(memberId), clubId).isEmpty()) { + throw new MemberClubException(MEMBERCLUB_NOT_FOUND); + } + return new PostResponse(post); + } +} diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index cd94ec6aaf..34bb115ee7 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -41,3 +41,8 @@ insert into club (id, manager_id, topic, name, created_at, maximum, public_statu insert into club (id, manager_id, topic, name, created_at, maximum, public_status, password, book_id) values (8, UNHEX(REPLACE("8fbcec63-e527-10ee-bd3d-0242ac120002", "-","")), '사회', '길동4의 모임', '2023-05-26 15:48:57.450179', 10, 'PUBLIC', null, 3); +insert into member_club (id, member_id, club_id) values +(1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), 1); + +insert into post (id, club_id, member_id, title, body, created_at, is_sticky) values +(1, 1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), '게시글 제목 1', '게시글 내용 1', '2023-05-26 15:48:57.450179', true); From cbbf06b3b18c7d1bbeaf382d9c625062f8c23d72 Mon Sep 17 00:00:00 2001 From: ji hwan Date: Sat, 20 Apr 2024 19:27:51 +0900 Subject: [PATCH 2/6] =?UTF-8?q?#22=20feat:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C,=20=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 33 ++++++++++ .../controller/dto/CommentResponse.java | 18 ++++++ .../controller/dto/CreateCommentRequest.java | 6 ++ .../capstone/comment/domain/Comment.java | 3 + .../comment/domain/CommentRepository.java | 9 +++ .../comment/exception/CommentException.java | 10 +++ .../exception/CommentExceptionType.java | 32 ++++++++++ .../comment/service/CommentService.java | 62 +++++++++++++++++++ .../member/controller/MemberController.java | 1 - .../capstone/member/domain/Member.java | 5 ++ backend/src/main/resources/data.sql | 7 +++ 11 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/project/capstone/comment/controller/CommentController.java create mode 100644 backend/src/main/java/com/project/capstone/comment/controller/dto/CommentResponse.java create mode 100644 backend/src/main/java/com/project/capstone/comment/controller/dto/CreateCommentRequest.java create mode 100644 backend/src/main/java/com/project/capstone/comment/domain/CommentRepository.java create mode 100644 backend/src/main/java/com/project/capstone/comment/exception/CommentException.java create mode 100644 backend/src/main/java/com/project/capstone/comment/exception/CommentExceptionType.java create mode 100644 backend/src/main/java/com/project/capstone/comment/service/CommentService.java diff --git a/backend/src/main/java/com/project/capstone/comment/controller/CommentController.java b/backend/src/main/java/com/project/capstone/comment/controller/CommentController.java new file mode 100644 index 0000000000..70571da939 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/controller/CommentController.java @@ -0,0 +1,33 @@ +package com.project.capstone.comment.controller; + +import com.project.capstone.auth.domain.PrincipalDetails; +import com.project.capstone.comment.controller.dto.CommentResponse; +import com.project.capstone.comment.controller.dto.CreateCommentRequest; +import com.project.capstone.comment.service.CommentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/comment") +public class CommentController { + + private final CommentService commentService; + + @PostMapping("create") + public ResponseEntity createComment(@AuthenticationPrincipal PrincipalDetails details, + @RequestParam Long postId, @RequestBody CreateCommentRequest request) { + commentService.createPost(details.getUserId(), postId, request); + return ResponseEntity.ok().body("댓글 생성 완료"); + } + + @GetMapping("/{id}") + public ResponseEntity getComment(@AuthenticationPrincipal PrincipalDetails details, + @PathVariable Long id) { + CommentResponse commentResponse = commentService.getComment(details.getUserId(), id); + return ResponseEntity.ok().body(commentResponse); + } + +} diff --git a/backend/src/main/java/com/project/capstone/comment/controller/dto/CommentResponse.java b/backend/src/main/java/com/project/capstone/comment/controller/dto/CommentResponse.java new file mode 100644 index 0000000000..fb8d6d3e19 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/controller/dto/CommentResponse.java @@ -0,0 +1,18 @@ +package com.project.capstone.comment.controller.dto; + +import com.project.capstone.comment.domain.Comment; + +import java.time.LocalDateTime; +import java.util.UUID; + +public record CommentResponse( + Long id, + Long postId, + UUID memberId, + String body, + LocalDateTime createdAt +) { + public CommentResponse(Comment comment) { + this(comment.getId(), comment.getPost().getId(), comment.getMember().getId(), comment.getBody(), comment.getCreatedAt()); + } +} diff --git a/backend/src/main/java/com/project/capstone/comment/controller/dto/CreateCommentRequest.java b/backend/src/main/java/com/project/capstone/comment/controller/dto/CreateCommentRequest.java new file mode 100644 index 0000000000..6dadaa4ca5 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/controller/dto/CreateCommentRequest.java @@ -0,0 +1,6 @@ +package com.project.capstone.comment.controller.dto; + +public record CreateCommentRequest ( + String body +) { +} diff --git a/backend/src/main/java/com/project/capstone/comment/domain/Comment.java b/backend/src/main/java/com/project/capstone/comment/domain/Comment.java index 26b1884f41..0ccb661e8c 100644 --- a/backend/src/main/java/com/project/capstone/comment/domain/Comment.java +++ b/backend/src/main/java/com/project/capstone/comment/domain/Comment.java @@ -1,5 +1,6 @@ package com.project.capstone.comment.domain; +import com.fasterxml.jackson.annotation.JsonBackReference; import com.project.capstone.member.domain.Member; import com.project.capstone.post.domain.Post; import jakarta.persistence.*; @@ -27,9 +28,11 @@ public class Comment { @Column(name = "created_at") private LocalDateTime createdAt; + @JsonBackReference @ManyToOne private Member member; + @JsonBackReference @ManyToOne private Post post; } diff --git a/backend/src/main/java/com/project/capstone/comment/domain/CommentRepository.java b/backend/src/main/java/com/project/capstone/comment/domain/CommentRepository.java new file mode 100644 index 0000000000..a5e39f660c --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/domain/CommentRepository.java @@ -0,0 +1,9 @@ +package com.project.capstone.comment.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CommentRepository extends JpaRepository { + Optional findCommentById(Long id); +} diff --git a/backend/src/main/java/com/project/capstone/comment/exception/CommentException.java b/backend/src/main/java/com/project/capstone/comment/exception/CommentException.java new file mode 100644 index 0000000000..3803fe7794 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/exception/CommentException.java @@ -0,0 +1,10 @@ +package com.project.capstone.comment.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class CommentException extends BaseException { + public CommentException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/comment/exception/CommentExceptionType.java b/backend/src/main/java/com/project/capstone/comment/exception/CommentExceptionType.java new file mode 100644 index 0000000000..c31291e063 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/exception/CommentExceptionType.java @@ -0,0 +1,32 @@ +package com.project.capstone.comment.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 CommentExceptionType implements ExceptionType { + COMMENT_NOT_FOUND(NOT_FOUND, 501, "해당 댓글을 찾을 수 없습니다."); + ; + + 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/comment/service/CommentService.java b/backend/src/main/java/com/project/capstone/comment/service/CommentService.java new file mode 100644 index 0000000000..5e5fa7c47f --- /dev/null +++ b/backend/src/main/java/com/project/capstone/comment/service/CommentService.java @@ -0,0 +1,62 @@ +package com.project.capstone.comment.service; + +import com.project.capstone.comment.controller.dto.CommentResponse; +import com.project.capstone.comment.controller.dto.CreateCommentRequest; +import com.project.capstone.comment.domain.Comment; +import com.project.capstone.comment.domain.CommentRepository; +import com.project.capstone.comment.exception.CommentException; +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.post.domain.Post; +import com.project.capstone.post.domain.PostRepository; +import com.project.capstone.post.exception.PostException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +import static com.project.capstone.comment.exception.CommentExceptionType.COMMENT_NOT_FOUND; +import static com.project.capstone.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static com.project.capstone.post.exception.PostExceptionType.POST_NOT_FOUND; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentService { + + private final CommentRepository commentRepository; + private final MemberRepository memberRepository; + private final PostRepository postRepository; + + public void createPost(String userId, Long postId, CreateCommentRequest request) { + Member member = memberRepository.findMemberById(UUID.fromString(userId)).orElseThrow( + () -> new MemberException(MEMBER_NOT_FOUND) + ); + + Post post = postRepository.findPostById(postId).orElseThrow( + () -> new PostException(POST_NOT_FOUND) + ); + + Comment saved = commentRepository.save(Comment.builder() + .body(request.body()) + .member(member) + .post(post) + .build()); + + member.getComments().add(saved); + post.getComments().add(saved); + } + + + public CommentResponse getComment(String userId, Long id) { + if (memberRepository.findMemberById(UUID.fromString(userId)).isEmpty()) { + throw new MemberException(MEMBER_NOT_FOUND); + } + Comment comment = commentRepository.findCommentById(id).orElseThrow( + () -> new CommentException(COMMENT_NOT_FOUND) + ); + return new CommentResponse(comment); + } +} diff --git a/backend/src/main/java/com/project/capstone/member/controller/MemberController.java b/backend/src/main/java/com/project/capstone/member/controller/MemberController.java index 64397c5dd4..3f809acad5 100644 --- a/backend/src/main/java/com/project/capstone/member/controller/MemberController.java +++ b/backend/src/main/java/com/project/capstone/member/controller/MemberController.java @@ -10,7 +10,6 @@ @RequiredArgsConstructor @RequestMapping("/member") public class MemberController { - @GetMapping("/test") public ResponseEntity test() { return ResponseEntity.ok("ok"); 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 30e8b0e554..74fa70fe69 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 @@ -1,5 +1,6 @@ package com.project.capstone.member.domain; +import com.fasterxml.jackson.annotation.JsonManagedReference; import com.project.capstone.auth.controller.dto.SignupRequest; import com.project.capstone.comment.domain.Comment; import com.project.capstone.memberclub.domain.MemberClub; @@ -38,15 +39,19 @@ public class Member { @Column(name = "created_at") private LocalDateTime createdAt; + @JsonManagedReference @OneToMany(mappedBy = "member") private List clubs = new ArrayList<>(); + @JsonManagedReference @OneToMany(mappedBy = "member") private List posts = new ArrayList<>(); + @JsonManagedReference @OneToMany(mappedBy = "member") private List comments = new ArrayList<>(); + @JsonManagedReference @OneToMany(mappedBy = "member") private List contents = new ArrayList<>(); diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 34bb115ee7..e0280d343d 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -46,3 +46,10 @@ insert into member_club (id, member_id, club_id) values insert into post (id, club_id, member_id, title, body, created_at, is_sticky) values (1, 1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), '게시글 제목 1', '게시글 내용 1', '2023-05-26 15:48:57.450179', true); + +insert into comment (id, post_id, member_id, body, created_at) values +(1, 1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), "댓글 1", "2023-05-26 15:48:57.450179"); +insert into comment (id, post_id, member_id, body, created_at) values + (2, 1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), "댓글 2", "2023-05-26 15:48:57.450179"); +insert into comment (id, post_id, member_id, body, created_at) values + (3, 1, UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")), "댓글 3", "2023-05-26 15:48:57.450179"); From 53ee22be19d060bd882b7160273cfce3130c472d Mon Sep 17 00:00:00 2001 From: Jihwan Jung Date: Mon, 22 Apr 2024 21:50:06 +0900 Subject: [PATCH 3/6] =?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 4/6] =?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 5/6] =?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 6/6] =?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); + } +}