From cf3d17324634bb6a7bd2396d4d6ddc740e96264c Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Wed, 31 Mar 2021 00:32:19 +0900 Subject: [PATCH 1/6] feat: Implement answer feature using ajax --- .../qna/controller/AnswerController.java | 18 ++++------- .../com/codessquad/qna/domain/Answer.java | 3 ++ .../com/codessquad/qna/domain/Question.java | 7 +++- .../codessquad/qna/service/AnswerService.java | 4 +-- src/main/resources/static/js/scripts.js | 32 ++++++++++++++++++- src/main/resources/templates/qna/show.html | 2 +- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/codessquad/qna/controller/AnswerController.java b/src/main/java/com/codessquad/qna/controller/AnswerController.java index 93f7a78aa..f0f6d6d2c 100644 --- a/src/main/java/com/codessquad/qna/controller/AnswerController.java +++ b/src/main/java/com/codessquad/qna/controller/AnswerController.java @@ -1,20 +1,17 @@ package com.codessquad.qna.controller; +import com.codessquad.qna.domain.Answer; import com.codessquad.qna.domain.User; import com.codessquad.qna.service.AnswerService; -import org.springframework.stereotype.Controller; -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.RequestMapping; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; import static com.codessquad.qna.HttpSessionUtils.getUserFromSession; import static com.codessquad.qna.HttpSessionUtils.isLoginUser; -@Controller -@RequestMapping("/questions/{questionId}/answers") +@RestController +@RequestMapping("/api/questions/{questionId}/answers") public class AnswerController { private final AnswerService answerService; @@ -23,14 +20,13 @@ public AnswerController(AnswerService answerService) { } @PostMapping - public String create(@PathVariable Long questionId, String contents, HttpSession session) { + public Answer create(@PathVariable Long questionId, String contents, HttpSession session) { if (!isLoginUser(session)) { - return "redirect:/users/login"; + return null; } User writer = getUserFromSession(session); - answerService.save(writer, contents, questionId); - return "redirect:/questions/{questionId}"; + return answerService.save(writer, contents, questionId); } @DeleteMapping("/{id}") diff --git a/src/main/java/com/codessquad/qna/domain/Answer.java b/src/main/java/com/codessquad/qna/domain/Answer.java index 95c0de5a6..69299207e 100644 --- a/src/main/java/com/codessquad/qna/domain/Answer.java +++ b/src/main/java/com/codessquad/qna/domain/Answer.java @@ -1,5 +1,7 @@ package com.codessquad.qna.domain; +import com.fasterxml.jackson.annotation.JsonManagedReference; + import javax.persistence.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -15,6 +17,7 @@ public class Answer { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_writer")) + @JsonManagedReference private User writer; @ManyToOne diff --git a/src/main/java/com/codessquad/qna/domain/Question.java b/src/main/java/com/codessquad/qna/domain/Question.java index 4589d88cd..c2fb04b7d 100644 --- a/src/main/java/com/codessquad/qna/domain/Question.java +++ b/src/main/java/com/codessquad/qna/domain/Question.java @@ -1,5 +1,8 @@ package com.codessquad.qna.domain; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; + import javax.persistence.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -16,6 +19,7 @@ public class Question { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_writer")) + @JsonManagedReference private User writer; private String title; @@ -26,7 +30,8 @@ public class Question { private LocalDateTime timeCreated = LocalDateTime.now(); @OneToMany(mappedBy = "question") - @OrderBy("id ASC") + @OrderBy("id DESC") + @JsonBackReference private List answers; private boolean deleted = false; diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index b9fe31781..c297525c6 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -18,10 +18,10 @@ public AnswerService(AnswerRepository answerRepository, QuestionService question this.questionService = questionService; } - public void save(User writer, String contents, Long questionId) { + public Answer save(User writer, String contents, Long questionId) { Question question = questionService.findById(questionId); Answer answer = new Answer(writer, question, contents); - answerRepository.save(answer); + return answerRepository.save(answer); } public Answer findAnswerById(Long id) { diff --git a/src/main/resources/static/js/scripts.js b/src/main/resources/static/js/scripts.js index 01f85bd23..3a5699faf 100755 --- a/src/main/resources/static/js/scripts.js +++ b/src/main/resources/static/js/scripts.js @@ -1,3 +1,33 @@ +$(".answer-write button[type=submit]").click(addAnswer); + +function addAnswer(e) { + e.preventDefault(); + console.log("clicked"); + + var queryString = $(".answer-write").serialize(); + var url = $(".answer-write").attr("action"); + + $.ajax({ + type: 'post', + url: url, + data : queryString, + dataType : 'json', + error : onError, + success : onSuccess + }); +} + +function onError() { +} + +function onSuccess(data) { + console.log(data); + var answerTemplate = $("#answerTemplate").html(); + var template = answerTemplate.format(data.writer.name, data.formattedTimeCreated, data.contents, data.id); + $(".qna-comment-slipp-articles").prepend(template); + $("textarea[name=contents]").val(""); +} + String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { @@ -6,4 +36,4 @@ String.prototype.format = function() { : match ; }); -}; \ No newline at end of file +}; diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index aa1a64630..836aca43b 100644 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -75,7 +75,7 @@

{{question.title}}

{{/question.answers}} -
+
From f0d8414e3dadeee7214760ede5feabe82223e298 Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Thu, 1 Apr 2021 18:46:22 +0900 Subject: [PATCH 2/6] feat: Implement delete-answer feature using ajax --- .../qna/controller/AnswerController.java | 10 +++---- .../com/codessquad/qna/domain/Result.java | 25 ++++++++++++++++ .../codessquad/qna/service/AnswerService.java | 19 +++++++----- src/main/resources/import.sql | 1 + src/main/resources/static/js/scripts.js | 29 ++++++++++++++++++- src/main/resources/templates/qna/show.html | 10 ++----- 6 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/codessquad/qna/domain/Result.java diff --git a/src/main/java/com/codessquad/qna/controller/AnswerController.java b/src/main/java/com/codessquad/qna/controller/AnswerController.java index f0f6d6d2c..a40f82ea9 100644 --- a/src/main/java/com/codessquad/qna/controller/AnswerController.java +++ b/src/main/java/com/codessquad/qna/controller/AnswerController.java @@ -1,6 +1,7 @@ package com.codessquad.qna.controller; import com.codessquad.qna.domain.Answer; +import com.codessquad.qna.domain.Result; import com.codessquad.qna.domain.User; import com.codessquad.qna.service.AnswerService; import org.springframework.web.bind.annotation.*; @@ -30,14 +31,13 @@ public Answer create(@PathVariable Long questionId, String contents, HttpSession } @DeleteMapping("/{id}") - public String delete(@PathVariable Long questionId, @PathVariable Long id, HttpSession session) { + public Result delete(@PathVariable Long questionId, @PathVariable Long id, HttpSession session) { if (!isLoginUser(session)) { - return "redirect:/users/login"; + return Result.fail("You must login"); } - User loginUser = getUserFromSession(session); - answerService.deleteById(id, loginUser); - return "redirect:/questions/{questionId}"; + // Returns either Result.ok() or Result.fail(); + return answerService.deleteById(id, loginUser); } } diff --git a/src/main/java/com/codessquad/qna/domain/Result.java b/src/main/java/com/codessquad/qna/domain/Result.java new file mode 100644 index 000000000..b4d6a3799 --- /dev/null +++ b/src/main/java/com/codessquad/qna/domain/Result.java @@ -0,0 +1,25 @@ +package com.codessquad.qna.domain; + +import com.fasterxml.jackson.annotation.JsonManagedReference; + +public class Result { + + @JsonManagedReference + private boolean valid; + + @JsonManagedReference + private String errorMessage; + + private Result(boolean valid, String errorMessage) { + this.valid = valid; + this.errorMessage = errorMessage; + } + + public static Result ok() { + return new Result(true, null); + } + + public static Result fail(String errorMessage) { + return new Result(false, errorMessage); + } +} diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index c297525c6..1c9ff251e 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -1,9 +1,6 @@ package com.codessquad.qna.service; -import com.codessquad.qna.domain.Answer; -import com.codessquad.qna.domain.AnswerRepository; -import com.codessquad.qna.domain.Question; -import com.codessquad.qna.domain.User; +import com.codessquad.qna.domain.*; import com.codessquad.qna.exception.AnswerNotFoundException; import com.codessquad.qna.exception.IllegalUserAccessException; import org.springframework.stereotype.Service; @@ -32,16 +29,22 @@ public void delete(Answer answer) { answerRepository.delete(answer); } - public void deleteById(Long id, User user) { + public Result deleteById(Long id, User user) { Answer answer = findAnswerById(id); - verifyWriter(answer, user); + if (!verifyWriter(answer, user)) { + return Result.fail("You Can Only Delete Your Answers"); + } answer.delete(); answerRepository.save(answer); + + return Result.ok(); } - public void verifyWriter(Answer answer, User user) { + public boolean verifyWriter(Answer answer, User user) { if (!answer.isAnswerWriter(user)) { - throw new IllegalUserAccessException(); + //throw new IllegalUserAccessException(); + return false; } + return true; } } diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 43026c207..831e72448 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -2,3 +2,4 @@ INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL) VALUES ('sally', '1234', '새 INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL) VALUES ('codesquad', '4321', '코쿼', 'code@codesquad.com'); INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, TIME_CREATED, DELETED) VALUES (1, '1번 게시물', '1번 게시물입니다', CURRENT_TIMESTAMP, FALSE); INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, TIME_CREATED, DELETED) VALUES (2, '2번 게시물', '2번 게시물입니다', CURRENT_TIMESTAMP, FALSE); +INSERT INTO ANSWER (CONTENTS, DELETED, TIME_CREATED, QUESTION_ID, WRITER_ID) VALUES ('HIHI', FALSE, CURRENT_TIMESTAMP, 2, 1); diff --git a/src/main/resources/static/js/scripts.js b/src/main/resources/static/js/scripts.js index 3a5699faf..dec3c010a 100755 --- a/src/main/resources/static/js/scripts.js +++ b/src/main/resources/static/js/scripts.js @@ -23,11 +23,38 @@ function onError() { function onSuccess(data) { console.log(data); var answerTemplate = $("#answerTemplate").html(); - var template = answerTemplate.format(data.writer.name, data.formattedTimeCreated, data.contents, data.id); + var template = answerTemplate.format(data.writer.name, data.formattedTimeCreated, data.contents, data.question.id, data.id); $(".qna-comment-slipp-articles").prepend(template); $("textarea[name=contents]").val(""); } +$(".qna-comment-slipp-articles").on("click", "a.link-delete-article", deleteAnswer); + +function deleteAnswer(e) { + e.preventDefault(); + + var deleteBtn = $(this); + var url = deleteBtn.attr("href"); + console.log(url); + + $.ajax({ + type : 'delete', + url : url, + dataType : 'json', + error : function (xhr, status) { + console.log("error"); + }, + success : function (data, status) { + console.log("success"); + if (data.valid) { + deleteBtn.closest("article").remove(); + } else { + alert(data.errorMessage); + } + } + }); +} + String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index 836aca43b..27debd88a 100644 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -66,10 +66,7 @@

{{question.title}}

수정
  • - - - -
  • + 삭제 @@ -110,10 +107,7 @@

    {{question.title}}

    수정
  • -
    - - -
    + 삭제
  • From 3d9fa3193eeae9394a3c4fe3fa1265c5d1296eca Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Fri, 2 Apr 2021 10:41:54 +0900 Subject: [PATCH 3/6] refactor: Create repository package and move all repositories --- .../qna/domain/{ => repository}/AnswerRepository.java | 3 ++- .../qna/domain/{ => repository}/QuestionRepository.java | 3 ++- .../codessquad/qna/domain/{ => repository}/UserRepository.java | 3 ++- src/main/java/com/codessquad/qna/service/AnswerService.java | 2 +- src/main/java/com/codessquad/qna/service/QuestionService.java | 2 +- src/main/java/com/codessquad/qna/service/UserService.java | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) rename src/main/java/com/codessquad/qna/domain/{ => repository}/AnswerRepository.java (61%) rename src/main/java/com/codessquad/qna/domain/{ => repository}/QuestionRepository.java (71%) rename src/main/java/com/codessquad/qna/domain/{ => repository}/UserRepository.java (71%) diff --git a/src/main/java/com/codessquad/qna/domain/AnswerRepository.java b/src/main/java/com/codessquad/qna/domain/repository/AnswerRepository.java similarity index 61% rename from src/main/java/com/codessquad/qna/domain/AnswerRepository.java rename to src/main/java/com/codessquad/qna/domain/repository/AnswerRepository.java index ad5d026e7..d4a3b0fb5 100644 --- a/src/main/java/com/codessquad/qna/domain/AnswerRepository.java +++ b/src/main/java/com/codessquad/qna/domain/repository/AnswerRepository.java @@ -1,5 +1,6 @@ -package com.codessquad.qna.domain; +package com.codessquad.qna.domain.repository; +import com.codessquad.qna.domain.Answer; import org.springframework.data.jpa.repository.JpaRepository; public interface AnswerRepository extends JpaRepository { diff --git a/src/main/java/com/codessquad/qna/domain/QuestionRepository.java b/src/main/java/com/codessquad/qna/domain/repository/QuestionRepository.java similarity index 71% rename from src/main/java/com/codessquad/qna/domain/QuestionRepository.java rename to src/main/java/com/codessquad/qna/domain/repository/QuestionRepository.java index 3acadbadf..ee771337d 100644 --- a/src/main/java/com/codessquad/qna/domain/QuestionRepository.java +++ b/src/main/java/com/codessquad/qna/domain/repository/QuestionRepository.java @@ -1,5 +1,6 @@ -package com.codessquad.qna.domain; +package com.codessquad.qna.domain.repository; +import com.codessquad.qna.domain.Question; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/codessquad/qna/domain/UserRepository.java b/src/main/java/com/codessquad/qna/domain/repository/UserRepository.java similarity index 71% rename from src/main/java/com/codessquad/qna/domain/UserRepository.java rename to src/main/java/com/codessquad/qna/domain/repository/UserRepository.java index 155ade7b9..d08e4c351 100644 --- a/src/main/java/com/codessquad/qna/domain/UserRepository.java +++ b/src/main/java/com/codessquad/qna/domain/repository/UserRepository.java @@ -1,5 +1,6 @@ -package com.codessquad.qna.domain; +package com.codessquad.qna.domain.repository; +import com.codessquad.qna.domain.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index 1c9ff251e..8f2b70d1d 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -1,8 +1,8 @@ package com.codessquad.qna.service; import com.codessquad.qna.domain.*; +import com.codessquad.qna.domain.repository.AnswerRepository; import com.codessquad.qna.exception.AnswerNotFoundException; -import com.codessquad.qna.exception.IllegalUserAccessException; import org.springframework.stereotype.Service; @Service diff --git a/src/main/java/com/codessquad/qna/service/QuestionService.java b/src/main/java/com/codessquad/qna/service/QuestionService.java index 5e6faef92..7342d0003 100644 --- a/src/main/java/com/codessquad/qna/service/QuestionService.java +++ b/src/main/java/com/codessquad/qna/service/QuestionService.java @@ -1,7 +1,7 @@ package com.codessquad.qna.service; import com.codessquad.qna.domain.Question; -import com.codessquad.qna.domain.QuestionRepository; +import com.codessquad.qna.domain.repository.QuestionRepository; import com.codessquad.qna.domain.User; import com.codessquad.qna.exception.QuestionNotFoundException; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/codessquad/qna/service/UserService.java b/src/main/java/com/codessquad/qna/service/UserService.java index 47726ae07..292d6f93f 100644 --- a/src/main/java/com/codessquad/qna/service/UserService.java +++ b/src/main/java/com/codessquad/qna/service/UserService.java @@ -1,7 +1,7 @@ package com.codessquad.qna.service; import com.codessquad.qna.domain.User; -import com.codessquad.qna.domain.UserRepository; +import com.codessquad.qna.domain.repository.UserRepository; import com.codessquad.qna.exception.UserNotFoundException; import org.springframework.stereotype.Service; From 53569a9e4f591f7db9ec6249c9ec857ba126c3e4 Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Fri, 2 Apr 2021 14:45:13 +0900 Subject: [PATCH 4/6] refactor: Create AbstractEntity class to map with all the overlapping attributes --- .../com/codessquad/qna/QnaApplication.java | 2 + .../codessquad/qna/domain/AbstractEntity.java | 58 +++++++++++++++++++ .../com/codessquad/qna/domain/Answer.java | 34 ++--------- .../com/codessquad/qna/domain/Question.java | 28 ++------- .../com/codessquad/qna/domain/Result.java | 6 +- .../java/com/codessquad/qna/domain/User.java | 28 ++------- .../codessquad/qna/service/AnswerService.java | 3 +- src/main/resources/import.sql | 10 ++-- src/main/resources/static/js/scripts.js | 5 +- src/main/resources/templates/index.html | 2 +- src/main/resources/templates/qna/show.html | 4 +- 11 files changed, 88 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/codessquad/qna/domain/AbstractEntity.java diff --git a/src/main/java/com/codessquad/qna/QnaApplication.java b/src/main/java/com/codessquad/qna/QnaApplication.java index dab708ec8..8d2f86fca 100644 --- a/src/main/java/com/codessquad/qna/QnaApplication.java +++ b/src/main/java/com/codessquad/qna/QnaApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class QnaApplication { public static void main(String[] args) { diff --git a/src/main/java/com/codessquad/qna/domain/AbstractEntity.java b/src/main/java/com/codessquad/qna/domain/AbstractEntity.java new file mode 100644 index 000000000..077dbc979 --- /dev/null +++ b/src/main/java/com/codessquad/qna/domain/AbstractEntity.java @@ -0,0 +1,58 @@ +package com.codessquad.qna.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class AbstractEntity { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm"); + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @JsonProperty + private Long id; + + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate = LocalDateTime.now(); + + public Long getId() { + return id; + } + + public String getFormattedCreatedDate() { return createdDate.format(DATE_TIME_FORMATTER); } + + public String getFormattedModifiedDate() { return modifiedDate.format(DATE_TIME_FORMATTER); } + + @Override + public String toString() { + return "AbstractEntity{" + + "id=" + id + + ", createdDate=" + getFormattedCreatedDate() + + ", modifiedDated=" + getFormattedModifiedDate() + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractEntity that = (AbstractEntity) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/src/main/java/com/codessquad/qna/domain/Answer.java b/src/main/java/com/codessquad/qna/domain/Answer.java index 69299207e..b41104bcc 100644 --- a/src/main/java/com/codessquad/qna/domain/Answer.java +++ b/src/main/java/com/codessquad/qna/domain/Answer.java @@ -8,13 +8,7 @@ import java.util.Objects; @Entity -public class Answer { - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm"); - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - +public class Answer extends AbstractEntity { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_answer_writer")) @JsonManagedReference @@ -27,11 +21,9 @@ public class Answer { @Lob private String contents; - private final LocalDateTime timeCreated = LocalDateTime.now(); - private boolean deleted = false; - protected Answer() {}; + protected Answer() {} public Answer(User writer, Question question, String contents) { this.writer = writer; @@ -39,8 +31,6 @@ public Answer(User writer, Question question, String contents) { this.contents = contents; } - public Long getId() { return id; } - public User getWriter() { return writer; } @@ -53,8 +43,6 @@ public String getContents() { return contents; } - public String getFormattedTimeCreated() { return timeCreated.format(DATE_TIME_FORMATTER); } - public boolean isDeleted() { return deleted; } @@ -63,28 +51,14 @@ public boolean isAnswerWriter(User writer) { return this.writer.equals(writer); } - public void delete() { + public void changeDeleteStatus() { this.deleted = true; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Answer answer = (Answer) o; - return Objects.equals(id, answer.id) && - Objects.equals(question, answer.question); - } - - @Override - public int hashCode() { - return Objects.hash(id, question); - } - @Override public String toString() { return "Answer{" + - "id=" + id + + super.toString() + ", writer=" + writer + ", questionId=" + question.getId() + ", contents='" + contents + '\'' + diff --git a/src/main/java/com/codessquad/qna/domain/Question.java b/src/main/java/com/codessquad/qna/domain/Question.java index c2fb04b7d..584001f0a 100644 --- a/src/main/java/com/codessquad/qna/domain/Question.java +++ b/src/main/java/com/codessquad/qna/domain/Question.java @@ -10,12 +10,7 @@ import java.util.stream.Collectors; @Entity -public class Question { - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm"); - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class Question extends AbstractEntity { @ManyToOne @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_writer")) @@ -27,8 +22,6 @@ public class Question { @Column(nullable = false, length = 2000) private String contents; - private LocalDateTime timeCreated = LocalDateTime.now(); - @OneToMany(mappedBy = "question") @OrderBy("id DESC") @JsonBackReference @@ -56,18 +49,6 @@ public String getContents() { return contents; } - public Long getId() { - return id; - } - - public LocalDateTime getTimeCreated() { - return timeCreated; - } - - public String getFormattedTimeCreated() { - return timeCreated.format(DATE_TIME_FORMATTER); - } - public List getAnswers() { return answers.stream().filter(answer -> !answer.isDeleted()).collect(Collectors.toList()); } @@ -76,7 +57,7 @@ public int getAnswerNumber() { if (this.answers.isEmpty()) { return 0; } - return this.answers.size(); + return getAnswers().size(); } public Question updateQuestion(Question modifiedQuestion) { @@ -92,18 +73,17 @@ public boolean isSameUser(User user) { public void delete() { this.deleted = true; for (Answer a : this.answers) { - a.delete(); + a.changeDeleteStatus(); } } @Override public String toString() { return "Question{" + + super.toString() + "writer='" + writer + '\'' + ", title='" + title + '\'' + ", contents='" + contents + '\'' + - ", id=" + id + - ", timeCreated='" + timeCreated + '\'' + '}'; } } diff --git a/src/main/java/com/codessquad/qna/domain/Result.java b/src/main/java/com/codessquad/qna/domain/Result.java index b4d6a3799..4c37c2c22 100644 --- a/src/main/java/com/codessquad/qna/domain/Result.java +++ b/src/main/java/com/codessquad/qna/domain/Result.java @@ -1,13 +1,13 @@ package com.codessquad.qna.domain; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; public class Result { - @JsonManagedReference + @JsonProperty private boolean valid; - @JsonManagedReference + @JsonProperty private String errorMessage; private Result(boolean valid, String errorMessage) { diff --git a/src/main/java/com/codessquad/qna/domain/User.java b/src/main/java/com/codessquad/qna/domain/User.java index b0d1aa01f..31f96bf5c 100644 --- a/src/main/java/com/codessquad/qna/domain/User.java +++ b/src/main/java/com/codessquad/qna/domain/User.java @@ -1,13 +1,9 @@ package com.codessquad.qna.domain; import javax.persistence.*; -import java.util.Objects; @Entity -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; +public class User extends AbstractEntity { @Column(nullable = false, length = 20, unique = true) private String userId; @@ -32,10 +28,6 @@ public void setEmail(String email) { this.email = email; } - public Long getId() { - return id; - } - public String getUserId() { return userId; } @@ -56,8 +48,8 @@ public boolean matchPassword(String passwordToMatch) { return this.password.equals(passwordToMatch); } - public boolean matchId(long idToMatch) { - return this.id == idToMatch; + public boolean matchId(Long idToMatch) { + return super.getId().equals(idToMatch); } public User updateProfile(User updatedUser) { @@ -72,23 +64,11 @@ public User updateProfile(User updatedUser) { @Override public String toString() { return "User{" + + super.toString() + "userId='" + userId + '\'' + ", name='" + name + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + '}'; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - User user = (User) o; - return id == user.id; - } - - @Override - public int hashCode() { - return Objects.hash(id); - } } diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index 8f2b70d1d..77312b701 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -26,6 +26,7 @@ public Answer findAnswerById(Long id) { } public void delete(Answer answer) { + answer.changeDeleteStatus(); answerRepository.delete(answer); } @@ -34,7 +35,7 @@ public Result deleteById(Long id, User user) { if (!verifyWriter(answer, user)) { return Result.fail("You Can Only Delete Your Answers"); } - answer.delete(); + answer.changeDeleteStatus(); answerRepository.save(answer); return Result.ok(); diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 831e72448..161109d82 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -1,5 +1,5 @@ -INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL) VALUES ('sally', '1234', '새리', 'sally@codesquad.com'); -INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL) VALUES ('codesquad', '4321', '코쿼', 'code@codesquad.com'); -INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, TIME_CREATED, DELETED) VALUES (1, '1번 게시물', '1번 게시물입니다', CURRENT_TIMESTAMP, FALSE); -INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, TIME_CREATED, DELETED) VALUES (2, '2번 게시물', '2번 게시물입니다', CURRENT_TIMESTAMP, FALSE); -INSERT INTO ANSWER (CONTENTS, DELETED, TIME_CREATED, QUESTION_ID, WRITER_ID) VALUES ('HIHI', FALSE, CURRENT_TIMESTAMP, 2, 1); +INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL, CREATED_DATE, MODIFIED_DATE) VALUES ('sally', '1234', '새리', 'sally@codesquad.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL, CREATED_DATE, MODIFIED_DATE) VALUES ('codesquad', '4321', '코쿼', 'code@codesquad.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, CREATED_DATE, DELETED, MODIFIED_DATE) VALUES (1, '1번 게시물', '1번 게시물입니다', CURRENT_TIMESTAMP, FALSE, CURRENT_TIMESTAMP); +INSERT INTO QUESTION (WRITER_ID, TITLE, CONTENTS, CREATED_DATE, DELETED, MODIFIED_DATE) VALUES (2, '2번 게시물', '2번 게시물입니다', CURRENT_TIMESTAMP, FALSE, CURRENT_TIMESTAMP); +INSERT INTO ANSWER (CONTENTS, DELETED, CREATED_DATE, QUESTION_ID, WRITER_ID, MODIFIED_DATE) VALUES ('HIHI', FALSE, CURRENT_TIMESTAMP, 2, 1, CURRENT_TIMESTAMP); diff --git a/src/main/resources/static/js/scripts.js b/src/main/resources/static/js/scripts.js index dec3c010a..0bfe88bdc 100755 --- a/src/main/resources/static/js/scripts.js +++ b/src/main/resources/static/js/scripts.js @@ -17,13 +17,14 @@ function addAnswer(e) { }); } -function onError() { +function onError(request, error) { + alert("Login First"); } function onSuccess(data) { console.log(data); var answerTemplate = $("#answerTemplate").html(); - var template = answerTemplate.format(data.writer.name, data.formattedTimeCreated, data.contents, data.question.id, data.id); + var template = answerTemplate.format(data.writer.name, data.formattedCreatedDate, data.contents, data.question.id, data.id); $(".qna-comment-slipp-articles").prepend(template); $("textarea[name=contents]").val(""); } diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 2fba1564f..97165ad47 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -12,7 +12,7 @@
    - {{formattedTimeCreated}} + {{formattedCreatedDate}} {{writer.name}}
    diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index 27debd88a..a0132fa99 100644 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -14,7 +14,7 @@

    {{question.title}}

    - {{question.formattedTimeCreated}} + {{question.formattedCreatedDate}}
    @@ -53,7 +53,7 @@

    {{question.title}}

    - {{formattedTimeCreated}} + {{formattedCreatedDate}}
    From cfdab0d0cdb031b6ad373a153cc0729af6982ac9 Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Sun, 4 Apr 2021 00:14:05 +0900 Subject: [PATCH 5/6] refactor: Set swagger to document web API --- build.gradle | 4 +++ .../com/codessquad/qna/QnaApplication.java | 30 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index c7f641bc0..65904fbd0 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,10 @@ dependencies { // h2 설정 implementation ('org.springframework.boot:spring-boot-starter-data-jpa') implementation ('com.h2database:h2') + + // swagger 2 + 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 8d2f86fca..1af71b1ed 100644 --- a/src/main/java/com/codessquad/qna/QnaApplication.java +++ b/src/main/java/com/codessquad/qna/QnaApplication.java @@ -2,14 +2,38 @@ 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); - } + public static void main(String[] args) { + SpringApplication.run(QnaApplication.class, args); + } + @Bean + public Docket newsApi () { + return new Docket(DocumentationType.SWAGGER_2) + .groupName("my-slipp") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.ant("/api/**")) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("My Slipp API") + .description("My Slipp API") + .build(); + } } From 7b6b13a711d7518bcbf196c56df63aed3c3f4bcd Mon Sep 17 00:00:00 2001 From: Sally Oh Date: Sun, 4 Apr 2021 19:05:16 +0900 Subject: [PATCH 6/6] fix: Show alert message when user is trying to post/delete answer without logging in using exception --- .../java/com/codessquad/qna/CommonExceptionHandler.java | 7 +++++++ .../com/codessquad/qna/controller/AnswerController.java | 5 +++-- .../codessquad/qna/exception/NotLoggedInException.java | 9 +++++++++ .../java/com/codessquad/qna/service/AnswerService.java | 1 - 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/codessquad/qna/exception/NotLoggedInException.java diff --git a/src/main/java/com/codessquad/qna/CommonExceptionHandler.java b/src/main/java/com/codessquad/qna/CommonExceptionHandler.java index 8574f5765..6e77ad142 100644 --- a/src/main/java/com/codessquad/qna/CommonExceptionHandler.java +++ b/src/main/java/com/codessquad/qna/CommonExceptionHandler.java @@ -1,6 +1,8 @@ package com.codessquad.qna; +import com.codessquad.qna.domain.Result; import com.codessquad.qna.exception.IllegalUserAccessException; +import com.codessquad.qna.exception.NotLoggedInException; import com.codessquad.qna.exception.QuestionNotFoundException; import com.codessquad.qna.exception.UserNotFoundException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -23,4 +25,9 @@ public String handleUserNotFoundException() { public String handleQuestionNotFoundException() { return "redirect:/"; } + + @ExceptionHandler(NotLoggedInException.class) + public Result handleNotLoggedInException() { + return Result.fail("Login Needed"); + } } diff --git a/src/main/java/com/codessquad/qna/controller/AnswerController.java b/src/main/java/com/codessquad/qna/controller/AnswerController.java index a40f82ea9..63a90c3af 100644 --- a/src/main/java/com/codessquad/qna/controller/AnswerController.java +++ b/src/main/java/com/codessquad/qna/controller/AnswerController.java @@ -3,6 +3,7 @@ import com.codessquad.qna.domain.Answer; import com.codessquad.qna.domain.Result; import com.codessquad.qna.domain.User; +import com.codessquad.qna.exception.NotLoggedInException; import com.codessquad.qna.service.AnswerService; import org.springframework.web.bind.annotation.*; @@ -23,7 +24,7 @@ public AnswerController(AnswerService answerService) { @PostMapping public Answer create(@PathVariable Long questionId, String contents, HttpSession session) { if (!isLoginUser(session)) { - return null; + throw new NotLoggedInException(); } User writer = getUserFromSession(session); @@ -33,7 +34,7 @@ public Answer create(@PathVariable Long questionId, String contents, HttpSession @DeleteMapping("/{id}") public Result delete(@PathVariable Long questionId, @PathVariable Long id, HttpSession session) { if (!isLoginUser(session)) { - return Result.fail("You must login"); + throw new NotLoggedInException(); } User loginUser = getUserFromSession(session); diff --git a/src/main/java/com/codessquad/qna/exception/NotLoggedInException.java b/src/main/java/com/codessquad/qna/exception/NotLoggedInException.java new file mode 100644 index 000000000..c738b177f --- /dev/null +++ b/src/main/java/com/codessquad/qna/exception/NotLoggedInException.java @@ -0,0 +1,9 @@ +package com.codessquad.qna.exception; + +import com.codessquad.qna.domain.Result; + +public class NotLoggedInException extends RuntimeException { + public NotLoggedInException() { + super("Login Needed"); + } +} diff --git a/src/main/java/com/codessquad/qna/service/AnswerService.java b/src/main/java/com/codessquad/qna/service/AnswerService.java index 77312b701..8bc8bdc54 100644 --- a/src/main/java/com/codessquad/qna/service/AnswerService.java +++ b/src/main/java/com/codessquad/qna/service/AnswerService.java @@ -43,7 +43,6 @@ public Result deleteById(Long id, User user) { public boolean verifyWriter(Answer answer, User user) { if (!answer.isAnswerWriter(user)) { - //throw new IllegalUserAccessException(); return false; } return true;