diff --git a/build.gradle b/build.gradle index 02295594f..85ce29597 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,8 @@ dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' compile 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.2' runtimeOnly 'com.h2database:h2:1.4.192' + compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2' + compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2' } test { diff --git a/src/main/java/com/codessquad/qna/QnaApplication.java b/src/main/java/com/codessquad/qna/QnaApplication.java index dab708ec8..845be62b1 100644 --- a/src/main/java/com/codessquad/qna/QnaApplication.java +++ b/src/main/java/com/codessquad/qna/QnaApplication.java @@ -2,12 +2,40 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; @SpringBootApplication +@EnableJpaAuditing +@EnableSwagger2 public class QnaApplication { public static void main(String[] args) { SpringApplication.run(QnaApplication.class, args); } + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .groupName("QnaApplication") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.ant("/api/**")) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("My Qna API") + .description("My Qna API") + .version("2.0") + .build(); + } + } diff --git a/src/main/java/com/codessquad/qna/controller/AnswerController.java b/src/main/java/com/codessquad/qna/controller/AnswerController.java index 6789f9540..e6bfb1d3e 100644 --- a/src/main/java/com/codessquad/qna/controller/AnswerController.java +++ b/src/main/java/com/codessquad/qna/controller/AnswerController.java @@ -23,30 +23,30 @@ public AnswerController(AnswerService answerService) { } @PostMapping("/answer/{questionId}") - public String createAnswer(@PathVariable("questionId") Long questionId, Answer answer, HttpSession session) { + public String createAnswer(@PathVariable Long questionId, Answer answer, HttpSession session) { this.answerService.save(questionId, answer, getUserFromSession(session)); logger.info("댓글 등록 요청"); return "redirect:/question/" + questionId; } @GetMapping("/answer/{id}") - public String viewUpdateAnswer(@PathVariable("id") Long id, Model model, HttpSession session) { + public String viewUpdateAnswer(@PathVariable Long id, Model model, HttpSession session) { model.addAttribute("answer", this.answerService.verifyAnswer(id, getUserFromSession(session))); logger.info("댓글 수정 페이지 요청"); return "qna/updateAnswer"; } @PutMapping("/answer/{id}") - public String updateAnswer(@PathVariable("id") Long id, Answer answer, HttpSession session) { + public String updateAnswer(@PathVariable Long id, Answer answer, HttpSession session) { this.answerService.update(id, answer, getUserFromSession(session)); logger.info("댓글 수정 요청"); return "redirect:/question/" + this.answerService.findQuestionId(id); } @DeleteMapping("/answer/{id}") - public String deleteAnswer(@PathVariable("id") Long id, HttpSession session) { + public String deleteAnswer(@PathVariable Long id, HttpSession session) { this.answerService.delete(id, getUserFromSession(session)); - logger.info("질문 삭제 요청"); + logger.info("댓글 삭제 요청"); return "redirect:/question/" + this.answerService.findQuestionId(id); } diff --git a/src/main/java/com/codessquad/qna/controller/ApiAnswerController.java b/src/main/java/com/codessquad/qna/controller/ApiAnswerController.java new file mode 100644 index 000000000..79f4b152a --- /dev/null +++ b/src/main/java/com/codessquad/qna/controller/ApiAnswerController.java @@ -0,0 +1,38 @@ +package com.codessquad.qna.controller; + +import com.codessquad.qna.model.Answer; +import com.codessquad.qna.service.AnswerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpSession; + +import static com.codessquad.qna.controller.HttpSessionUtils.getUserFromSession; + +@RestController +public class ApiAnswerController { + + private final Logger logger = LoggerFactory.getLogger(AnswerController.class); + private final AnswerService answerService; + + public ApiAnswerController(AnswerService answerService) { + this.answerService = answerService; + } + + @PostMapping("/api/answer/{questionId}") + public Answer createAnswer(@PathVariable Long questionId, Answer answer, HttpSession session) { + logger.info("댓글 등록 요청"); + return this.answerService.save(questionId, answer, getUserFromSession(session)); + } + + @DeleteMapping("/api/answer/{id}") + public Answer deleteAnswer(@PathVariable Long id, HttpSession session) { + logger.info("댓글 삭제 요청"); + return this.answerService.delete(id, getUserFromSession(session)); + } + +} diff --git a/src/main/java/com/codessquad/qna/controller/HttpSessionUtils.java b/src/main/java/com/codessquad/qna/controller/HttpSessionUtils.java index cec160d96..62a32ce4f 100644 --- a/src/main/java/com/codessquad/qna/controller/HttpSessionUtils.java +++ b/src/main/java/com/codessquad/qna/controller/HttpSessionUtils.java @@ -1,5 +1,6 @@ package com.codessquad.qna.controller; +import com.codessquad.qna.exception.ErrorMessage; import com.codessquad.qna.exception.UserSessionException; import com.codessquad.qna.model.User; @@ -15,7 +16,7 @@ public static boolean isLoginUser(HttpSession session) { public static User getUserFromSession(HttpSession session) { if (!isLoginUser(session)) { - throw new UserSessionException(); + throw new UserSessionException(ErrorMessage.NEED_LOGIN); } return (User) session.getAttribute(USER_SESSION_KEY); } diff --git a/src/main/java/com/codessquad/qna/controller/QuestionController.java b/src/main/java/com/codessquad/qna/controller/QuestionController.java index 5cde41711..5fbacffff 100644 --- a/src/main/java/com/codessquad/qna/controller/QuestionController.java +++ b/src/main/java/com/codessquad/qna/controller/QuestionController.java @@ -45,28 +45,28 @@ public String createQuestion(Question question, HttpSession session) { } @GetMapping("/question/{id}") - public String viewQuestion(@PathVariable("id") Long id, Model model) { + public String viewQuestion(@PathVariable Long id, Model model) { model.addAttribute("question", this.questionService.findById(id)); logger.info("상세 질문 페이지 요청"); return "qna/show"; } @GetMapping("/question/{id}/form") - public String viewUpdateQuestion(@PathVariable("id") Long id, Model model, HttpSession session) { + public String viewUpdateQuestion(@PathVariable Long id, Model model, HttpSession session) { model.addAttribute("question", this.questionService.verifyQuestion(id, getUserFromSession(session))); logger.info("질문 수정 페이지 요청"); return "qna/updateForm"; } @PutMapping("/question/{id}/form") - public String updateQuestion(@PathVariable("id") Long id, Question question, HttpSession session) { + public String updateQuestion(@PathVariable Long id, Question question, HttpSession session) { this.questionService.update(id, question, getUserFromSession(session)); logger.info("질문 수정 요청"); return "redirect:/question/" + id; } @DeleteMapping("/question/{id}") - public String deleteQuestion(@PathVariable("id") Long id, HttpSession session) { + public String deleteQuestion(@PathVariable Long id, HttpSession session) { boolean result = this.questionService.delete(id, getUserFromSession(session)); logger.info("질문 삭제 요청"); return result ? "redirect:/" : "redirect:/question/" + id; diff --git a/src/main/java/com/codessquad/qna/controller/UserController.java b/src/main/java/com/codessquad/qna/controller/UserController.java index 5d8311f9b..2ac5ee7dc 100644 --- a/src/main/java/com/codessquad/qna/controller/UserController.java +++ b/src/main/java/com/codessquad/qna/controller/UserController.java @@ -2,7 +2,6 @@ import com.codessquad.qna.model.User; import com.codessquad.qna.service.UserService; -import org.omg.CORBA.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; @@ -12,7 +11,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import static com.codessquad.qna.controller.HttpSessionUtils.USER_SESSION_KEY; @@ -36,8 +34,7 @@ public String viewSingUp() { } @PostMapping("/user/form") - public String signUp(User user, HttpServletRequest request) { - request.setAttribute("path", "/user/form"); + public String signUp(User user) { this.userService.save(user); logger.info("회원가입 요청"); return "redirect:/user/list"; @@ -50,8 +47,7 @@ public String viewLogin() { } @PostMapping("/user/login") - public String login(String userId, String password, HttpServletRequest request, HttpSession session) { - request.setAttribute("path", "/user/login"); + public String login(String userId, String password, HttpSession session) { session.setAttribute(USER_SESSION_KEY, this.userService.login(userId, password)); logger.info("로그인 요청"); return "redirect:/"; @@ -72,22 +68,21 @@ public String viewUserList(Model model) { } @GetMapping("/user/{userId}/profile") - public String viewProfile(@PathVariable("userId") String userId, Model model) { + public String viewProfile(@PathVariable String userId, Model model) { model.addAttribute("user", this.userService.findByUserId(userId)); logger.info("유저 프로필 페이지 요청"); return "user/profile"; } @GetMapping("/user/{id}/form") - public String viewUpdateProfile(@PathVariable("id") Long id, Model model, HttpSession session) { + public String viewUpdateProfile(@PathVariable Long id, Model model, HttpSession session) { model.addAttribute("user", this.userService.verifyUser(id, getUserFromSession(session))); logger.info("유저 정보 수정 페이지 요청"); return "user/updateForm"; } @PutMapping("/user/{id}/form") - public String updateProfile(@PathVariable("id") Long id, User user, String oldPassword, HttpServletRequest request, HttpSession session) { - request.setAttribute("path", "/user/updateForm"); + public String updateProfile(@PathVariable Long id, User user, String oldPassword, HttpSession session) { this.userService.update(id, user, oldPassword, getUserFromSession(session)); logger.info("유저 정보 수정 요청"); return "redirect:/user/list"; diff --git a/src/main/java/com/codessquad/qna/exception/EntityNotFoundException.java b/src/main/java/com/codessquad/qna/exception/EntityNotFoundException.java new file mode 100644 index 000000000..f30ff77b8 --- /dev/null +++ b/src/main/java/com/codessquad/qna/exception/EntityNotFoundException.java @@ -0,0 +1,9 @@ +package com.codessquad.qna.exception; + +public class EntityNotFoundException extends RuntimeException { + + public EntityNotFoundException(ErrorMessage errorMessage) { + super(errorMessage.getErrorMessage()); + } + +} diff --git a/src/main/java/com/codessquad/qna/exception/ErrorMessage.java b/src/main/java/com/codessquad/qna/exception/ErrorMessage.java new file mode 100644 index 000000000..455a23e00 --- /dev/null +++ b/src/main/java/com/codessquad/qna/exception/ErrorMessage.java @@ -0,0 +1,24 @@ +package com.codessquad.qna.exception; + +public enum ErrorMessage { + + USER_NOT_FOUND("해당 유저를 찾을 수 없습니다."), + QUESTION_NOT_FOUND("해당 질문을 찾을 수 없습니다."), + ANSWER_NOT_FOUND("해당 답변을 찾을 수 없습니다."), + NEED_LOGIN("로그인이 필요한 서비스입니다."), + ILLEGAL_USER("접근 권한이 없는 유저입니다."), + DUPLICATED_ID("이미 사용중인 아이디입니다."), + LOGIN_FAILED("아이디 또는 비밀번호가 틀립니다. 다시 로그인 해주세요."), + WRONG_PASSWORD("기존 비밀번호가 일치하지 않습니다."); + + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage() { + return errorMessage; + } + +} diff --git a/src/main/java/com/codessquad/qna/exception/GlobalExceptionHandler.java b/src/main/java/com/codessquad/qna/exception/GlobalExceptionHandler.java index a93eb352d..a55199d8a 100644 --- a/src/main/java/com/codessquad/qna/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/codessquad/qna/exception/GlobalExceptionHandler.java @@ -1,23 +1,24 @@ package com.codessquad.qna.exception; +import org.springframework.http.HttpStatus; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import static com.codessquad.qna.controller.HttpSessionUtils.getUserFromSession; -import static com.codessquad.qna.controller.HttpSessionUtils.isLoginUser; @ControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(NotFoundException.class) - private String handleNotFoundException() { + @ExceptionHandler(EntityNotFoundException.class) + private String handleEntityNotFoundException() { return "redirect:/"; } + @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(UserSessionException.class) private String handleUserSessionException(Model model, UserSessionException e) { model.addAttribute("errorMessage", e.getMessage()); @@ -25,12 +26,19 @@ private String handleUserSessionException(Model model, UserSessionException e) { } @ExceptionHandler(UserAccountException.class) - private String handleUserAccountException(Model model, HttpSession session, HttpServletRequest request, UserAccountException e) { + private String handleUserAccountException(Model model, HttpSession session, UserAccountException e) { model.addAttribute("errorMessage", e.getMessage()); - if (isLoginUser(session)) { - model.addAttribute("user", getUserFromSession(session)); + switch (e.getErrorMessage()) { + case DUPLICATED_ID: + return "/user/form"; + case LOGIN_FAILED: + return "/user/login"; + case WRONG_PASSWORD: + model.addAttribute("user", getUserFromSession(session)); + return "/user/updateForm"; + default: + return "/"; } - return (String) request.getAttribute("path"); } } diff --git a/src/main/java/com/codessquad/qna/exception/NotFoundException.java b/src/main/java/com/codessquad/qna/exception/NotFoundException.java deleted file mode 100644 index a2a79b6ae..000000000 --- a/src/main/java/com/codessquad/qna/exception/NotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.codessquad.qna.exception; - -public class NotFoundException extends RuntimeException { - - public NotFoundException() { - super("해당 정보를 찾을 수 없습니다."); - } - -} diff --git a/src/main/java/com/codessquad/qna/exception/UserAccountException.java b/src/main/java/com/codessquad/qna/exception/UserAccountException.java index ef206c712..985a14c79 100644 --- a/src/main/java/com/codessquad/qna/exception/UserAccountException.java +++ b/src/main/java/com/codessquad/qna/exception/UserAccountException.java @@ -2,8 +2,15 @@ public class UserAccountException extends RuntimeException { - public UserAccountException(String errorMessage) { - super(errorMessage); + private ErrorMessage errorMessage; + + public UserAccountException(ErrorMessage errorMessage) { + super(errorMessage.getErrorMessage()); + this.errorMessage = errorMessage; + } + + public ErrorMessage getErrorMessage() { + return errorMessage; } } diff --git a/src/main/java/com/codessquad/qna/exception/UserSessionException.java b/src/main/java/com/codessquad/qna/exception/UserSessionException.java index e736efae3..7d3ff4d99 100644 --- a/src/main/java/com/codessquad/qna/exception/UserSessionException.java +++ b/src/main/java/com/codessquad/qna/exception/UserSessionException.java @@ -2,8 +2,8 @@ public class UserSessionException extends RuntimeException { - public UserSessionException() { - super("접근권한이 없는 사용자입니다."); + public UserSessionException(ErrorMessage errorMessage) { + super(errorMessage.getErrorMessage()); } } diff --git a/src/main/java/com/codessquad/qna/model/AbstractEntity.java b/src/main/java/com/codessquad/qna/model/AbstractEntity.java new file mode 100644 index 000000000..ade547d6d --- /dev/null +++ b/src/main/java/com/codessquad/qna/model/AbstractEntity.java @@ -0,0 +1,63 @@ +package com.codessquad.qna.model; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.text.SimpleDateFormat; +import java.util.Date; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class AbstractEntity { + + @Id + @GeneratedValue + private Long id; + + @CreatedDate + private Date createDate; + + @LastModifiedDate + private Date modifiedDate; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String formatDate(Date date, String format) { + return new SimpleDateFormat(format).format(date); + } + + public String getCreateDate() { + return formatDate(this.createDate, "yyyy-MM-dd HH:mm"); + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public String getModifiedDate() { + return formatDate(this.modifiedDate, "yyyy-MM-dd HH:mm"); + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + @Override + public String toString() { + return "id: " + this.id + ", " + + "createDate: " + this.createDate + ", " + + "modifiedDate: " + this.modifiedDate; + } + +} diff --git a/src/main/java/com/codessquad/qna/model/Answer.java b/src/main/java/com/codessquad/qna/model/Answer.java index 89e8fcc2a..a51731bdf 100644 --- a/src/main/java/com/codessquad/qna/model/Answer.java +++ b/src/main/java/com/codessquad/qna/model/Answer.java @@ -4,15 +4,9 @@ import org.hibernate.annotations.OnDeleteAction; import javax.persistence.*; -import java.text.SimpleDateFormat; -import java.util.Date; @Entity -public class Answer { - - @Id - @GeneratedValue - private Long id; +public class Answer extends AbstractEntity { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_to_user"), nullable = false) @@ -21,9 +15,6 @@ public class Answer { @Column(nullable = false) private String contents; - @Column(nullable = false) - private Date date; - @Column(columnDefinition = "boolean default false") private boolean deleted; @@ -32,35 +23,25 @@ public class Answer { @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_to_question"), nullable = false) private Question question; - public boolean matchWriter(User loginUser) { - return this.writer.matchId(loginUser.getId()); - } - - public Long getQuestionId() { - return this.question.getId(); - } - public void save(User writer, Question question) { this.writer = writer; - this.date = new Date(); this.question = question; } public void update(Answer answer) { - this.contents = answer.getContents(); - this.date = new Date(); + this.contents = answer.contents; } public void delete() { this.deleted = true; } - public Long getId() { - return id; + public boolean matchWriter(User loginUser) { + return this.writer.matchId(loginUser.getId()); } - public void setId(Long id) { - this.id = id; + public Long getQuestionId() { + return this.question.getId(); } public User getWriter() { @@ -79,15 +60,6 @@ public void setContents(String contents) { this.contents = contents; } - public String getDate() { - SimpleDateFormat simpleDate = new SimpleDateFormat( "yyyy-MM-dd HH:mm"); - return simpleDate.format(this.date); - } - - public void setDate(Date date) { - this.date = date; - } - public boolean isDeleted() { return deleted; } @@ -106,9 +78,9 @@ public void setQuestion(Question question) { @Override public String toString() { - return "writer: " + this.writer.getUserId() + ", " + + return super.toString() + ", " + + "writer: " + this.writer.getUserId() + ", " + "contents: " + this.contents + ", " + - "date: " + this.date + ", " + "question: " + this.question.getId() + ", "; } diff --git a/src/main/java/com/codessquad/qna/model/Question.java b/src/main/java/com/codessquad/qna/model/Question.java index 3cca6860d..259ca203b 100644 --- a/src/main/java/com/codessquad/qna/model/Question.java +++ b/src/main/java/com/codessquad/qna/model/Question.java @@ -1,17 +1,13 @@ package com.codessquad.qna.model; +import com.fasterxml.jackson.annotation.JsonIgnore; + import javax.persistence.*; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.List; import java.util.stream.Collectors; @Entity -public class Question { - - @Id - @GeneratedValue - private Long id; +public class Question extends AbstractEntity { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_to_user"), nullable = false) @@ -23,49 +19,40 @@ public class Question { @Column(nullable = false) private String contents; - @Column(nullable = false) - private Date date; - @Column(columnDefinition = "boolean default false") private boolean deleted; + @JsonIgnore @OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE) + @OrderBy("id DESC") private List answers; - public boolean matchWriter(User writer) { - return this.writer.matchId(writer.getId()); - } - - public boolean matchWriterOfAnswerList() { - Long writerId = this.writer.getId(); - long answerCount = getAnswers().stream() - .filter(answer -> !answer.getWriter().matchId(writerId)) - .count(); - return answerCount == 0; - } - public void save(User writer) { this.writer = writer; - this.date = new Date(); } public void update(Question question) { - this.title = question.getTitle(); - this.contents = question.getContents(); - this.date = new Date(); + this.title = question.title; + this.contents = question.contents; } - public void delete() { + public boolean delete() { + if (countMismatchAnswers() != 0) { + return false; + } this.deleted = true; this.answers.forEach(Answer::delete); + return true; } - public Long getId() { - return id; + public boolean matchWriter(User writer) { + return this.writer.matchId(writer.getId()); } - public void setId(Long id) { - this.id = id; + public int countMismatchAnswers() { + return (int )getAnswers().stream() + .filter(answer -> !answer.matchWriter(this.writer)) + .count(); } public User getWriter() { @@ -92,15 +79,6 @@ public void setContents(String contents) { this.contents = contents; } - public String getDate() { - SimpleDateFormat simpleDate = new SimpleDateFormat( "yyyy-MM-dd HH:mm"); - return simpleDate.format(this.date); - } - - public void setDate(Date date) { - this.date = date; - } - public boolean isDeleted() { return deleted; } @@ -121,10 +99,10 @@ public void setAnswers(List answers) { @Override public String toString() { - return "writer: " + this.writer.getUserId() + ", " + + return super.toString() + ", " + + "writer: " + this.writer.getUserId() + ", " + "title: " + this.title + ", " + - "contents: " + this.contents + ", " + - "date: " + this.date; + "contents: " + this.contents + ", "; } } diff --git a/src/main/java/com/codessquad/qna/model/User.java b/src/main/java/com/codessquad/qna/model/User.java index eda493858..ae16e1bf4 100644 --- a/src/main/java/com/codessquad/qna/model/User.java +++ b/src/main/java/com/codessquad/qna/model/User.java @@ -1,20 +1,17 @@ package com.codessquad.qna.model; +import com.fasterxml.jackson.annotation.JsonIgnore; + import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; @Entity -public class User { - - @Id - @GeneratedValue - private Long id; +public class User extends AbstractEntity { @Column(nullable = false, unique = true) private String userId; + @JsonIgnore @Column(nullable = false) private String password; @@ -24,26 +21,18 @@ public class User { @Column(nullable = false) private String email; - public boolean matchId(Long id) { - return this.id.equals(id); - } - - public boolean matchPassword(String password) { - return this.password.equals(password); - } - public void update(User user) { - this.password = user.getPassword(); - this.name = user.getName(); - this.email = user.getEmail(); + this.password = user.password; + this.name = user.name; + this.email = user.email; } - public Long getId() { - return id; + public boolean matchId(Long id) { + return this.getId().equals(id); } - public void setId(Long id) { - this.id = id; + public boolean matchPassword(String password) { + return this.password.equals(password); } public String getUserId() { @@ -80,7 +69,8 @@ public void setEmail(String email) { @Override public String toString() { - return "userId: " + this.userId + ", " + + return super.toString() + ", " + + "userId: " + this.userId + ", " + "password: " + this.password + ", " + "name: " + this.name + ", " + "email: " + this.email; diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index 3de93f49e..3674f4a71 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -1,26 +1,31 @@ package com.codessquad.qna.service; -import com.codessquad.qna.exception.NotFoundException; +import com.codessquad.qna.exception.EntityNotFoundException; +import com.codessquad.qna.exception.ErrorMessage; import com.codessquad.qna.exception.UserSessionException; import com.codessquad.qna.model.Answer; +import com.codessquad.qna.model.Question; import com.codessquad.qna.model.User; import com.codessquad.qna.repository.AnswerRepository; +import com.codessquad.qna.repository.QuestionRepository; import org.springframework.stereotype.Service; @Service public class AnswerService { private final AnswerRepository answerRepository; - private final QuestionService questionService; + private final QuestionRepository questionRepository; - public AnswerService(AnswerRepository answerRepository, QuestionService questionService) { + public AnswerService(AnswerRepository answerRepository, QuestionRepository questionRepository) { this.answerRepository = answerRepository; - this.questionService = questionService; + this.questionRepository = questionRepository; } - public void save(Long id, Answer answer, User sessionUser) { - answer.save(sessionUser, questionService.findById(id)); - this.answerRepository.save(answer); + public Answer save(Long id, Answer answer, User sessionUser) { + Question question = this.questionRepository.findByIdAndDeletedFalse(id).orElseThrow(() -> + new EntityNotFoundException(ErrorMessage.QUESTION_NOT_FOUND)); + answer.save(sessionUser, question); + return this.answerRepository.save(answer); } public void update(Long id, Answer answer, User sessionUser) { @@ -29,26 +34,27 @@ public void update(Long id, Answer answer, User sessionUser) { this.answerRepository.save(targetAnswer); } - public void delete(Long id, User sessionUser) { + public Answer delete(Long id, User sessionUser) { Answer targetAnswer = verifyAnswer(id, sessionUser); targetAnswer.delete(); - this.answerRepository.save(targetAnswer); + return this.answerRepository.save(targetAnswer); } public Answer verifyAnswer(Long id, User sessionUser) { Answer targetAnswer = findById(id); if (!targetAnswer.matchWriter(sessionUser)) { - throw new UserSessionException(); + throw new UserSessionException(ErrorMessage.ILLEGAL_USER); } return targetAnswer; } - public Answer findById(Long id) { - return this.answerRepository.findByIdAndDeletedFalse(id).orElseThrow(NotFoundException::new); - } - public Long findQuestionId(Long answerId) { return findById(answerId).getQuestionId(); } + public Answer findById(Long id) { + return this.answerRepository.findByIdAndDeletedFalse(id).orElseThrow(() -> + new EntityNotFoundException(ErrorMessage.ANSWER_NOT_FOUND)); + } + } diff --git a/src/main/java/com/codessquad/qna/service/QuestionService.java b/src/main/java/com/codessquad/qna/service/QuestionService.java index 36a25b77a..a1c244dae 100644 --- a/src/main/java/com/codessquad/qna/service/QuestionService.java +++ b/src/main/java/com/codessquad/qna/service/QuestionService.java @@ -1,6 +1,7 @@ package com.codessquad.qna.service; -import com.codessquad.qna.exception.NotFoundException; +import com.codessquad.qna.exception.EntityNotFoundException; +import com.codessquad.qna.exception.ErrorMessage; import com.codessquad.qna.exception.UserSessionException; import com.codessquad.qna.model.Question; import com.codessquad.qna.model.User; @@ -31,8 +32,8 @@ public void update(Long id, Question question, User sessionUser) { public boolean delete(Long id, User sessionUser) { Question targetQuestion = verifyQuestion(id, sessionUser); - if (targetQuestion.matchWriterOfAnswerList()) { - targetQuestion.delete(); + boolean result = targetQuestion.delete(); + if (result) { this.questionRepository.save(targetQuestion); return true; } @@ -42,7 +43,7 @@ public boolean delete(Long id, User sessionUser) { public Question verifyQuestion(Long id, User sessionUser) { Question question = findById(id); if (!question.matchWriter(sessionUser)) { - throw new UserSessionException(); + throw new UserSessionException(ErrorMessage.ILLEGAL_USER); } return question; } @@ -52,7 +53,8 @@ public List findAll() { } public Question findById(Long id) { - return this.questionRepository.findByIdAndDeletedFalse(id).orElseThrow(NotFoundException::new); + return this.questionRepository.findByIdAndDeletedFalse(id).orElseThrow(() -> + new EntityNotFoundException(ErrorMessage.QUESTION_NOT_FOUND)); } } diff --git a/src/main/java/com/codessquad/qna/service/UserService.java b/src/main/java/com/codessquad/qna/service/UserService.java index f21da191a..9c0dbe66e 100644 --- a/src/main/java/com/codessquad/qna/service/UserService.java +++ b/src/main/java/com/codessquad/qna/service/UserService.java @@ -19,20 +19,20 @@ public UserService(UserRepository userRepository) { public void save(User user) { if (this.userRepository.findByUserId(user.getUserId()).isPresent()) { - throw new UserAccountException("이미 사용 중인 아이디입니다."); + throw new UserAccountException(ErrorMessage.DUPLICATED_ID); } this.userRepository.save(user); } public User login(String userId, String password) { return this.userRepository.findByUserIdAndPassword(userId, password).orElseThrow(() -> - new UserAccountException("아이디 또는 비밀번호가 일치하지 않습니다.")); + new UserAccountException(ErrorMessage.LOGIN_FAILED)); } public void update(Long id, User user, String oldPassword, User sessionUser) { User loginUser = verifyUser(id, sessionUser); if (!loginUser.matchPassword(oldPassword)) { - throw new UserAccountException("기존 비밀번호가 일치하지 않습니다."); + throw new UserAccountException(ErrorMessage.WRONG_PASSWORD); } loginUser.update(user); this.userRepository.save(loginUser); @@ -40,7 +40,7 @@ public void update(Long id, User user, String oldPassword, User sessionUser) { public User verifyUser(Long id, User sessionUser) { if (!sessionUser.matchId(id)) { - throw new UserSessionException(); + throw new UserSessionException(ErrorMessage.ILLEGAL_USER); } return sessionUser; } @@ -52,7 +52,8 @@ public List findAll() { } public User findByUserId(String userId) { - return this.userRepository.findByUserId(userId).orElseThrow(NotFoundException::new); + return this.userRepository.findByUserId(userId).orElseThrow(() -> + new EntityNotFoundException(ErrorMessage.USER_NOT_FOUND)); } } diff --git a/src/main/resources/static/js/scripts.js b/src/main/resources/static/js/scripts.js index 01f85bd23..458366fad 100755 --- a/src/main/resources/static/js/scripts.js +++ b/src/main/resources/static/js/scripts.js @@ -1,9 +1,107 @@ String.prototype.format = function() { - var args = arguments; + let args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match ; }); -}; \ No newline at end of file +}; + +const redirectUrl = { + LOGINPAGE : "http://localhost:8080/user/login" +} + +const errorMessage = { + NEEDLOGIN : "로그인이 필요한 서비스입니다.", + ILLEGALUSER : "접근권한이 없는 유저입니다." +} + +const request = { + post : (url, body) => { + return fetch(url, { + method : 'post', + headers : { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body : body + }) + }, + delete : (url) => { + return fetch(url, { + method : 'delete', + headers : { + 'Content-Type': 'application/json' + } + }) + } +}; + +function processResponse(errorMessage, url) { + return response => { + if (response.status === 200) { + return response.json(); + } + alert(errorMessage); + window.location.href = url; + } +} + +function updateAnswerCount() { + let linkDeleteArticle = document.querySelectorAll('a.link-delete-article'); + let qnaCommentCount = document.querySelector(".qna-comment-count strong"); + qnaCommentCount.textContent = linkDeleteArticle.length; +} + +function addAnswerEvent() { + let answerWrite = document.querySelector('form.answer-write button[type=submit]'); + if (answerWrite !== null) { + answerWrite.addEventListener('click', addAnswer); + } +} + +function addAnswer(e) { + e.preventDefault(); + let url = document.querySelector('form.answer-write').action; + let body = 'contents=' + document.querySelector('form.answer-write textarea').value; + request.post(url, body) + .then(processResponse(errorMessage.NEEDLOGIN, redirectUrl.LOGINPAGE)) + .then(answer => { + let answerTemplate = document.querySelector('#answerTemplate').innerHTML; + let template = answerTemplate.format(answer.writer.userId, answer.modifiedDate, answer.contents, answer.id); + document.querySelector('.qna-comment-slipp-articles').insertAdjacentHTML('afterbegin', template); + document.querySelector('form.answer-write textarea').value = ''; + addDeleteEventToAllAnswer(); + updateAnswerCount(); + }) +} + +function addDeleteEventToAllAnswer() { + let linkDeleteArticle = document.querySelectorAll('a.link-delete-article'); + linkDeleteArticle.forEach(targetQuery => { + targetQuery.addEventListener('click', deleteAnswer); + }); +} + +function deleteAnswer(e) { + e.preventDefault(); + let url = e.target.href; + request.delete(url) + .then(processResponse(errorMessage.ILLEGALUSER, redirectUrl.LOGINPAGE)) + .then(() => { + let linkDeleteArticle = document.querySelectorAll('a.link-delete-article'); + linkDeleteArticle.forEach(targetQuery => { + if (targetQuery.href === url) { + targetQuery.closest('article').remove(); + updateAnswerCount(); + } + }) + }) +} + +function init() { + addAnswerEvent(); + addDeleteEventToAllAnswer(); +} + +init(); diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index d39d386d7..3a8b60d2f 100755 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -18,7 +18,7 @@
- {{date}} + {{modifiedDate}} {{writer.userId}}
diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index b1541e2db..e946d2ee1 100755 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -4,100 +4,125 @@ {{> partial/header}} - {{> partial/navigation}} -
-
-
- {{#question}} -
-

{{title}}

-
-
- -
-
-

{{answers.length}}개의 의견

-
- {{#answers}} - + {{/answers}} +
+
+ +
+ +
+
-
- {{/question}} +
+ {{/question}}
- {{> partial/script}} +
+ + + +{{> partial/script}}