From 750861d41418454ce07a8468c927b2d9df503c0c Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Thu, 24 Mar 2022 22:20:03 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat=20:=205=EB=8B=A8=EA=B3=84=20PR=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=BD=94=EB=A9=98=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SessionUser, ArticleUpdateDto 구현 equals 메서드 수정 Article 생성자 팩토리 메서드로 수정 --- .../kakao/cafe/domain/article/Article.java | 19 ++++---- .../JdbcTemplateArticleRepository.java | 6 +-- .../java/com/kakao/cafe/domain/user/User.java | 2 +- .../kakao/cafe/service/ArticleService.java | 10 ++--- .../com/kakao/cafe/web/ArticleController.java | 18 ++++---- .../com/kakao/cafe/web/UserController.java | 9 ++-- .../com/kakao/cafe/web/dto/ArticleDto.java | 19 +------- .../cafe/web/dto/ArticleResponseDto.java | 12 ----- .../kakao/cafe/web/dto/ArticleUpdateDto.java | 32 +++++++++++++ .../com/kakao/cafe/web/dto/SessionUser.java | 45 +++++++++++++++++++ .../kakao/cafe/web/dto/UserResponseDto.java | 4 -- 11 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/kakao/cafe/web/dto/ArticleUpdateDto.java create mode 100644 src/main/java/com/kakao/cafe/web/dto/SessionUser.java diff --git a/src/main/java/com/kakao/cafe/domain/article/Article.java b/src/main/java/com/kakao/cafe/domain/article/Article.java index 921575f4e..f730f2a0a 100644 --- a/src/main/java/com/kakao/cafe/domain/article/Article.java +++ b/src/main/java/com/kakao/cafe/domain/article/Article.java @@ -12,22 +12,23 @@ public class Article { private final String contents; private final LocalDateTime writtenTime; - public Article(Integer id, String writer, String title, String contents) { + private Article(Integer id, String writer, String title, String contents, LocalDateTime writtenTime) { this.id = id; this.writer = writer; this.title = title; this.contents = contents; - this.writtenTime = LocalDateTime.now(); + this.writtenTime = writtenTime; } - public Article(Integer id, String writer, String title, String contents, LocalDateTime writtenTime) { - this.id = id; - this.writer = writer; - this.title = title; - this.contents = contents; - this.writtenTime = writtenTime; + public static Article newInstance(Integer id, String writer, String title, String contents) { + return new Article(id, writer, title, contents, LocalDateTime.now()); + } + + public static Article of(Integer id, String writer, String title, String contents, LocalDateTime writtenTime){ + return new Article(id, writer, title, contents, writtenTime); } + public boolean hasId() { return this.id != null; } @@ -61,7 +62,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Article article = (Article) o; - return Objects.equals(getId(), article.getId()); + return this.id.equals(article.getId()); } @Override diff --git a/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java b/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java index b49c32530..26ffa0a12 100644 --- a/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java +++ b/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java @@ -62,11 +62,7 @@ public void clear() { @Override public boolean deleteOne(Integer id) { String sql = "delete from cafe_article where id = :id"; - int result = jdbcTemplate.update(sql, new MapSqlParameterSource("id", id)); - if(result == 1) { - return true; - } - return false; + return jdbcTemplate.update(sql, new MapSqlParameterSource("id", id)) == 1; } private RowMapper
articleRowMapper() { diff --git a/src/main/java/com/kakao/cafe/domain/user/User.java b/src/main/java/com/kakao/cafe/domain/user/User.java index b6cde7d8f..f266bec45 100644 --- a/src/main/java/com/kakao/cafe/domain/user/User.java +++ b/src/main/java/com/kakao/cafe/domain/user/User.java @@ -47,7 +47,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; - return Objects.equals(getUserId(), user.getUserId()) && Objects.equals(getPassword(), user.getPassword()); + return getUserId().equals(user.getUserId()) && getPassword().equals(user.getPassword()); } @Override diff --git a/src/main/java/com/kakao/cafe/service/ArticleService.java b/src/main/java/com/kakao/cafe/service/ArticleService.java index 239855514..ff296e98a 100644 --- a/src/main/java/com/kakao/cafe/service/ArticleService.java +++ b/src/main/java/com/kakao/cafe/service/ArticleService.java @@ -5,6 +5,7 @@ import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.web.dto.ArticleDto; import com.kakao.cafe.web.dto.ArticleResponseDto; +import com.kakao.cafe.web.dto.ArticleUpdateDto; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -42,13 +43,12 @@ public void clearRepository() { public boolean deleteOne(Integer articleId, String writer) { checkAuthorized(articleId, writer); - boolean deleted = articleRepository.deleteOne(articleId); - return deleted; + return articleRepository.deleteOne(articleId); } - public Article updateOne(String userId, Integer articleId, ArticleDto articleDto) { - checkAuthorized(articleId, userId); - return articleRepository.save(articleDto.toUpdateEntity(articleId, userId)); + public Article updateOne(String userId, ArticleUpdateDto articleUpdateDto) { + checkAuthorized(articleUpdateDto.getId(), userId); + return articleRepository.save(articleUpdateDto.toEntityWithWriter(userId)); } private void checkAuthorized(Integer articleId, String writer) { diff --git a/src/main/java/com/kakao/cafe/web/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java index a600ed555..7fd0e0df6 100644 --- a/src/main/java/com/kakao/cafe/web/ArticleController.java +++ b/src/main/java/com/kakao/cafe/web/ArticleController.java @@ -4,9 +4,7 @@ import com.kakao.cafe.constants.LoginConstants; import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.service.ArticleService; -import com.kakao.cafe.web.dto.ArticleDto; -import com.kakao.cafe.web.dto.ArticleResponseDto; -import com.kakao.cafe.web.dto.UserResponseDto; +import com.kakao.cafe.web.dto.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -38,9 +36,9 @@ public String writeForm() { @PostMapping("/write-qna") public String write(ArticleDto articleDto, HttpSession httpSession) { UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); - String sessionedUserUserId = sessionedUser.getUserId(); - logger.info("[{}] writing qna{}", sessionedUserUserId, articleDto); - articleService.write(sessionedUserUserId, articleDto); + String sessionedUserId = sessionedUser.getUserId(); + logger.info("[{}] writing qna{}", sessionedUserId, articleDto); + articleService.write(sessionedUserId, articleDto); return "redirect:/qna/all"; } @@ -77,7 +75,7 @@ public String deleteArticle(@PathVariable Integer id, HttpSession httpSession) { @GetMapping("/update/{id}") public String updateForm(@PathVariable Integer id, HttpSession httpSession, Model model) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = (SessionUser) httpSession.getAttribute(LoginConstants.SESSIONED_USER); logger.info("[{}] request updateForm qna{}", sessionedUser.getUserId(), id); ArticleResponseDto result = articleService.findOne(id); @@ -88,15 +86,15 @@ public String updateForm(@PathVariable Integer id, HttpSession httpSession, Mode } @PutMapping("/update/{id}") - public String updateArticle(@PathVariable Integer id, ArticleDto articleDto, HttpSession httpSession) { + public String updateArticle(@PathVariable Integer id, ArticleUpdateDto articleUpdateDto, HttpSession httpSession) { UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); - articleService.updateOne(sessionedUser.getUserId(), id, articleDto); + articleService.updateOne(sessionedUser.getUserId(), articleUpdateDto); return "redirect:/qna/show/" + id; } // session정보와 pathArticleId 확인 - private void checkAccessPermission(ArticleResponseDto articleResponseDto, UserResponseDto sessionedUser) { + private void checkAccessPermission(ArticleResponseDto articleResponseDto, SessionUser sessionedUser) { String writer = articleResponseDto.getWriter(); Integer id = articleResponseDto.getId(); if(!sessionedUser.hasSameId(writer)){ diff --git a/src/main/java/com/kakao/cafe/web/UserController.java b/src/main/java/com/kakao/cafe/web/UserController.java index 68ef5558e..fb2e1edb8 100644 --- a/src/main/java/com/kakao/cafe/web/UserController.java +++ b/src/main/java/com/kakao/cafe/web/UserController.java @@ -4,6 +4,7 @@ import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.service.UserService; import com.kakao.cafe.web.dto.LoginDto; +import com.kakao.cafe.web.dto.SessionUser; import com.kakao.cafe.web.dto.UserDto; import com.kakao.cafe.web.dto.UserResponseDto; import org.slf4j.Logger; @@ -71,7 +72,7 @@ public String showProfile(@PathVariable String id, Model model) { @GetMapping("/users/{id}/update") public String updateForm(@PathVariable String id, Model model, HttpSession httpSession) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = (SessionUser) httpSession.getAttribute(LoginConstants.SESSIONED_USER); checkAccessPermission(id, sessionedUser); logger.info("[{}] in updateForm for update info", id); @@ -82,7 +83,7 @@ public String updateForm(@PathVariable String id, Model model, HttpSession httpS @PutMapping("/users/{id}/update") public String updateInfo(@PathVariable String id, UserDto userDto, HttpSession httpSession) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = (SessionUser) httpSession.getAttribute(LoginConstants.SESSIONED_USER); checkAccessPermission(id, sessionedUser); logger.info("[{}] updated info [{}]", id, userDto); @@ -123,9 +124,9 @@ public String logout(HttpSession httpSession) { } // session정보와 pathID 확인 - private void checkAccessPermission(String id, UserResponseDto sessionedUser) { + private void checkAccessPermission(String id, SessionUser sessionedUser) { if(!sessionedUser.hasSameId(id)){ - logger.info("[{}] tries access [{}]'s info", sessionedUser.getUserId(), id); + logger.error("[{}] tries access [{}]'s info", sessionedUser.getUserId(), id); throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); } } diff --git a/src/main/java/com/kakao/cafe/web/dto/ArticleDto.java b/src/main/java/com/kakao/cafe/web/dto/ArticleDto.java index fe2c8f482..d990873f4 100644 --- a/src/main/java/com/kakao/cafe/web/dto/ArticleDto.java +++ b/src/main/java/com/kakao/cafe/web/dto/ArticleDto.java @@ -26,24 +26,7 @@ public String getContents() { } public Article toEntityWithWriter(String writer) { - return new Article(null, writer, this.title, this.contents); - } - - public Article toUpdateEntity(Integer articleId, String writer) { - return new Article(articleId, writer, this.title, this.contents); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ArticleDto that = (ArticleDto) o; - return Objects.equals(getTitle(), that.getTitle()) && Objects.equals(getContents(), that.getContents()); - } - - @Override - public int hashCode() { - return Objects.hash(getTitle(), getContents()); + return Article.newInstance(null, writer, this.title, this.contents); } @Override diff --git a/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java b/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java index 814c68494..e7f045d10 100644 --- a/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java +++ b/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java @@ -45,18 +45,6 @@ public LocalDateTime getWrittenTime() { return writtenTime; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ArticleResponseDto that = (ArticleResponseDto) o; - return Objects.equals(getId(), that.getId()) && Objects.equals(getWriter(), that.getWriter()) && Objects.equals(getTitle(), that.getTitle()) && Objects.equals(getContents(), that.getContents()) && Objects.equals(getWrittenTime(), that.getWrittenTime()); - } - - @Override - public int hashCode() { - return Objects.hash(id, writer, title, contents, writtenTime); - } @Override public String toString() { diff --git a/src/main/java/com/kakao/cafe/web/dto/ArticleUpdateDto.java b/src/main/java/com/kakao/cafe/web/dto/ArticleUpdateDto.java new file mode 100644 index 000000000..47e514209 --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/dto/ArticleUpdateDto.java @@ -0,0 +1,32 @@ +package com.kakao.cafe.web.dto; + +import com.kakao.cafe.domain.article.Article; + +public class ArticleUpdateDto { + + private final Integer id; + private final String title; + private final String contents; + + public ArticleUpdateDto(Integer id, String title, String contents) { + this.id = id; + this.title = title; + this.contents = contents; + } + + public Article toEntityWithWriter(String writer) { + return Article.newInstance(this.id, writer, this.title, this.contents); + } + + public Integer getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getContents() { + return contents; + } +} diff --git a/src/main/java/com/kakao/cafe/web/dto/SessionUser.java b/src/main/java/com/kakao/cafe/web/dto/SessionUser.java new file mode 100644 index 000000000..0d39affb0 --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/dto/SessionUser.java @@ -0,0 +1,45 @@ +package com.kakao.cafe.web.dto; + +import java.util.Objects; + +public class SessionUser { + + private final String userId; + private final String name; + private final String email; + + public SessionUser(String userId, String name, String email) { + this.userId = userId; + this.name = name; + this.email = email; + } + + public boolean hasSameId(String userId) { + return this.userId.equals(userId); + } + + public String getUserId() { + return userId; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionUser that = (SessionUser) o; + return getUserId().equals(that.getUserId()) && getName().equals(that.getName()) && getEmail().equals(that.getEmail()); + } + + @Override + public int hashCode() { + return Objects.hash(getUserId(), getName(), getEmail()); + } +} diff --git a/src/main/java/com/kakao/cafe/web/dto/UserResponseDto.java b/src/main/java/com/kakao/cafe/web/dto/UserResponseDto.java index beff45d38..01a7fdc2b 100644 --- a/src/main/java/com/kakao/cafe/web/dto/UserResponseDto.java +++ b/src/main/java/com/kakao/cafe/web/dto/UserResponseDto.java @@ -14,10 +14,6 @@ public UserResponseDto(User user) { this.email = user.getEmail(); } - public boolean hasSameId(String userId) { - return this.userId.equals(userId); - } - public String getUserId() { return userId; } From 11b7020df693ab1311b8caecc2c97f41c51c9e3d Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Thu, 24 Mar 2022 23:18:12 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat=20:=20Reply,=20ReplyRepository,=20Jd?= =?UTF-8?q?bcTemplateReplyRepository=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 댓글 기능 구현을 위한 domain 구현 schema.sql에 cafe_reply table 생성 ddl 작성 --- .../JdbcTemplateArticleRepository.java | 2 +- .../reply/JdbcTemplateReplyRepository.java | 65 +++++++++++++++++ .../com/kakao/cafe/domain/reply/Reply.java | 69 +++++++++++++++++++ .../cafe/domain/reply/ReplyRepository.java | 11 +++ src/main/resources/schema.sql | 10 +++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java create mode 100644 src/main/java/com/kakao/cafe/domain/reply/Reply.java create mode 100644 src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java diff --git a/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java b/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java index 26ffa0a12..5e24dbb43 100644 --- a/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java +++ b/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java @@ -66,7 +66,7 @@ public boolean deleteOne(Integer id) { } private RowMapper
articleRowMapper() { - return (rs, rowNum) -> new Article(rs.getInt("id"), + return (rs, rowNum) -> Article.of(rs.getInt("id"), rs.getString("writer"), rs.getString("title"), rs.getString("contents"), diff --git a/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java new file mode 100644 index 000000000..457618f8c --- /dev/null +++ b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java @@ -0,0 +1,65 @@ +package com.kakao.cafe.domain.reply; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Repository +public class JdbcTemplateReplyRepository implements ReplyRepository{ + + private final NamedParameterJdbcTemplate jdbcTemplate; + + public JdbcTemplateReplyRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Reply save(Reply reply) { + if (reply.hasId()) { + return insert(reply); + } + return update(reply); + } + + @Override + public List findAllByArticleId(Integer articleId) { + String sql ="select id, articleId, writer, contents, writtenTime from cafe_reply where articleId = :articleId"; + return jdbcTemplate.query(sql, new MapSqlParameterSource("articleId", articleId), replyRowMapper()); + } + + @Override + public Boolean deleteOne(Integer id) { + String sql = "delete from cafe_reply where id = :id"; + return jdbcTemplate.update(sql, new MapSqlParameterSource("id", id)) == 1; + } + + private Reply insert(Reply reply) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + String sql = "insert into cafe_reply (articleId, writer, contents, writtenTime) values (:writer, :title, :contents, :writtenTime);"; + jdbcTemplate.update(sql, new BeanPropertySqlParameterSource(reply), keyHolder); + reply.setId(Objects.requireNonNull(keyHolder.getKey()).intValue()); + return reply; + } + + private Reply update(Reply reply) { + String sql = "update cafe_reply set contents = :contents where id = :id"; + jdbcTemplate.update(sql, new BeanPropertySqlParameterSource(reply)); + return reply; + } + + private RowMapper replyRowMapper() { + return (rs, rowNum) -> Reply.of(rs.getInt("id"), + rs.getInt("articleId"), + rs.getString("writer"), + rs.getString("contents"), + rs.getObject("writtenTime", LocalDateTime.class)); + } +} diff --git a/src/main/java/com/kakao/cafe/domain/reply/Reply.java b/src/main/java/com/kakao/cafe/domain/reply/Reply.java new file mode 100644 index 000000000..cf7340a09 --- /dev/null +++ b/src/main/java/com/kakao/cafe/domain/reply/Reply.java @@ -0,0 +1,69 @@ +package com.kakao.cafe.domain.reply; + +import java.time.LocalDateTime; +import java.util.Objects; + +public class Reply { + + private Integer id; + private final Integer ArticleId; + private final String writer; + private final String contents; + private final LocalDateTime writtenTime; + + private Reply(Integer id, Integer articleId, String writer, String contents, LocalDateTime writtenTime) { + this.id = id; + ArticleId = articleId; + this.writer = writer; + this.contents = contents; + this.writtenTime = writtenTime; + } + public static Reply newInstance(Integer articleId, String writer, String contents) { + return new Reply(null, articleId, writer, contents, LocalDateTime.now()); + } + + public static Reply of(Integer id, Integer articleId, String writer, String contents, LocalDateTime writtenTime) { + return new Reply(id, articleId, writer, contents, writtenTime); + } + + public boolean hasId() { + return id != null; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getArticleId() { + return ArticleId; + } + + public String getWriter() { + return writer; + } + + public String getContents() { + return contents; + } + + public LocalDateTime getWrittenTime() { + return writtenTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Reply reply = (Reply) o; + return getId().equals(reply.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java new file mode 100644 index 000000000..e6277b126 --- /dev/null +++ b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java @@ -0,0 +1,11 @@ +package com.kakao.cafe.domain.reply; + +import java.util.List; +import java.util.Optional; + +public interface ReplyRepository { + + Reply save(Reply reply); + List findAllByArticleId(Integer articleId); + Boolean deleteOne(Integer id); +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c1a95f08b..c0d12dca7 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -14,3 +14,13 @@ create table cafe_article ( writtenTime timestamp ); +create table cafe_reply ( + id int auto_increment, + articleId int, + writer varchar(255), + contents varchar(255), + writtenTime timestamp, + primary key (id), + foreign key (articleId) references cafe_article (id) +) + From bdddae5d9d97ac7f17347e8d4eac2b9f999129d1 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 00:19:46 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat=20:=20SessionUser=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HttpSesion에 담긴 user 정보를 담는 모델 구현 기존 UserResponseDto로 담았던 로직을 수정 --- .../java/com/kakao/cafe/service/UserService.java | 5 +++-- .../java/com/kakao/cafe/web/UserController.java | 2 +- .../java/com/kakao/cafe/web/dto/SessionUser.java | 13 +++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kakao/cafe/service/UserService.java b/src/main/java/com/kakao/cafe/service/UserService.java index 667e376f2..ab470cb9f 100644 --- a/src/main/java/com/kakao/cafe/service/UserService.java +++ b/src/main/java/com/kakao/cafe/service/UserService.java @@ -4,6 +4,7 @@ import com.kakao.cafe.domain.user.UserRepository; import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.web.dto.LoginDto; +import com.kakao.cafe.web.dto.SessionUser; import com.kakao.cafe.web.dto.UserDto; import com.kakao.cafe.web.dto.UserResponseDto; import org.springframework.beans.factory.annotation.Autowired; @@ -58,10 +59,10 @@ public User updateUserInfo(UserDto userDto) { return user; } - public Optional login(LoginDto loginDto) { + public Optional login(LoginDto loginDto) { return userRepository.findById(loginDto.getUserId()) .filter(user -> user.isSamePassword(loginDto.getPassword())) - .map(UserResponseDto::new); + .map(SessionUser::from); } public void logout(HttpSession httpSession) { diff --git a/src/main/java/com/kakao/cafe/web/UserController.java b/src/main/java/com/kakao/cafe/web/UserController.java index fb2e1edb8..61164fdde 100644 --- a/src/main/java/com/kakao/cafe/web/UserController.java +++ b/src/main/java/com/kakao/cafe/web/UserController.java @@ -102,7 +102,7 @@ public String loginForm() { @PostMapping("/user/login") public String loginUser(LoginDto loginDto, HttpSession httpSession, HttpServletResponse httpServletResponse, RedirectAttributes redirectAttributes) { logger.info("[{}] request login", loginDto.getUserId()); - Optional loginUserOptional = userService.login(loginDto); + Optional loginUserOptional = userService.login(loginDto); if(loginUserOptional.isPresent()) { logger.info("[{}] succeded login ", loginDto.getUserId()); diff --git a/src/main/java/com/kakao/cafe/web/dto/SessionUser.java b/src/main/java/com/kakao/cafe/web/dto/SessionUser.java index 0d39affb0..0e437d99c 100644 --- a/src/main/java/com/kakao/cafe/web/dto/SessionUser.java +++ b/src/main/java/com/kakao/cafe/web/dto/SessionUser.java @@ -1,6 +1,11 @@ package com.kakao.cafe.web.dto; +import com.kakao.cafe.constants.LoginConstants; +import com.kakao.cafe.domain.user.User; + +import javax.servlet.http.HttpSession; import java.util.Objects; +import java.util.Optional; public class SessionUser { @@ -8,6 +13,14 @@ public class SessionUser { private final String name; private final String email; + public static SessionUser from(User user) { + return new SessionUser(user.getUserId(), user.getName(), user.getEmail()); + } + + public static SessionUser from(HttpSession httpSession) { + return (SessionUser) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + } + public SessionUser(String userId, String name, String email) { this.userId = userId; this.name = name; From 5516c7ac4018844447315a6efce50af54237cb35 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 00:21:40 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat=20:=20ReplyRepository,=20JdbcTemplat?= =?UTF-8?q?eReplyRepository=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reply를 DB에 저장하는 로직을 구현 --- .../reply/JdbcTemplateReplyRepository.java | 18 ++++++++++++++++-- .../cafe/domain/reply/ReplyRepository.java | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java index 457618f8c..dfc427b3c 100644 --- a/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java +++ b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java @@ -1,5 +1,6 @@ package com.kakao.cafe.domain.reply; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -11,6 +12,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Objects; +import java.util.Optional; @Repository public class JdbcTemplateReplyRepository implements ReplyRepository{ @@ -23,12 +25,24 @@ public JdbcTemplateReplyRepository(NamedParameterJdbcTemplate jdbcTemplate) { @Override public Reply save(Reply reply) { - if (reply.hasId()) { + if (!reply.hasId()) { return insert(reply); } return update(reply); } + @Override + public Optional findWriterById(Integer id) { + String sql = "select writer from cafe_reply where id = :id"; + try { + String writer = jdbcTemplate.queryForObject(sql, new MapSqlParameterSource("id", id), String.class); + return Optional.ofNullable(writer); + + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + @Override public List findAllByArticleId(Integer articleId) { String sql ="select id, articleId, writer, contents, writtenTime from cafe_reply where articleId = :articleId"; @@ -43,7 +57,7 @@ public Boolean deleteOne(Integer id) { private Reply insert(Reply reply) { KeyHolder keyHolder = new GeneratedKeyHolder(); - String sql = "insert into cafe_reply (articleId, writer, contents, writtenTime) values (:writer, :title, :contents, :writtenTime);"; + String sql = "insert into cafe_reply (articleId, writer, contents, writtenTime) values (:articleId, :writer, :contents, :writtenTime);"; jdbcTemplate.update(sql, new BeanPropertySqlParameterSource(reply), keyHolder); reply.setId(Objects.requireNonNull(keyHolder.getKey()).intValue()); return reply; diff --git a/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java index e6277b126..5d8b61cda 100644 --- a/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java +++ b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java @@ -6,6 +6,7 @@ public interface ReplyRepository { Reply save(Reply reply); + Optional findWriterById(Integer id); List findAllByArticleId(Integer articleId); Boolean deleteOne(Integer id); } From 454430e97b78fb98685a0c1d5b21a30cd4ff3e94 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 00:22:48 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat=20:=20ReplyController,=20ReplyServic?= =?UTF-8?q?e,=20Reply=20=EA=B4=80=EB=A0=A8=20DTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ArticleController에 ReplyService 필드 추가 qna/show.html에 reply 정보를 출력하도록 수정 --- .../com/kakao/cafe/service/ReplyService.java | 51 +++++++++++++++++ .../com/kakao/cafe/web/ArticleController.java | 12 +++- .../com/kakao/cafe/web/ReplyController.java | 47 ++++++++++++++++ .../java/com/kakao/cafe/web/dto/ReplyDto.java | 35 ++++++++++++ .../kakao/cafe/web/dto/ReplyResponseDto.java | 46 +++++++++++++++ src/main/resources/templates/qna/show.html | 56 +++++-------------- 6 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/kakao/cafe/service/ReplyService.java create mode 100644 src/main/java/com/kakao/cafe/web/ReplyController.java create mode 100644 src/main/java/com/kakao/cafe/web/dto/ReplyDto.java create mode 100644 src/main/java/com/kakao/cafe/web/dto/ReplyResponseDto.java diff --git a/src/main/java/com/kakao/cafe/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java new file mode 100644 index 000000000..159adff75 --- /dev/null +++ b/src/main/java/com/kakao/cafe/service/ReplyService.java @@ -0,0 +1,51 @@ +package com.kakao.cafe.service; + +import com.kakao.cafe.domain.reply.Reply; +import com.kakao.cafe.domain.reply.ReplyRepository; +import com.kakao.cafe.exception.ClientException; +import com.kakao.cafe.web.dto.ReplyDto; +import com.kakao.cafe.web.dto.ReplyResponseDto; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class ReplyService { + + private final ReplyRepository replyRepository; + + public ReplyService(ReplyRepository replyRepository) { + this.replyRepository = replyRepository; + } + + public ReplyResponseDto write(String writer, ReplyDto replyDto) { + Reply reply = replyRepository.save(replyDto.toEntityWithWriter(writer)); + return ReplyResponseDto.from(reply); + } + + public List showAllInArticle(Integer articleId) { + return replyRepository.findAllByArticleId(articleId) + .stream() + .map(ReplyResponseDto::from) + .collect(Collectors.toList()); + } + + public boolean delete(String writer, Integer id) { + if(checkWriter(writer, id)) { + return replyRepository.deleteOne(id); + } + throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); + } + + private boolean checkWriter(String writer, Integer id) { + String targetWriter = replyRepository.findWriterById(id) + .orElseThrow(() -> new ClientException(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다.")); + + return targetWriter.equals(writer); + } + + +} diff --git a/src/main/java/com/kakao/cafe/web/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java index 7fd0e0df6..e54766491 100644 --- a/src/main/java/com/kakao/cafe/web/ArticleController.java +++ b/src/main/java/com/kakao/cafe/web/ArticleController.java @@ -4,6 +4,7 @@ import com.kakao.cafe.constants.LoginConstants; import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.service.ArticleService; +import com.kakao.cafe.service.ReplyService; import com.kakao.cafe.web.dto.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; +import java.util.List; @Controller @RequestMapping("/qna") @@ -21,9 +23,11 @@ public class ArticleController { private final Logger logger = LoggerFactory.getLogger(ArticleController.class); private final ArticleService articleService; + private final ReplyService replyService; - public ArticleController(ArticleService articleService) { + public ArticleController(ArticleService articleService, ReplyService replyService) { this.articleService = articleService; + this.replyService = replyService; } @GetMapping("/write-qna") @@ -56,7 +60,13 @@ public String showArticle(@PathVariable Integer id, Model model) { logger.info("Search for articleId{} to show client", id); ArticleResponseDto result = articleService.findOne(id); + List replyResponseDtos = replyService.showAllInArticle(id); model.addAttribute("article", result); + model.addAttribute("size",replyResponseDtos.size()); + if(!replyResponseDtos.isEmpty()) { + model.addAttribute("replies", replyResponseDtos); + } + logger.info("Show article{}", result); return "qna/show"; diff --git a/src/main/java/com/kakao/cafe/web/ReplyController.java b/src/main/java/com/kakao/cafe/web/ReplyController.java new file mode 100644 index 000000000..727bb2fd9 --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/ReplyController.java @@ -0,0 +1,47 @@ +package com.kakao.cafe.web; + +import com.kakao.cafe.service.ReplyService; +import com.kakao.cafe.web.dto.ReplyDto; +import com.kakao.cafe.web.dto.ReplyResponseDto; +import com.kakao.cafe.web.dto.SessionUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 javax.servlet.http.HttpSession; + +@Controller +@RequestMapping("/qna/{articleId}/reply") +public class ReplyController { + + private final Logger logger = LoggerFactory.getLogger(ReplyController.class); + + private final ReplyService replyService; + + public ReplyController(ReplyService replyService) { + this.replyService = replyService; + } + + @PostMapping("/write") + public String write(ReplyDto replyDto, HttpSession httpSession) { + SessionUser sessionedUser = SessionUser.from(httpSession); + String sessionedUserId = sessionedUser.getUserId(); + logger.info("{} write reply{} in {}", sessionedUserId, replyDto.getContents(), replyDto.getArticleId()); + + ReplyResponseDto replyResponseDto = replyService.write(sessionedUserId, replyDto); + + return "redirect:/qna/show/"+replyResponseDto.getArticleId(); + } + + @DeleteMapping("/{id}/delete") + public String delete(@PathVariable Integer articleId, @PathVariable Integer id, HttpSession httpSession) { + SessionUser sessionedUser = SessionUser.from(httpSession); + replyService.delete(sessionedUser.getUserId(), id); + + return "redirect:/qna/show/"+articleId; + } +} diff --git a/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java b/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java new file mode 100644 index 000000000..a16e11c6f --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java @@ -0,0 +1,35 @@ +package com.kakao.cafe.web.dto; + + +import com.kakao.cafe.domain.reply.Reply; + +public class ReplyDto { + + private final Integer articleId; + private final String contents; + + public ReplyDto(Integer articleId, String contents) { + this.articleId = articleId; + this.contents = contents; + } + + public Reply toEntityWithWriter(String writer) { + return Reply.newInstance(this.articleId, writer, this.contents); + } + + public Integer getArticleId() { + return articleId; + } + + public String getContents() { + return contents; + } + + @Override + public String toString() { + return "ReplyDto{" + + "articleId=" + articleId + + ", contents='" + contents + '\'' + + '}'; + } +} diff --git a/src/main/java/com/kakao/cafe/web/dto/ReplyResponseDto.java b/src/main/java/com/kakao/cafe/web/dto/ReplyResponseDto.java new file mode 100644 index 000000000..e4a8964cc --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/dto/ReplyResponseDto.java @@ -0,0 +1,46 @@ +package com.kakao.cafe.web.dto; + +import com.kakao.cafe.domain.reply.Reply; + +import java.time.LocalDateTime; + +public class ReplyResponseDto { + + private final Integer id; + private final Integer articleId; + private final String writer; + private final String contents; + private final LocalDateTime writtenTime; + + public static ReplyResponseDto from(Reply reply) { + return new ReplyResponseDto(reply.getId(), reply.getArticleId(), reply.getWriter(), reply.getContents(), reply.getWrittenTime()); + } + + public ReplyResponseDto(Integer id, Integer articleId, String writer, String contents, LocalDateTime writtenTime) { + this.id = id; + this.articleId = articleId; + this.writer = writer; + this.contents = contents; + this.writtenTime = writtenTime; + } + + public Integer getId() { + return id; + } + + public Integer getArticleId() { + return articleId; + } + + public String getWriter() { + return writer; + } + + public String getContents() { + return contents; + } + + public LocalDateTime getWrittenTime() { + return writtenTime; + } +} diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index e76f4a431..ad114d3b8 100644 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -43,63 +43,33 @@

{{title}}

{{/article}} -
-

2개의 의견

+

{{size}}개의 의견

- + {{#replies}}
- - - 2016-01-12 14:06 - -
-
-
-

이 글만으로는 원인 파악하기 힘들겠다. 소스 코드와 설정을 단순화해서 공유해 주면 같이 디버깅해줄 수도 있겠다.

-
-
-
    -
  • - 수정 -
  • -
  • -
    - - -
    -
  • -
-
-
- -
-
- + {{/replies}} + {{#article}} + +
+
- +
+ {{/article}} +
From ff0ec2ff650ee6f82abe2080fca51df6934e7911 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 01:20:57 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=EC=84=9C=20=EC=82=AD=EC=A0=9C,=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReplyRepository.hasReplyOfAnotherWriter() 메서드 구현 ReplyService.isDelatableArticle() 메서드 구현 ArticleController.checkDeletable() 메서드 구현 SessionUser 수정하지 못하고 빼먹은 부분 수정 MvcConfig addInterceptors 댓글 작성 ,삭제 url pathPattern 추가 --- .../java/com/kakao/cafe/config/MvcConfig.java | 9 ++++--- .../reply/JdbcTemplateReplyRepository.java | 17 +++++++++--- .../cafe/domain/reply/ReplyRepository.java | 3 ++- .../com/kakao/cafe/service/ReplyService.java | 4 +++ .../com/kakao/cafe/web/ArticleController.java | 27 ++++++++++++------- src/main/resources/schema.sql | 2 +- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/kakao/cafe/config/MvcConfig.java b/src/main/java/com/kakao/cafe/config/MvcConfig.java index 7c8ddc6f0..2706d6001 100644 --- a/src/main/java/com/kakao/cafe/config/MvcConfig.java +++ b/src/main/java/com/kakao/cafe/config/MvcConfig.java @@ -12,8 +12,11 @@ public class MvcConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .order(1) - .addPathPatterns("/users/{id}", "/users/{id}/update", - "/qna/write-qna", "/qna/show/{id}", - "qna/update/{id}", "qna/delete/{id}" ); + .addPathPatterns("/users/{id}", "/users/{id}/update") + .addPathPatterns("/qna/write-qna", "/qna/show/{id}") + .addPathPatterns("qna/update/{id}", "qna/delete/{id}") + .addPathPatterns("/qna/{articleId}/reply/write", "/qna/{articleId}/reply/{id}/delete"); + + } } diff --git a/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java index dfc427b3c..97d553a7f 100644 --- a/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java +++ b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java @@ -10,9 +10,7 @@ import org.springframework.stereotype.Repository; import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; @Repository public class JdbcTemplateReplyRepository implements ReplyRepository{ @@ -50,11 +48,22 @@ public List findAllByArticleId(Integer articleId) { } @Override - public Boolean deleteOne(Integer id) { + public boolean deleteOne(Integer id) { String sql = "delete from cafe_reply where id = :id"; return jdbcTemplate.update(sql, new MapSqlParameterSource("id", id)) == 1; } + @Override + public boolean hasReplyOfAnotherWriter(Integer articleId, String writer) { + String sql = "select count(id) from cafe_reply where articleId = :articleId and writer != :writer"; + + MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource(); + mapSqlParameterSource.addValue("articleId", articleId); + mapSqlParameterSource.addValue("writer", writer); + + return jdbcTemplate.queryForObject(sql, mapSqlParameterSource, Integer.class) > 0; + } + private Reply insert(Reply reply) { KeyHolder keyHolder = new GeneratedKeyHolder(); String sql = "insert into cafe_reply (articleId, writer, contents, writtenTime) values (:articleId, :writer, :contents, :writtenTime);"; diff --git a/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java index 5d8b61cda..30d3b6660 100644 --- a/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java +++ b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java @@ -8,5 +8,6 @@ public interface ReplyRepository { Reply save(Reply reply); Optional findWriterById(Integer id); List findAllByArticleId(Integer articleId); - Boolean deleteOne(Integer id); + boolean deleteOne(Integer id); + boolean hasReplyOfAnotherWriter(Integer articleId, String writer); } diff --git a/src/main/java/com/kakao/cafe/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java index 159adff75..aaa389323 100644 --- a/src/main/java/com/kakao/cafe/service/ReplyService.java +++ b/src/main/java/com/kakao/cafe/service/ReplyService.java @@ -40,6 +40,10 @@ public boolean delete(String writer, Integer id) { throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); } + public boolean isDeletableArticle(Integer articleId, String writer) { + return !replyRepository.hasReplyOfAnotherWriter(articleId, writer); + } + private boolean checkWriter(String writer, Integer id) { String targetWriter = replyRepository.findWriterById(id) .orElseThrow(() -> new ClientException(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다.")); diff --git a/src/main/java/com/kakao/cafe/web/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java index e54766491..f2662ac7d 100644 --- a/src/main/java/com/kakao/cafe/web/ArticleController.java +++ b/src/main/java/com/kakao/cafe/web/ArticleController.java @@ -39,7 +39,7 @@ public String writeForm() { @PostMapping("/write-qna") public String write(ArticleDto articleDto, HttpSession httpSession) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = SessionUser.from(httpSession); String sessionedUserId = sessionedUser.getUserId(); logger.info("[{}] writing qna{}", sessionedUserId, articleDto); articleService.write(sessionedUserId, articleDto); @@ -60,9 +60,10 @@ public String showArticle(@PathVariable Integer id, Model model) { logger.info("Search for articleId{} to show client", id); ArticleResponseDto result = articleService.findOne(id); - List replyResponseDtos = replyService.showAllInArticle(id); model.addAttribute("article", result); - model.addAttribute("size",replyResponseDtos.size()); + + List replyResponseDtos = replyService.showAllInArticle(id); + model.addAttribute("size", replyResponseDtos.size()); if(!replyResponseDtos.isEmpty()) { model.addAttribute("replies", replyResponseDtos); } @@ -74,18 +75,18 @@ public String showArticle(@PathVariable Integer id, Model model) { @DeleteMapping("/delete/{id}") public String deleteArticle(@PathVariable Integer id, HttpSession httpSession) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); - String sessionedUserUserId = sessionedUser.getUserId(); - - articleService.deleteOne(id, sessionedUserUserId); - logger.info("[{}] delete qna{}", sessionedUserUserId, id); + SessionUser sessionedUser = SessionUser.from(httpSession); + String sessionedUserId = sessionedUser.getUserId(); + checkDeletable(id, sessionedUserId); + articleService.deleteOne(id, sessionedUserId); + logger.info("[{}] delete qna{}", sessionedUserId, id); return "redirect:/qna/all"; } @GetMapping("/update/{id}") public String updateForm(@PathVariable Integer id, HttpSession httpSession, Model model) { - SessionUser sessionedUser = (SessionUser) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = SessionUser.from(httpSession); logger.info("[{}] request updateForm qna{}", sessionedUser.getUserId(), id); ArticleResponseDto result = articleService.findOne(id); @@ -97,7 +98,7 @@ public String updateForm(@PathVariable Integer id, HttpSession httpSession, Mode @PutMapping("/update/{id}") public String updateArticle(@PathVariable Integer id, ArticleUpdateDto articleUpdateDto, HttpSession httpSession) { - UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER); + SessionUser sessionedUser = SessionUser.from(httpSession); articleService.updateOne(sessionedUser.getUserId(), articleUpdateDto); return "redirect:/qna/show/" + id; @@ -112,4 +113,10 @@ private void checkAccessPermission(ArticleResponseDto articleResponseDto, Sessio throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); } } + + private void checkDeletable(Integer id, String userId) { + if(!replyService.isDeletableArticle(id, userId)) { + throw new ClientException(HttpStatus.CONFLICT, "다른 유저의 답변이 포함되어 있어서 질문을 삭제할 수 없습니다."); + } + } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c0d12dca7..5475d3a6c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -21,6 +21,6 @@ create table cafe_reply ( contents varchar(255), writtenTime timestamp, primary key (id), - foreign key (articleId) references cafe_article (id) + foreign key (articleId) references cafe_article (id) on delete cascade ) From f078a53751afae2f67adbfe9417bf0d2494e5f4f Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 16:50:43 +0900 Subject: [PATCH 07/12] =?UTF-8?q?test=20:=20=EC=88=98=EC=A0=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=9E=91=EB=8F=99=ED=95=98?= =?UTF-8?q?=EC=A7=80=EC=95=8A=EB=8A=94=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20replyRepo?= =?UTF-8?q?sitoryTest=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit data.sql reply의 foreign key로 인해서 테스트를 위한 article insert query 2개 작성 --- .../cafe/web/dto/ArticleResponseDto.java | 12 ++ src/main/resources/data.sql | 2 + .../JdbcTemplateArticleRepositoryTest.java | 4 +- .../article/MemoryArticleRepositoryTest.java | 2 +- .../JdbcTemplateReplyRepositoryTest.java | 117 ++++++++++++++++++ .../cafe/service/ArticleServiceTest.java | 4 +- .../kakao/cafe/service/UserServiceTest.java | 7 +- .../kakao/cafe/web/ArticleControllerTest.java | 8 +- .../kakao/cafe/web/HomeControllerTest.java | 7 +- .../kakao/cafe/web/UserControllerTest.java | 17 +-- 10 files changed, 157 insertions(+), 23 deletions(-) create mode 100644 src/main/resources/data.sql create mode 100644 src/test/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepositoryTest.java diff --git a/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java b/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java index e7f045d10..672c9b8b8 100644 --- a/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java +++ b/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java @@ -45,6 +45,18 @@ public LocalDateTime getWrittenTime() { return writtenTime; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ArticleResponseDto that = (ArticleResponseDto) o; + return getId().equals(that.getId()) && getWriter().equals(that.getWriter()) && getTitle().equals(that.getTitle()) && getContents().equals(that.getContents()) && getWrittenTime().equals(that.getWrittenTime()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getWriter(), getTitle(), getContents(), getWrittenTime()); + } @Override public String toString() { diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 000000000..1c00cc1e1 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,2 @@ +insert into cafe_article (writer, title, contents, writtenTime) values ('writer1', 'title1', 'contents1', '2022-03-26 12:30'); +insert into cafe_article (writer, title, contents, writtenTime) values ('writer2', 'title2', 'contents2', '2022-03-26 12:31'); diff --git a/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java b/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java index 3f0dba3dc..9acf060c5 100644 --- a/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java +++ b/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java @@ -30,7 +30,7 @@ public JdbcTemplateArticleRepositoryTest(NamedParameterJdbcTemplate namedParamet @BeforeEach void setUp() { - article = new Article(null,"writer","title","contents"); + article = Article.newInstance(null,"writer","title","contents"); } @Test @@ -88,7 +88,7 @@ void findAllTest() { void updateTest() { Article previousArticle = jdbcTemplateArticleRepository.save(article); - Article updateArticle = new Article(previousArticle.getId(), "writer", "newTitle", "newContents", LocalDateTime.now()); + Article updateArticle = Article.of(previousArticle.getId(), "writer", "newTitle", "newContents", LocalDateTime.now()); Article updated = jdbcTemplateArticleRepository.save(updateArticle); Optional
updatedArticleOptional = jdbcTemplateArticleRepository.findById(previousArticle.getId()); diff --git a/src/test/java/com/kakao/cafe/domain/article/MemoryArticleRepositoryTest.java b/src/test/java/com/kakao/cafe/domain/article/MemoryArticleRepositoryTest.java index e12dd6c0f..64709a36e 100644 --- a/src/test/java/com/kakao/cafe/domain/article/MemoryArticleRepositoryTest.java +++ b/src/test/java/com/kakao/cafe/domain/article/MemoryArticleRepositoryTest.java @@ -19,7 +19,7 @@ class MemoryArticleRepositoryTest { @BeforeEach void setUp() { articleRepository = new MemoryArticleRepository(); - article = new Article(null,"작성자", "제목", "본문"); + article = Article.newInstance(null,"작성자", "제목", "본문"); } @AfterEach diff --git a/src/test/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepositoryTest.java b/src/test/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepositoryTest.java new file mode 100644 index 000000000..e7168ec89 --- /dev/null +++ b/src/test/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepositoryTest.java @@ -0,0 +1,117 @@ +package com.kakao.cafe.domain.reply; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; + +@JdbcTest +class JdbcTemplateReplyRepositoryTest { + + private final JdbcTemplateReplyRepository jdbcTemplateReplyRepository; + private Reply reply; + + @Autowired + public JdbcTemplateReplyRepositoryTest(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.jdbcTemplateReplyRepository = new JdbcTemplateReplyRepository(namedParameterJdbcTemplate); + } + + @BeforeEach + void setUp() { + reply = Reply.newInstance(1, "writer1", "contents"); + } + + @Test + @DisplayName("기존에 저장되어 있지 않은 답변이라면, 저장하고, id를 부여한다.") + void save_new_Reply_test() { + + Reply saved = jdbcTemplateReplyRepository.save(reply); + + assertThat(saved.hasId()).isTrue(); + assertThat(saved.getId()).isEqualTo(1); + assertThat(saved.getArticleId()).isEqualTo(reply.getArticleId()); + assertThat(saved.getWriter()).isEqualTo(reply.getWriter()); + assertThat(saved.getContents()).isEqualTo(reply.getContents()); + assertThat(saved.getWrittenTime()).isEqualTo(reply.getWrittenTime()); + } + + @Test + @DisplayName("id를 가지고 있는 (이미 저장한)답변이라면, 정보를 업데이트한다.") + void save_reply_update_test() { + + Reply saved = jdbcTemplateReplyRepository.save(reply); + Reply updateReply = Reply.of(1, 1, "writer", "updatedContents", reply.getWrittenTime()); + + Reply updated = jdbcTemplateReplyRepository.save(updateReply); + + assertThat(updated).isEqualTo(saved); + assertThat(updated.getContents()).isNotEqualTo(saved.getContents()); + } + + @Test + @DisplayName("reply의 id로 해당 writer를 찾아 optional로 반환한다.") + void findWriterByIdTest() { + + Reply saved = jdbcTemplateReplyRepository.save(reply); + + Optional writerById = jdbcTemplateReplyRepository.findWriterById(saved.getId()); + + assertThat(writerById).isNotEmpty(); + assertThat(writerById.get()).isEqualTo(saved.getWriter()); + + } + + @Test + @DisplayName("찾는 id의 reply가 없으면, optional.empty를 반환한다.") + void findWriterByIdTest_not_found() { + + jdbcTemplateReplyRepository.save(reply); + + Optional writerById = jdbcTemplateReplyRepository.findWriterById(2); + + assertThat(writerById).isEmpty(); + + } + + @Test + @DisplayName("하나의 질문 게시글에 포함된 답변을 모두 반환한다.") + void findAllByArticleIdTest() { + Reply saved = jdbcTemplateReplyRepository.save(reply); + Reply replyOfAnotherArticle = jdbcTemplateReplyRepository.save(Reply.newInstance(2, "anotherWriter", "contents")); + + List all = jdbcTemplateReplyRepository.findAllByArticleId(1); + + assertThat(all.size()).isEqualTo(1); + assertThat(all).contains(saved); + assertThat(all).doesNotContain(replyOfAnotherArticle); + } + + @Test + @DisplayName("id로 해당하는 답변을 삭제한다.") + void deleteOne() { + Reply saved = jdbcTemplateReplyRepository.save(reply); + + boolean isDeleted = jdbcTemplateReplyRepository.deleteOne(saved.getId()); + + assertThat(isDeleted).isTrue(); + assertThat(jdbcTemplateReplyRepository.findWriterById(saved.getId())).isEmpty(); + } + + @Test + void hasReplyOfAnotherWriter() { + jdbcTemplateReplyRepository.save(reply); + Reply replyOfAnotherWriter = Reply.newInstance(1, "AnotherWriter", "contents"); + jdbcTemplateReplyRepository.save(replyOfAnotherWriter); + + boolean hasReplyOfAnotherWriter = jdbcTemplateReplyRepository.hasReplyOfAnotherWriter(1, "writer1"); + + assertThat(hasReplyOfAnotherWriter).isTrue(); + } +} diff --git a/src/test/java/com/kakao/cafe/service/ArticleServiceTest.java b/src/test/java/com/kakao/cafe/service/ArticleServiceTest.java index 1024f6a56..1e7b4ba49 100644 --- a/src/test/java/com/kakao/cafe/service/ArticleServiceTest.java +++ b/src/test/java/com/kakao/cafe/service/ArticleServiceTest.java @@ -37,7 +37,7 @@ class ArticleServiceTest { @BeforeEach void setUp() { - article = new Article(1, "writer", "title", "contents", LocalDateTime.of(2022,03,11,11,25)); + article = Article.of(1, "writer", "title", "contents", LocalDateTime.of(2022,03,11,11,25)); } @Test @@ -84,7 +84,7 @@ void findOneTest_error(int id) { } @Test - @DisplayName("모든 게시글을 조회하면 ArticleDto로 변환해서 반환한다.") + @DisplayName("모든 게시글을 조회하면 ArticleResponseDto로 변환해서 반환한다.") void findAllTest() { given(articleRepository.findAll()).willReturn(List.of(article)); diff --git a/src/test/java/com/kakao/cafe/service/UserServiceTest.java b/src/test/java/com/kakao/cafe/service/UserServiceTest.java index 86cde2f5e..23e1ee3d1 100644 --- a/src/test/java/com/kakao/cafe/service/UserServiceTest.java +++ b/src/test/java/com/kakao/cafe/service/UserServiceTest.java @@ -4,6 +4,7 @@ import com.kakao.cafe.domain.user.UserRepository; import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.web.dto.LoginDto; +import com.kakao.cafe.web.dto.SessionUser; import com.kakao.cafe.web.dto.UserDto; import com.kakao.cafe.web.dto.UserResponseDto; import org.junit.jupiter.api.BeforeEach; @@ -138,7 +139,7 @@ void loginTest() { given(userRepository.findById("ron2")).willReturn(Optional.of(user)); //when - UserResponseDto login = userService.login(loginUser).orElse(null); + SessionUser login = userService.login(loginUser).orElse(null); //then assertThat(login.getUserId()).isEqualTo(loginUser.getUserId()); @@ -156,7 +157,7 @@ void login_not_signup_user_throw_test() { given(userRepository.findById(any())).willReturn(Optional.empty()); //when - Optional loginUserOptional = userService.login(loginUser); + Optional loginUserOptional = userService.login(loginUser); //then assertThat(loginUserOptional).isEmpty(); @@ -170,7 +171,7 @@ void login_wrong_password_throw_test() { given(userRepository.findById("ron2")).willReturn(Optional.of(user)); //when - Optional loginUserOptional = userService.login(loginUser); + Optional loginUserOptional = userService.login(loginUser); //then assertThat(loginUserOptional).isEmpty(); diff --git a/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java b/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java index 8bd20ab7f..ae4f95f3f 100644 --- a/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java +++ b/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java @@ -88,7 +88,7 @@ void write_not_logined_user_redirect_test() throws Exception { @DisplayName("/qna/all로 get 요청시 /qna/list 호출해서 게시글 목록을 보여준다.") void showAll() throws Exception { - Article article = new Article(1,"작성자","제목","본문"); + Article article = Article.newInstance(1,"작성자","제목","본문"); List articleResponseDtos = List.of(new ArticleResponseDto(article)); given(articleService.findAll()).willReturn(articleResponseDtos); @@ -103,7 +103,7 @@ void showAll() throws Exception { @Test @DisplayName("/qna/show/{id} get 요청시 해당 id를 가지고 있는 게시글을 /qna/show에서 보여준다.") void showArticle() throws Exception { - Article article = new Article(1,"작성자","제목","본문"); + Article article = Article.newInstance(1,"작성자","제목","본문"); ArticleResponseDto articleResponseDto = new ArticleResponseDto(article); given(articleService.findOne(anyInt())).willReturn(articleResponseDto); @@ -121,7 +121,7 @@ void deleteArticleTest() throws Exception { //given Integer articleId = 1; String writer = "ron2"; - Article article = new Article(articleId, writer, "제목","본문"); + Article article = Article.newInstance(articleId, writer, "제목","본문"); given(articleService.findOne(any())).willReturn(new ArticleResponseDto(article)); //when @@ -140,7 +140,7 @@ void deleteArticleTest() throws Exception { void updateFormTest() throws Exception { //given Integer articleId = 1; - Article article = new Article(articleId, "ron2", "제목","본문"); + Article article = Article.newInstance(articleId, "ron2", "제목","본문"); ArticleResponseDto articleResponseDto = new ArticleResponseDto(article); given(articleService.findOne(any())).willReturn(articleResponseDto); diff --git a/src/test/java/com/kakao/cafe/web/HomeControllerTest.java b/src/test/java/com/kakao/cafe/web/HomeControllerTest.java index 69cd73caf..1d73ca0ed 100644 --- a/src/test/java/com/kakao/cafe/web/HomeControllerTest.java +++ b/src/test/java/com/kakao/cafe/web/HomeControllerTest.java @@ -6,8 +6,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(HomeController.class) @@ -20,8 +19,8 @@ class HomeControllerTest { @DisplayName("GetMapping index.html 테스트") void homeTest() throws Exception { mockMvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(view().name("index")); + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/qna/all")); } diff --git a/src/test/java/com/kakao/cafe/web/UserControllerTest.java b/src/test/java/com/kakao/cafe/web/UserControllerTest.java index 659037776..9ec14d28f 100644 --- a/src/test/java/com/kakao/cafe/web/UserControllerTest.java +++ b/src/test/java/com/kakao/cafe/web/UserControllerTest.java @@ -3,6 +3,7 @@ import com.kakao.cafe.constants.LoginConstants; import com.kakao.cafe.domain.user.User; import com.kakao.cafe.service.UserService; +import com.kakao.cafe.web.dto.SessionUser; import com.kakao.cafe.web.dto.UserResponseDto; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -35,11 +36,13 @@ class UserControllerTest { private MockHttpSession httpSession = new MockHttpSession(); private UserResponseDto userResponseDto; + private SessionUser sessionUser; @BeforeEach void setUp() { User user = new User("ron2", "1234", "ron2", "ron2@gmail.com"); userResponseDto = new UserResponseDto(user); + sessionUser = new SessionUser(user.getUserId(), user.getName(), user.getEmail()); } @AfterEach @@ -96,11 +99,11 @@ void showProfile() throws Exception { @DisplayName("GetMapping userId를 @PathVariable로 받아서 해당 유저 정보를 /user/update_form으로 넘겨준다.") void updateFormTest() throws Exception { - httpSession.setAttribute("sessionedUser",userResponseDto); + httpSession.setAttribute("sessionedUser", sessionUser); mockMvc.perform(get("/users/ron2/update").session(httpSession)) .andExpect(status().isOk()) - .andExpect(model().attribute("user", userResponseDto)) + .andExpect(model().attribute("user", sessionUser)) .andExpect(view().name("user/update_form")) .andDo(print()); } @@ -120,7 +123,7 @@ void updateForm_notLoggedIn_Test() throws Exception { @DisplayName("updatform get 요청시, session과 id가 일치하지않으면 error-page/4xx을 반환한다.") void updateForm_another_user_access_Test() throws Exception { //given - httpSession.setAttribute("sessionedUser",userResponseDto); + httpSession.setAttribute("sessionedUser", sessionUser); //when mockMvc.perform(get("/users/anotherId/update").session(httpSession)) @@ -135,7 +138,7 @@ void updateForm_another_user_access_Test() throws Exception { @DisplayName("PutMapping 수정정보를 받아서 회원정보를 수정 후 /users로 리다이렉션한다.") void updateInfoTest() throws Exception { //given - httpSession.setAttribute("sessionedUser", userResponseDto); + httpSession.setAttribute("sessionedUser", sessionUser); //when mockMvc.perform(put("/users/ron2/update") @@ -163,7 +166,7 @@ void updateInfo_notLoggedIn_User_test() throws Exception { @DisplayName("updateInfo put 요청시, 로그인된 유저가 아닌 다른 유저의 정보를 변경하려고 하면 ClientException이 발생한다. ") void updateInfo_another_user_access_Test() throws Exception { //given - httpSession.setAttribute("sessionedUser", userResponseDto); + httpSession.setAttribute("sessionedUser", sessionUser); //when mockMvc.perform(put("/users/anotherId/update") @@ -189,7 +192,7 @@ void loginFormGetTest() throws Exception { @DisplayName("/user/login post 요청시 user/login을 반환한다.") void loginPostTest() throws Exception { //given - given(userService.login(any())).willReturn(Optional.ofNullable(userResponseDto)); + given(userService.login(any())).willReturn(Optional.ofNullable(sessionUser)); //when mockMvc.perform(post("/user/login") @@ -198,7 +201,7 @@ void loginPostTest() throws Exception { //then .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/qna/all")) - .andExpect(request().sessionAttribute("sessionedUser", userResponseDto)); + .andExpect(request().sessionAttribute("sessionedUser", sessionUser)); } @Test From 71dd17fcf264d54091c1866c78abbcce3929f9d4 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 17:40:30 +0900 Subject: [PATCH 08/12] =?UTF-8?q?test=20:=20replyServiceTest=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kakao/cafe/service/ReplyService.java | 1 - .../kakao/cafe/service/ReplyServiceTest.java | 121 ++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/kakao/cafe/service/ReplyServiceTest.java diff --git a/src/main/java/com/kakao/cafe/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java index aaa389323..6be96a05f 100644 --- a/src/main/java/com/kakao/cafe/service/ReplyService.java +++ b/src/main/java/com/kakao/cafe/service/ReplyService.java @@ -9,7 +9,6 @@ import org.springframework.stereotype.Service; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Service diff --git a/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java b/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java new file mode 100644 index 000000000..dd73fc5b6 --- /dev/null +++ b/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java @@ -0,0 +1,121 @@ +package com.kakao.cafe.service; + +import com.kakao.cafe.domain.reply.Reply; +import com.kakao.cafe.domain.reply.ReplyRepository; +import com.kakao.cafe.exception.ClientException; +import com.kakao.cafe.web.dto.ReplyDto; +import com.kakao.cafe.web.dto.ReplyResponseDto; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class ReplyServiceTest { + + @InjectMocks + private ReplyService replyService; + + @Mock + private ReplyRepository replyRepository; + + private ReplyDto replyDto; + private String writer; + private Reply reply; + + @BeforeEach + void setUp() { + writer = "writer"; + replyDto = new ReplyDto(1, "contents"); + reply = Reply.of(1, 1, writer, "contents", LocalDateTime.now()); + } + + @Test + @DisplayName("writer와 replyDto를 매개변수로, 저장한 후 replyResponseDto를 반환한다.") + void writeTest() { + //given + given(replyRepository.save(any())).willReturn(reply); + + //when + ReplyResponseDto replyResponseDto = replyService.write(writer, replyDto); + + //then + assertThat(replyResponseDto.getId()).isEqualTo(reply.getId()); + assertThat(replyResponseDto.getArticleId()).isEqualTo(reply.getArticleId()); + assertThat(replyResponseDto.getWriter()).isEqualTo(reply.getWriter()); + assertThat(replyResponseDto.getContents()).isEqualTo(reply.getContents()); + assertThat(replyResponseDto.getWrittenTime()).isEqualTo(reply.getWrittenTime()); + } + + @Test + @DisplayName("articleId로 해당하는 article의 답변들을 검색하면, 해당 articleId를 가지는 reply들을 replyResponseDto로 변환해서 리스트로 반환한다.") + void showAllInArticleTest() { + //given + given(replyRepository.findAllByArticleId(1)).willReturn(List.of(reply)); + + //when + List replyResponseDtos = replyService.showAllInArticle(1); + + //then + ReplyResponseDto replyResponseDto = replyResponseDtos.get(0); + assertThat(replyResponseDtos.size()).isEqualTo(1); + assertThat(replyResponseDto.getId()).isEqualTo(reply.getId()); + assertThat(replyResponseDto.getArticleId()).isEqualTo(reply.getArticleId()); + assertThat(replyResponseDto.getWriter()).isEqualTo(reply.getWriter()); + assertThat(replyResponseDto.getContents()).isEqualTo(reply.getContents()); + assertThat(replyResponseDto.getWrittenTime()).isEqualTo(reply.getWrittenTime()); + } + + @Test + @DisplayName("작성자는 직접 작성한 답변이라면, 해당하는 reply의 id로 답변을 삭제할 수 있다.") + void delete_owned_reply_test() { + //given + given(replyRepository.deleteOne(1)).willReturn(true); + given(replyRepository.findWriterById(1)).willReturn(Optional.of(writer)); + + //when + boolean isDeleted = replyService.delete(writer, 1); + + //then + assertThat(isDeleted).isTrue(); + } + + @Test + @DisplayName("다른 작성자의 답변을 삭제하려하면, ClientException이 발생한다") + void delete() { + //given + given(replyRepository.findWriterById(1)).willReturn(Optional.of(writer)); + + //when & then + assertThatThrownBy(()->replyService.delete("AnotherWriter", 1)) + .isInstanceOf(ClientException.class) + .hasMessage("접근 권한이 없습니다."); + + } + + @Test + void isDeletableArticle() { + //given + given(replyRepository.hasReplyOfAnotherWriter(1, writer)).willReturn(false); + + //when + boolean isDeletable = replyService.isDeletableArticle(1, writer); + + //then + assertThat(isDeletable).isTrue(); + + } +} From 9720947ee3039b1b2f7e0eeffe71188157dae064 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 19:17:51 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat=20:=20reply=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reply 수정 로직을 구현했으나, 실제로 화면에 보이기위해서는 softDeletion이나 다른 필드가 필요할 것 같아서 연결하지는 못하였음 DB 쿼리도 변경이 필요할 것 같아서, 다음단계에서 구현을 목표로 함. --- .../com/kakao/cafe/service/ReplyService.java | 8 +++ .../com/kakao/cafe/web/ArticleController.java | 8 +-- .../com/kakao/cafe/web/ReplyController.java | 21 +++++--- .../kakao/cafe/web/dto/ReplyUpdateDto.java | 50 +++++++++++++++++++ 4 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/kakao/cafe/web/dto/ReplyUpdateDto.java diff --git a/src/main/java/com/kakao/cafe/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java index 6be96a05f..fee539ac4 100644 --- a/src/main/java/com/kakao/cafe/service/ReplyService.java +++ b/src/main/java/com/kakao/cafe/service/ReplyService.java @@ -5,6 +5,7 @@ import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.web.dto.ReplyDto; import com.kakao.cafe.web.dto.ReplyResponseDto; +import com.kakao.cafe.web.dto.ReplyUpdateDto; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -43,6 +44,13 @@ public boolean isDeletableArticle(Integer articleId, String writer) { return !replyRepository.hasReplyOfAnotherWriter(articleId, writer); } + public ReplyResponseDto update(ReplyUpdateDto replyUpdateDto, String writer) { + if(!replyUpdateDto.hasSameWriter(writer)) { + throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); + }; + return ReplyResponseDto.from(replyRepository.save(replyUpdateDto.toEntity())); + } + private boolean checkWriter(String writer, Integer id) { String targetWriter = replyRepository.findWriterById(id) .orElseThrow(() -> new ClientException(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다.")); diff --git a/src/main/java/com/kakao/cafe/web/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java index f2662ac7d..b96a66177 100644 --- a/src/main/java/com/kakao/cafe/web/ArticleController.java +++ b/src/main/java/com/kakao/cafe/web/ArticleController.java @@ -1,7 +1,5 @@ package com.kakao.cafe.web; - -import com.kakao.cafe.constants.LoginConstants; import com.kakao.cafe.exception.ClientException; import com.kakao.cafe.service.ArticleService; import com.kakao.cafe.service.ReplyService; @@ -68,7 +66,7 @@ public String showArticle(@PathVariable Integer id, Model model) { model.addAttribute("replies", replyResponseDtos); } - logger.info("Show article{}", result); + logger.info("Show article[{}] with [{}] replies ", result, replyResponseDtos.size()); return "qna/show"; } @@ -100,6 +98,7 @@ public String updateForm(@PathVariable Integer id, HttpSession httpSession, Mode public String updateArticle(@PathVariable Integer id, ArticleUpdateDto articleUpdateDto, HttpSession httpSession) { SessionUser sessionedUser = SessionUser.from(httpSession); articleService.updateOne(sessionedUser.getUserId(), articleUpdateDto); + logger.info("[{}] update qna[{}]", sessionedUser.getUserId(), id); return "redirect:/qna/show/" + id; } @@ -109,13 +108,14 @@ private void checkAccessPermission(ArticleResponseDto articleResponseDto, Sessio String writer = articleResponseDto.getWriter(); Integer id = articleResponseDto.getId(); if(!sessionedUser.hasSameId(writer)){ - logger.info("[{}] tries access [{}]'s article[{}]", sessionedUser.getUserId(), writer, id); + logger.error("[{}] tries access [{}]'s article[{}]", sessionedUser.getUserId(), writer, id); throw new ClientException(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."); } } private void checkDeletable(Integer id, String userId) { if(!replyService.isDeletableArticle(id, userId)) { + logger.error("[{}] failed to delete article[{}] with other user's replies", userId, id); throw new ClientException(HttpStatus.CONFLICT, "다른 유저의 답변이 포함되어 있어서 질문을 삭제할 수 없습니다."); } } diff --git a/src/main/java/com/kakao/cafe/web/ReplyController.java b/src/main/java/com/kakao/cafe/web/ReplyController.java index 727bb2fd9..2c3de41a9 100644 --- a/src/main/java/com/kakao/cafe/web/ReplyController.java +++ b/src/main/java/com/kakao/cafe/web/ReplyController.java @@ -3,14 +3,12 @@ import com.kakao.cafe.service.ReplyService; import com.kakao.cafe.web.dto.ReplyDto; import com.kakao.cafe.web.dto.ReplyResponseDto; +import com.kakao.cafe.web.dto.ReplyUpdateDto; import com.kakao.cafe.web.dto.SessionUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; @@ -30,18 +28,27 @@ public ReplyController(ReplyService replyService) { public String write(ReplyDto replyDto, HttpSession httpSession) { SessionUser sessionedUser = SessionUser.from(httpSession); String sessionedUserId = sessionedUser.getUserId(); - logger.info("{} write reply{} in {}", sessionedUserId, replyDto.getContents(), replyDto.getArticleId()); + logger.info("[{}] write reply[{}] in [article{}]", sessionedUserId, replyDto.getContents(), replyDto.getArticleId()); ReplyResponseDto replyResponseDto = replyService.write(sessionedUserId, replyDto); - return "redirect:/qna/show/"+replyResponseDto.getArticleId(); + return "redirect:/qna/show/" + replyResponseDto.getArticleId(); } @DeleteMapping("/{id}/delete") public String delete(@PathVariable Integer articleId, @PathVariable Integer id, HttpSession httpSession) { SessionUser sessionedUser = SessionUser.from(httpSession); replyService.delete(sessionedUser.getUserId(), id); + logger.info("[{}] delete reply[{}] in [article{}]", sessionedUser.getUserId(), id, articleId); - return "redirect:/qna/show/"+articleId; + return "redirect:/qna/show/" + articleId; + } + + @PostMapping("/{id}/update") + public String update(ReplyUpdateDto replyUpdateDto, HttpSession httpSession) { + SessionUser sessionedUser = SessionUser.from(httpSession); + replyService.update(replyUpdateDto, sessionedUser.getUserId()); + + return "redirect:/qna/show/" + replyUpdateDto.getArticleId(); } } diff --git a/src/main/java/com/kakao/cafe/web/dto/ReplyUpdateDto.java b/src/main/java/com/kakao/cafe/web/dto/ReplyUpdateDto.java new file mode 100644 index 000000000..4fdfec5cf --- /dev/null +++ b/src/main/java/com/kakao/cafe/web/dto/ReplyUpdateDto.java @@ -0,0 +1,50 @@ +package com.kakao.cafe.web.dto; + +import com.kakao.cafe.domain.reply.Reply; + +import java.time.LocalDateTime; + +public class ReplyUpdateDto { + + private final Integer id; + private final Integer articleId; + private final String writer; + private final String contents; + private final LocalDateTime writtenTime; + + public ReplyUpdateDto(Integer id, Integer articleId, String writer, String contents, LocalDateTime writtenTime) { + this.id = id; + this.articleId = articleId; + this.writer = writer; + this.contents = contents; + this.writtenTime = writtenTime; + } + + public Reply toEntity() { + return Reply.of(this.id, this.articleId, this.writer, this.contents, this.writtenTime); + } + + public boolean hasSameWriter(String writer) { + return this.writer.equals(writer); + } + + public Integer getId() { + return id; + } + + public Integer getArticleId() { + return articleId; + } + + public String getWriter() { + return writer; + } + + public String getContents() { + return contents; + } + + public LocalDateTime getWrittenTime() { + return writtenTime; + } +} From a1dd0fcad45905b5e2dc645bea965031247b6b31 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 19:39:59 +0900 Subject: [PATCH 10/12] =?UTF-8?q?test=20:=20ArticleController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=8C=93=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20ReplyController=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReplyDto 삭제에 따른 컨트롤러 서비스 수정 --- .../com/kakao/cafe/service/ReplyService.java | 5 +- .../com/kakao/cafe/web/ReplyController.java | 9 +-- .../java/com/kakao/cafe/web/dto/ReplyDto.java | 35 --------- .../kakao/cafe/service/ReplyServiceTest.java | 7 +- .../kakao/cafe/web/ArticleControllerTest.java | 20 ++++- .../kakao/cafe/web/ReplyControllerTest.java | 78 +++++++++++++++++++ 6 files changed, 101 insertions(+), 53 deletions(-) delete mode 100644 src/main/java/com/kakao/cafe/web/dto/ReplyDto.java create mode 100644 src/test/java/com/kakao/cafe/web/ReplyControllerTest.java diff --git a/src/main/java/com/kakao/cafe/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java index fee539ac4..140d25439 100644 --- a/src/main/java/com/kakao/cafe/service/ReplyService.java +++ b/src/main/java/com/kakao/cafe/service/ReplyService.java @@ -3,7 +3,6 @@ import com.kakao.cafe.domain.reply.Reply; import com.kakao.cafe.domain.reply.ReplyRepository; import com.kakao.cafe.exception.ClientException; -import com.kakao.cafe.web.dto.ReplyDto; import com.kakao.cafe.web.dto.ReplyResponseDto; import com.kakao.cafe.web.dto.ReplyUpdateDto; import org.springframework.http.HttpStatus; @@ -21,8 +20,8 @@ public ReplyService(ReplyRepository replyRepository) { this.replyRepository = replyRepository; } - public ReplyResponseDto write(String writer, ReplyDto replyDto) { - Reply reply = replyRepository.save(replyDto.toEntityWithWriter(writer)); + public ReplyResponseDto write(Integer articleId, String writer, String contents) { + Reply reply = replyRepository.save(Reply.newInstance(articleId, writer, contents)); return ReplyResponseDto.from(reply); } diff --git a/src/main/java/com/kakao/cafe/web/ReplyController.java b/src/main/java/com/kakao/cafe/web/ReplyController.java index 2c3de41a9..c223a808f 100644 --- a/src/main/java/com/kakao/cafe/web/ReplyController.java +++ b/src/main/java/com/kakao/cafe/web/ReplyController.java @@ -1,7 +1,6 @@ package com.kakao.cafe.web; import com.kakao.cafe.service.ReplyService; -import com.kakao.cafe.web.dto.ReplyDto; import com.kakao.cafe.web.dto.ReplyResponseDto; import com.kakao.cafe.web.dto.ReplyUpdateDto; import com.kakao.cafe.web.dto.SessionUser; @@ -25,14 +24,14 @@ public ReplyController(ReplyService replyService) { } @PostMapping("/write") - public String write(ReplyDto replyDto, HttpSession httpSession) { + public String write(@PathVariable Integer articleId, String contents, HttpSession httpSession) { SessionUser sessionedUser = SessionUser.from(httpSession); String sessionedUserId = sessionedUser.getUserId(); - logger.info("[{}] write reply[{}] in [article{}]", sessionedUserId, replyDto.getContents(), replyDto.getArticleId()); + logger.info("[{}] write reply[{}] in [article{}]", sessionedUserId, contents, articleId); - ReplyResponseDto replyResponseDto = replyService.write(sessionedUserId, replyDto); + ReplyResponseDto replyResponseDto = replyService.write(articleId, sessionedUserId, contents); - return "redirect:/qna/show/" + replyResponseDto.getArticleId(); + return "redirect:/qna/show/" + articleId; } @DeleteMapping("/{id}/delete") diff --git a/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java b/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java deleted file mode 100644 index a16e11c6f..000000000 --- a/src/main/java/com/kakao/cafe/web/dto/ReplyDto.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.kakao.cafe.web.dto; - - -import com.kakao.cafe.domain.reply.Reply; - -public class ReplyDto { - - private final Integer articleId; - private final String contents; - - public ReplyDto(Integer articleId, String contents) { - this.articleId = articleId; - this.contents = contents; - } - - public Reply toEntityWithWriter(String writer) { - return Reply.newInstance(this.articleId, writer, this.contents); - } - - public Integer getArticleId() { - return articleId; - } - - public String getContents() { - return contents; - } - - @Override - public String toString() { - return "ReplyDto{" + - "articleId=" + articleId + - ", contents='" + contents + '\'' + - '}'; - } -} diff --git a/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java b/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java index dd73fc5b6..eeca77557 100644 --- a/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java +++ b/src/test/java/com/kakao/cafe/service/ReplyServiceTest.java @@ -3,9 +3,7 @@ import com.kakao.cafe.domain.reply.Reply; import com.kakao.cafe.domain.reply.ReplyRepository; import com.kakao.cafe.exception.ClientException; -import com.kakao.cafe.web.dto.ReplyDto; import com.kakao.cafe.web.dto.ReplyResponseDto; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +17,6 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -32,14 +29,12 @@ class ReplyServiceTest { @Mock private ReplyRepository replyRepository; - private ReplyDto replyDto; private String writer; private Reply reply; @BeforeEach void setUp() { writer = "writer"; - replyDto = new ReplyDto(1, "contents"); reply = Reply.of(1, 1, writer, "contents", LocalDateTime.now()); } @@ -50,7 +45,7 @@ void writeTest() { given(replyRepository.save(any())).willReturn(reply); //when - ReplyResponseDto replyResponseDto = replyService.write(writer, replyDto); + ReplyResponseDto replyResponseDto = replyService.write(1, writer, "contents"); //then assertThat(replyResponseDto.getId()).isEqualTo(reply.getId()); diff --git a/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java b/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java index ae4f95f3f..cc9b8e91b 100644 --- a/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java +++ b/src/test/java/com/kakao/cafe/web/ArticleControllerTest.java @@ -4,8 +4,10 @@ import com.kakao.cafe.domain.article.Article; import com.kakao.cafe.domain.user.User; import com.kakao.cafe.service.ArticleService; +import com.kakao.cafe.service.ReplyService; import com.kakao.cafe.web.dto.ArticleResponseDto; -import com.kakao.cafe.web.dto.UserResponseDto; +import com.kakao.cafe.web.dto.ReplyResponseDto; +import com.kakao.cafe.web.dto.SessionUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,6 +18,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; +import java.time.LocalDateTime; import java.util.List; import static org.mockito.ArgumentMatchers.any; @@ -35,12 +38,15 @@ class ArticleControllerTest { @MockBean ArticleService articleService; + @MockBean + ReplyService replyService; + private MockHttpSession httpSession = new MockHttpSession(); @BeforeEach void SetUp() { User user = new User("ron2", "1234", "로니", "ron2@gmail.com"); - httpSession.setAttribute(LoginConstants.SESSIONED_USER, new UserResponseDto(user)); + httpSession.setAttribute(LoginConstants.SESSIONED_USER, SessionUser.from(user)); } @Test @@ -101,28 +107,34 @@ void showAll() throws Exception { } @Test - @DisplayName("/qna/show/{id} get 요청시 해당 id를 가지고 있는 게시글을 /qna/show에서 보여준다.") + @DisplayName("/qna/show/{id} get 요청시 해당 id를 가지고 있는 게시글을 /qna/show에서 댓글들과 함께 보여준다.") void showArticle() throws Exception { Article article = Article.newInstance(1,"작성자","제목","본문"); ArticleResponseDto articleResponseDto = new ArticleResponseDto(article); + ReplyResponseDto replyResponseDto = new ReplyResponseDto(1,1,"작성자", "본문", LocalDateTime.now()); + List replyResponseDtos = List.of(replyResponseDto); given(articleService.findOne(anyInt())).willReturn(articleResponseDto); + given(replyService.showAllInArticle(anyInt())).willReturn(replyResponseDtos); mockMvc.perform(get("/qna/show/"+anyInt()).session(httpSession)) .andExpect(status().isOk()) .andExpect(model().attributeExists("article")) .andExpect(model().attribute("article", articleResponseDto)) + .andExpect(model().attribute("size", 1)) + .andExpect(model().attribute("replies", replyResponseDtos)) .andExpect(view().name("qna/show")); } @Test - @DisplayName("/qna/delete/{id} DELETE 요청시 로그인된 해당유저가 작성한 글이면 삭제하고, /qna/all로 리다이렉션한다.") + @DisplayName("/qna/delete/{id} DELETE 요청시 로그인된 해당유저가 작성한 글이고, 다른 사용자의 댓글이 없다면 삭제하고, /qna/all로 리다이렉션한다.") void deleteArticleTest() throws Exception { //given Integer articleId = 1; String writer = "ron2"; Article article = Article.newInstance(articleId, writer, "제목","본문"); given(articleService.findOne(any())).willReturn(new ArticleResponseDto(article)); + given(replyService.isDeletableArticle(anyInt(), any())).willReturn(true); //when mockMvc.perform(delete("/qna/delete/"+articleId) diff --git a/src/test/java/com/kakao/cafe/web/ReplyControllerTest.java b/src/test/java/com/kakao/cafe/web/ReplyControllerTest.java new file mode 100644 index 000000000..872d43a07 --- /dev/null +++ b/src/test/java/com/kakao/cafe/web/ReplyControllerTest.java @@ -0,0 +1,78 @@ +package com.kakao.cafe.web; + +import com.kakao.cafe.constants.LoginConstants; +import com.kakao.cafe.domain.reply.Reply; +import com.kakao.cafe.domain.user.User; +import com.kakao.cafe.service.ReplyService; +import com.kakao.cafe.web.dto.ReplyResponseDto; +import com.kakao.cafe.web.dto.SessionUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(ReplyController.class) +class ReplyControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + ReplyService replyService; + + private MockHttpSession httpSession = new MockHttpSession(); + + @BeforeEach + void setUp() { + SessionUser sessionUser = SessionUser.from(new User("ron2", "1234", "로니", "ron2@gmail.com")); + httpSession.setAttribute(LoginConstants.SESSIONED_USER, sessionUser); + } + + @Test + @DisplayName("/qna/{articleId}/reply/write post 요청시 contents를 받아서 댓글을 작성하고, /qna/show/{articleId}로 리다이렉션한다.") + void writeTest() throws Exception { + //given + Reply reply = Reply.of(1,1,"ron2","답변", LocalDateTime.now()); + given(replyService.write(1,"ron2","답변")).willReturn(ReplyResponseDto.from(reply)); + + //when + mockMvc.perform(post("/qna/1/reply/write") + .session(httpSession) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .content("contents=답변")) + //then + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/qna/show/1")); + + } + + @Test + @DisplayName("/qna/{articleId}/reply/{id}/delete delete 요청시 해당 댓글을 삭제하고 /qna/show/{articleId}로 리다이렉션한다.") + void deleteTest() throws Exception { + + //when + mockMvc.perform(delete("/qna/1/reply/1/delete") + .session(httpSession)) + //then + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/qna/show/1")); + + verify(replyService, only()).delete("ron2", 1); + + } + +} From 4f6444a1b672cc36b954b20c434772d675137ce4 Mon Sep 17 00:00:00 2001 From: Minseok-Choi Date: Sat, 26 Mar 2022 19:54:14 +0900 Subject: [PATCH 11/12] =?UTF-8?q?docs=20:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?6=EB=8B=A8=EA=B3=84=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EA=B0=84=EB=8B=A8=20=EC=A0=95=EB=A6=AC,=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++++++++- .../java/com/kakao/cafe/web/ArticleController.java | 6 +++--- src/main/java/com/kakao/cafe/web/ReplyController.java | 2 +- .../article/JdbcTemplateArticleRepositoryTest.java | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cd28b96c4..1944718ba 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - [x] 게시글 목록 구현하기 - [x] 게시글 상세보기 구현하기 - [x] 사용자 정보 DB에 저장 -- [ ] 배포 +- [x] 배포 ### 4단계 @@ -33,3 +33,12 @@ - [x] 세션을 통한 로그인 검증 - [x] 로그인된 유저만이 각 작성한 게시글 수정 및 삭제 기능 +### 6단계 + +- [x] 게시글을 볼 때, 댓글까지 함께 표시 +- [x] 로그인한 유저는 댓글 작성 가능 +- [x] 자신의 작성한 댓글만 삭제 가능 +- [x] 다른 유저의 댓글이 포함되어있는 게시글은 삭제 불가능하도록 구현 +- [ ] 댓글 수정기능 + +> 댓글 수정 기능의 경우, softDeletion 혹은 수정하는 상태에 대한 필드를 추가해서 쿼리 및 로직을 수정해야 깔끔하게 구현할 수 있을 듯하다. diff --git a/src/main/java/com/kakao/cafe/web/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java index b96a66177..1bd72bc09 100644 --- a/src/main/java/com/kakao/cafe/web/ArticleController.java +++ b/src/main/java/com/kakao/cafe/web/ArticleController.java @@ -55,7 +55,7 @@ public String showAll(Model model) { @GetMapping("/show/{id}") public String showArticle(@PathVariable Integer id, Model model) { - logger.info("Search for articleId{} to show client", id); + logger.info("Search for articleId[{}] to show client", id); ArticleResponseDto result = articleService.findOne(id); model.addAttribute("article", result); @@ -77,7 +77,7 @@ public String deleteArticle(@PathVariable Integer id, HttpSession httpSession) { String sessionedUserId = sessionedUser.getUserId(); checkDeletable(id, sessionedUserId); articleService.deleteOne(id, sessionedUserId); - logger.info("[{}] delete qna{}", sessionedUserId, id); + logger.info("[{}] delete qna[{}]", sessionedUserId, id); return "redirect:/qna/all"; } @@ -85,7 +85,7 @@ public String deleteArticle(@PathVariable Integer id, HttpSession httpSession) { @GetMapping("/update/{id}") public String updateForm(@PathVariable Integer id, HttpSession httpSession, Model model) { SessionUser sessionedUser = SessionUser.from(httpSession); - logger.info("[{}] request updateForm qna{}", sessionedUser.getUserId(), id); + logger.info("[{}] request updateForm qna[{}]", sessionedUser.getUserId(), id); ArticleResponseDto result = articleService.findOne(id); checkAccessPermission(result, sessionedUser); diff --git a/src/main/java/com/kakao/cafe/web/ReplyController.java b/src/main/java/com/kakao/cafe/web/ReplyController.java index c223a808f..cf7af1e35 100644 --- a/src/main/java/com/kakao/cafe/web/ReplyController.java +++ b/src/main/java/com/kakao/cafe/web/ReplyController.java @@ -29,7 +29,7 @@ public String write(@PathVariable Integer articleId, String contents, HttpSessio String sessionedUserId = sessionedUser.getUserId(); logger.info("[{}] write reply[{}] in [article{}]", sessionedUserId, contents, articleId); - ReplyResponseDto replyResponseDto = replyService.write(articleId, sessionedUserId, contents); + replyService.write(articleId, sessionedUserId, contents); return "redirect:/qna/show/" + articleId; } diff --git a/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java b/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java index 9acf060c5..83ed9f985 100644 --- a/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java +++ b/src/test/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepositoryTest.java @@ -104,7 +104,7 @@ void updateTest() { } @Test - @DisplayName("delte 시 해당 게시글을 삭제한다.") + @DisplayName("delete 시 해당 게시글을 삭제한다.") void deleteTest() { Article saved = jdbcTemplateArticleRepository.save(article); From e75b1e3d5fb7b1ad24a1168706a4217f18689966 Mon Sep 17 00:00:00 2001 From: Minseok-Choi <81129309+CMSSKKK@users.noreply.github.com> Date: Thu, 28 Jul 2022 18:49:14 +0900 Subject: [PATCH 12/12] =?UTF-8?q?docs:=20test=20summary=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 1944718ba..768f691c4 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,10 @@ - [ ] 댓글 수정기능 > 댓글 수정 기능의 경우, softDeletion 혹은 수정하는 상태에 대한 필드를 추가해서 쿼리 및 로직을 수정해야 깔끔하게 구현할 수 있을 듯하다. + +## 테스트 + +스크린샷 2022-07-28 오후 6 41 21 + +스크린샷 2022-07-28 오후 6 45 58 +