From aef7ae92044734600f2a95b0d78a7137d82024f8 Mon Sep 17 00:00:00 2001 From: vanessa Date: Mon, 15 Jul 2024 00:23:43 +0900 Subject: [PATCH] Solo project submission --- .../springboot/comment/Dto/CommentDto.java | 4 + .../comment/controller/CommentController.java | 4 + .../springboot/comment/entity/Comment.java | 40 ++++++ .../comment/repository/CommentRepository.java | 8 ++ .../comment/service/CommentService.java | 4 + .../exception/BusinessLogicException.java | 13 ++ .../springboot/exception/ExceptionCode.java | 23 ++++ .../com/springboot/member/Dto/MemberDto.java | 4 + .../member/controller/MemberController.java | 4 + .../com/springboot/member/entity/Member.java | 56 ++++++++ .../com/springboot/member/entity/Role.java | 6 + .../member/repository/MemberRepository.java | 10 ++ .../member/service/MemberService.java | 67 ++++++++++ .../springboot/question/Dto/QuestionDto.java | 4 + .../question/Dto/QuestionRequestDto.java | 24 ++++ .../controller/QuestionController.java | 24 ++++ .../springboot/question/entity/Question.java | 88 ++++++++++++ .../repository/QuestionRepository.java | 10 ++ .../question/service/QuestionService.java | 125 ++++++++++++++++++ 19 files changed, 518 insertions(+) create mode 100644 src/main/java/com/springboot/comment/Dto/CommentDto.java create mode 100644 src/main/java/com/springboot/comment/controller/CommentController.java create mode 100644 src/main/java/com/springboot/comment/entity/Comment.java create mode 100644 src/main/java/com/springboot/comment/repository/CommentRepository.java create mode 100644 src/main/java/com/springboot/comment/service/CommentService.java create mode 100644 src/main/java/com/springboot/exception/BusinessLogicException.java create mode 100644 src/main/java/com/springboot/exception/ExceptionCode.java create mode 100644 src/main/java/com/springboot/member/Dto/MemberDto.java create mode 100644 src/main/java/com/springboot/member/controller/MemberController.java create mode 100644 src/main/java/com/springboot/member/entity/Member.java create mode 100644 src/main/java/com/springboot/member/entity/Role.java create mode 100644 src/main/java/com/springboot/member/repository/MemberRepository.java create mode 100644 src/main/java/com/springboot/member/service/MemberService.java create mode 100644 src/main/java/com/springboot/question/Dto/QuestionDto.java create mode 100644 src/main/java/com/springboot/question/Dto/QuestionRequestDto.java create mode 100644 src/main/java/com/springboot/question/controller/QuestionController.java create mode 100644 src/main/java/com/springboot/question/entity/Question.java create mode 100644 src/main/java/com/springboot/question/repository/QuestionRepository.java create mode 100644 src/main/java/com/springboot/question/service/QuestionService.java diff --git a/src/main/java/com/springboot/comment/Dto/CommentDto.java b/src/main/java/com/springboot/comment/Dto/CommentDto.java new file mode 100644 index 0000000..2229b7f --- /dev/null +++ b/src/main/java/com/springboot/comment/Dto/CommentDto.java @@ -0,0 +1,4 @@ +package com.springboot.comment.Dto; + +public class CommentDto { +} diff --git a/src/main/java/com/springboot/comment/controller/CommentController.java b/src/main/java/com/springboot/comment/controller/CommentController.java new file mode 100644 index 0000000..cc6c8c9 --- /dev/null +++ b/src/main/java/com/springboot/comment/controller/CommentController.java @@ -0,0 +1,4 @@ +package com.springboot.comment.controller; + +public class CommentController { +} diff --git a/src/main/java/com/springboot/comment/entity/Comment.java b/src/main/java/com/springboot/comment/entity/Comment.java new file mode 100644 index 0000000..6f0eab2 --- /dev/null +++ b/src/main/java/com/springboot/comment/entity/Comment.java @@ -0,0 +1,40 @@ +package com.springboot.comment.entity; + +import com.springboot.member.entity.Member; +import com.springboot.question.entity.Question; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@NoArgsConstructor +@Getter +@Setter +@Entity(name = "COMMENTS") +public class Comment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long commentId; + + @Column(length = 10, nullable = false) + private String createdBy; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @Setter + @OneToOne + @JoinColumn(name = "POST_ID") + private Question question; + + @ManyToOne + @JoinColumn(name = "MEMBER_ID") + private Member member; + + public void addMember(Member member) { + this.member = member; + } +} diff --git a/src/main/java/com/springboot/comment/repository/CommentRepository.java b/src/main/java/com/springboot/comment/repository/CommentRepository.java new file mode 100644 index 0000000..685813a --- /dev/null +++ b/src/main/java/com/springboot/comment/repository/CommentRepository.java @@ -0,0 +1,8 @@ +package com.springboot.comment.repository; + +import com.springboot.comment.entity.Comment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { + +} diff --git a/src/main/java/com/springboot/comment/service/CommentService.java b/src/main/java/com/springboot/comment/service/CommentService.java new file mode 100644 index 0000000..8a94bab --- /dev/null +++ b/src/main/java/com/springboot/comment/service/CommentService.java @@ -0,0 +1,4 @@ +package com.springboot.comment.service; + +public class CommentService { +} diff --git a/src/main/java/com/springboot/exception/BusinessLogicException.java b/src/main/java/com/springboot/exception/BusinessLogicException.java new file mode 100644 index 0000000..4d025ff --- /dev/null +++ b/src/main/java/com/springboot/exception/BusinessLogicException.java @@ -0,0 +1,13 @@ +package com.springboot.exception; + +import lombok.Getter; + +public class BusinessLogicException extends RuntimeException { + @Getter + private ExceptionCode exceptionCode; + + public BusinessLogicException(ExceptionCode exceptionCode) { + super(exceptionCode.getMessage()); + this.exceptionCode = exceptionCode; + } +} diff --git a/src/main/java/com/springboot/exception/ExceptionCode.java b/src/main/java/com/springboot/exception/ExceptionCode.java new file mode 100644 index 0000000..27ca11a --- /dev/null +++ b/src/main/java/com/springboot/exception/ExceptionCode.java @@ -0,0 +1,23 @@ +package com.springboot.exception; + +import lombok.Getter; + +public enum ExceptionCode { + MEMBER_EXISTS("Member already exists"), + MEMBER_NOT_FOUND("Member not found"), + INVALID_VISIBILITY_STATUS("Choose either PUBLIC or SECRET"), + QUESTION_NOT_FOUND("Question not found"), + NO_PERMISSION_TO_VIEW("No permission to view"), + NO_PERMISSION_TO_DELETE("No permission to delete"), + NO_PERMISSION_TO_EDIT("No permission to edit"), + QUESTION_DELETED("Question deleted"), + QUESTION_ALREADY_DELETED("Question already deleted. Unable to view"), + QUESTION_CANNOT_BE_EDITED("Question already has a comment.Cannot be edited"); + + @Getter + private final String message; + + ExceptionCode(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/springboot/member/Dto/MemberDto.java b/src/main/java/com/springboot/member/Dto/MemberDto.java new file mode 100644 index 0000000..d5c9cd4 --- /dev/null +++ b/src/main/java/com/springboot/member/Dto/MemberDto.java @@ -0,0 +1,4 @@ +package com.springboot.member.Dto; + +public class MemberDto { +} diff --git a/src/main/java/com/springboot/member/controller/MemberController.java b/src/main/java/com/springboot/member/controller/MemberController.java new file mode 100644 index 0000000..a7455ee --- /dev/null +++ b/src/main/java/com/springboot/member/controller/MemberController.java @@ -0,0 +1,4 @@ +package com.springboot.member.controller; + +public class MemberController { +} diff --git a/src/main/java/com/springboot/member/entity/Member.java b/src/main/java/com/springboot/member/entity/Member.java new file mode 100644 index 0000000..eb172a9 --- /dev/null +++ b/src/main/java/com/springboot/member/entity/Member.java @@ -0,0 +1,56 @@ +package com.springboot.member.entity; + +import com.springboot.comment.entity.Comment; +import com.springboot.question.entity.Question; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@NoArgsConstructor +@Getter +@Setter +@Entity +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long memberId; + + @Column(nullable = false, updatable = false, unique = true) + private String email; + + @Column(length = 100, nullable = false, updatable = false) + private String name; + + @Column(length = 13, nullable = false, unique = true) + private String phone; + + @ElementCollection(fetch = FetchType.EAGER) + private List roles = new ArrayList<>(); + + @OneToMany(mappedBy = "member") + private List questions = new ArrayList<>(); + + @OneToMany(mappedBy = "member") + private List comments = new ArrayList<>(); + + public Member(String email) { + this.email = email; + } + + public Member(String email, String name, String phone) { + this.email = email; + this.name = name; + this.phone = phone; + } + + public void addQuestions(Question question) { + questions.add(question); + } + + +} diff --git a/src/main/java/com/springboot/member/entity/Role.java b/src/main/java/com/springboot/member/entity/Role.java new file mode 100644 index 0000000..6e342b9 --- /dev/null +++ b/src/main/java/com/springboot/member/entity/Role.java @@ -0,0 +1,6 @@ +package com.springboot.member.entity; + +public enum Role { + ROLE_USER, + ROLE_ADMIN +} diff --git a/src/main/java/com/springboot/member/repository/MemberRepository.java b/src/main/java/com/springboot/member/repository/MemberRepository.java new file mode 100644 index 0000000..3ae2c75 --- /dev/null +++ b/src/main/java/com/springboot/member/repository/MemberRepository.java @@ -0,0 +1,10 @@ +package com.springboot.member.repository; + +import com.springboot.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/com/springboot/member/service/MemberService.java b/src/main/java/com/springboot/member/service/MemberService.java new file mode 100644 index 0000000..2658ac0 --- /dev/null +++ b/src/main/java/com/springboot/member/service/MemberService.java @@ -0,0 +1,67 @@ +package com.springboot.member.service; + +import com.springboot.exception.BusinessLogicException; +import com.springboot.exception.ExceptionCode; +import com.springboot.member.entity.Member; +import com.springboot.member.repository.MemberRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +public class MemberService { + + private final MemberRepository memberRepository; + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public Member createMember(Member member) { + verifyExistsEmail(member.getEmail()); + member.setRoles(List.of("ROLE_USER")); + return memberRepository.save(member); + } + + public Member updateMember(Member member) { + Member foundMember = findVerifiedMember(member.getMemberId()); + + Optional.ofNullable(member.getName()) + .ifPresent(name -> foundMember.setName(name)); + Optional.ofNullable(member.getPhone()) + .ifPresent(phone -> foundMember.setPhone(phone)); + + return memberRepository.save(foundMember); + } + + public Member findMember(Long memberId) { + return findVerifiedMember(memberId); + } + + public Page findMembers(int page, int size) { + return memberRepository.findAll(PageRequest.of(page, size, Sort.by("memberId").descending())); + + } + + public void deleteMember(Long memberId) { + Member foundMember = findVerifiedMember(memberId); + memberRepository.delete(foundMember); + } + + private void verifyExistsEmail(String email) { + Optional member = memberRepository.findByEmail(email); + if (member.isPresent()) + throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS); + } + + @Transactional(readOnly = true) + public Member findVerifiedMember(Long memberId) { + Optional member = memberRepository.findById(memberId); + return member.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/springboot/question/Dto/QuestionDto.java b/src/main/java/com/springboot/question/Dto/QuestionDto.java new file mode 100644 index 0000000..50a92b6 --- /dev/null +++ b/src/main/java/com/springboot/question/Dto/QuestionDto.java @@ -0,0 +1,4 @@ +package com.springboot.question.Dto; + +public class QuestionDto { +} diff --git a/src/main/java/com/springboot/question/Dto/QuestionRequestDto.java b/src/main/java/com/springboot/question/Dto/QuestionRequestDto.java new file mode 100644 index 0000000..e3b7dc3 --- /dev/null +++ b/src/main/java/com/springboot/question/Dto/QuestionRequestDto.java @@ -0,0 +1,24 @@ +package com.springboot.question.Dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class QuestionRequestDto { + + public Long memberId; + public String title; + public String body; + private String visibilityStatus; + + public QuestionRequestDto() { + } + + public QuestionRequestDto(Long memberId, String title, String body, String visibilityStatus) { + this.memberId = memberId; + this.title = title; + this.body = body; + this.visibilityStatus = visibilityStatus; + } +} diff --git a/src/main/java/com/springboot/question/controller/QuestionController.java b/src/main/java/com/springboot/question/controller/QuestionController.java new file mode 100644 index 0000000..5deacd4 --- /dev/null +++ b/src/main/java/com/springboot/question/controller/QuestionController.java @@ -0,0 +1,24 @@ +package com.springboot.question.controller; + +import com.springboot.question.Dto.QuestionRequestDto; +import com.springboot.question.service.QuestionService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/questions") +public class QuestionController { + + private final QuestionService questionService; + + public QuestionController(QuestionService questionService) { + this.questionService = questionService; + } + + @PostMapping + public void createQuestion(@RequestBody QuestionRequestDto questionRequestDto) { + questionService.createQuestion(questionRequestDto.getMemberId(), questionRequestDto.getTitle(), questionRequestDto.getBody(), questionRequestDto.getVisibilityStatus()); + } +} diff --git a/src/main/java/com/springboot/question/entity/Question.java b/src/main/java/com/springboot/question/entity/Question.java new file mode 100644 index 0000000..5525ba3 --- /dev/null +++ b/src/main/java/com/springboot/question/entity/Question.java @@ -0,0 +1,88 @@ +package com.springboot.question.entity; + +import com.springboot.comment.entity.Comment; +import com.springboot.member.entity.Member; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.tomcat.jni.Local; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@NoArgsConstructor +@Getter +@Setter +@Entity +public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long questionId; + + @Column(length = 100, nullable = false) + private String title; + + @Column(length = 2000, nullable = false) + private String body; + + @Column(length = 20, nullable = false) + private String createdBy; + + @Column(nullable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(nullable = false) + private LocalDateTime updatedAt = LocalDateTime.now(); + + @Enumerated(value = EnumType.STRING) + @Column(length = 20, nullable = false) + private QuestionStatus questionStatus = QuestionStatus.QUESTION_REGISTERED; + + @Column(nullable = false) + private int viewCount; + + @Column(nullable = false) + private Boolean isSecret = false; + + @Column(nullable = false) + private Boolean isNew; + + @OneToOne(cascade = CascadeType.PERSIST) + private Comment comment; + + @ManyToOne + @JoinColumn(name = "MEMBER_ID") + private Member member; + + public void addMember(Member member) { + this.member = member; + } + + public enum QuestionStatus { + QUESTION_REGISTERED("질문 등록 상태"), + QUESTION_ANSWERED("답변 완료 상태"), + QUESTION_DELETED("질문 삭제 상태"), + QUESTION_DEACTIVATED("질문 비활성화 상태"); + + @Getter + private String status; + + QuestionStatus(String status) { + this.status = status; + } + } + + @Enumerated(EnumType.STRING) + private VisibilityStatus visibilityStatus; + + public enum VisibilityStatus { + PUBLIC, + SECRET + } + + public void addComment(Comment comment) { + this.comment = comment; + comment.setQuestion(this); + } +} diff --git a/src/main/java/com/springboot/question/repository/QuestionRepository.java b/src/main/java/com/springboot/question/repository/QuestionRepository.java new file mode 100644 index 0000000..6744f6c --- /dev/null +++ b/src/main/java/com/springboot/question/repository/QuestionRepository.java @@ -0,0 +1,10 @@ +package com.springboot.question.repository; + +import com.springboot.question.entity.Question; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface QuestionRepository extends JpaRepository { + Optional findByQuestionId(Long questionId); +} diff --git a/src/main/java/com/springboot/question/service/QuestionService.java b/src/main/java/com/springboot/question/service/QuestionService.java new file mode 100644 index 0000000..818cd73 --- /dev/null +++ b/src/main/java/com/springboot/question/service/QuestionService.java @@ -0,0 +1,125 @@ +package com.springboot.question.service; + +import com.springboot.exception.BusinessLogicException; +import com.springboot.exception.ExceptionCode; +import com.springboot.member.entity.Member; +import com.springboot.member.service.MemberService; +import com.springboot.question.entity.Question; +import com.springboot.question.repository.QuestionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +public class QuestionService { + + private final QuestionRepository questionRepository; + private final MemberService memberService; + + @Autowired + public QuestionService(QuestionRepository questionRepository, + MemberService memberService) { + this.questionRepository = questionRepository; + this.memberService = memberService; + } + + public void createQuestion(Long memberId, String title, String body, String visibilityStatus) { + Member verifiedMember = memberService.findVerifiedMember(memberId); + + Question question = new Question(); + question.setMember(verifiedMember); + question.setTitle(title); + question.setBody(body); + question.setCreatedBy(verifiedMember.getEmail()); + question.setCreatedAt(LocalDateTime.now()); + question.setQuestionStatus(Question.QuestionStatus.QUESTION_REGISTERED); + question.setIsSecret(true); + + if (visibilityStatus.equals("PUBLIC")) { + question.setVisibilityStatus(Question.VisibilityStatus.PUBLIC); + } else if (visibilityStatus.equals("SECRET")) { + question.setVisibilityStatus(Question.VisibilityStatus.SECRET); + } else { + throw new BusinessLogicException(ExceptionCode.INVALID_VISIBILITY_STATUS); + } + questionRepository.save(question); + } + + public Question updateQuestion(Long questionId, Long memberId, String newTitle, String newBody ) { + Member verifiedMember = memberService.findVerifiedMember(memberId); + Question question = findVerifiedQuestion(questionId); + + if (!question.getMember().getMemberId().equals(memberId)) { + throw new BusinessLogicException(ExceptionCode.NO_PERMISSION_TO_EDIT); + } + if (question.getQuestionStatus() == Question.QuestionStatus.QUESTION_ANSWERED) { + throw new BusinessLogicException(ExceptionCode.QUESTION_CANNOT_BE_EDITED); + } + if (newTitle != null && !newTitle.trim().isEmpty()) { + question.setTitle(newTitle); + } + if (newBody != null && !newBody.trim().isEmpty()) { + question.setBody(newBody); + } + + question.setUpdatedAt(LocalDateTime.now()); + return questionRepository.save(question); + } + + public Question findQuestion(Long questionId, Long memberId) { + Member verifiedMember = memberService.findVerifiedMember(memberId); + Question question = findVerifiedQuestion(questionId); + + if (question.getVisibilityStatus() == Question.VisibilityStatus.SECRET) { + if (!question.getCreatedBy().equals(verifiedMember.getEmail()) && !isAdmin(verifiedMember)) { + throw new BusinessLogicException(ExceptionCode.NO_PERMISSION_TO_VIEW); + } + } + + if (question.getQuestionStatus() == Question.QuestionStatus.QUESTION_DELETED) { + throw new BusinessLogicException(ExceptionCode.QUESTION_DELETED); + } + return question; + } + + public Question findQuestions(Long questionId, Long memberId) { + Member verifiedMember = memberService.findVerifiedMember(memberId); + Question question = findVerifiedQuestion(questionId); + + if (question.getQuestionStatus() == Question.QuestionStatus.QUESTION_DELETED) { + throw new BusinessLogicException(ExceptionCode.QUESTION_ALREADY_DELETED); + } + return question; + + // TODO: 각각의 질문에 대한 답변이 있으면 같이 조회 + // TODO: 페이지네이션 처리 : 최신글, 오래된글 순으로 구현 + } + + public void deleteQuestion(Long questionId, Long memberId) { + Member verifiedMember = memberService.findVerifiedMember(memberId); + Question question = findVerifiedQuestion(questionId); + + if (!question.getMember().getMemberId().equals(memberId)) { + throw new BusinessLogicException(ExceptionCode.NO_PERMISSION_TO_DELETE); + } + + if (question.getQuestionStatus() == Question.QuestionStatus.QUESTION_DELETED) { + throw new BusinessLogicException(ExceptionCode.QUESTION_ALREADY_DELETED); + } + + question.setQuestionStatus(Question.QuestionStatus.QUESTION_DELETED); + questionRepository.save(question); + } + + public Question findVerifiedQuestion(Long questionId) { + Optional optionalQuestion = questionRepository.findById(questionId); + return optionalQuestion.orElseThrow(() -> + new BusinessLogicException(ExceptionCode.QUESTION_NOT_FOUND)); + } + + private boolean isAdmin(Member member) { + return member.getRoles().contains("ROLE_ADMIN"); + } +}