diff --git a/README.md b/README.md
index cd28b96c4..768f691c4 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
- [x] 게시글 목록 구현하기
- [x] 게시글 상세보기 구현하기
- [x] 사용자 정보 DB에 저장
-- [ ] 배포
+- [x] 배포
### 4단계
@@ -33,3 +33,19 @@
- [x] 세션을 통한 로그인 검증
- [x] 로그인된 유저만이 각 작성한 게시글 수정 및 삭제 기능
+### 6단계
+
+- [x] 게시글을 볼 때, 댓글까지 함께 표시
+- [x] 로그인한 유저는 댓글 작성 가능
+- [x] 자신의 작성한 댓글만 삭제 가능
+- [x] 다른 유저의 댓글이 포함되어있는 게시글은 삭제 불가능하도록 구현
+- [ ] 댓글 수정기능
+
+> 댓글 수정 기능의 경우, softDeletion 혹은 수정하는 상태에 대한 필드를 추가해서 쿼리 및 로직을 수정해야 깔끔하게 구현할 수 있을 듯하다.
+
+## 테스트
+
+
+
+
+
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/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..5e24dbb43 100644
--- a/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java
+++ b/src/main/java/com/kakao/cafe/domain/article/JdbcTemplateArticleRepository.java
@@ -62,15 +62,11 @@ 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() {
- 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..97d553a7f
--- /dev/null
+++ b/src/main/java/com/kakao/cafe/domain/reply/JdbcTemplateReplyRepository.java
@@ -0,0 +1,88 @@
+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;
+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.*;
+
+@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 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";
+ 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;
+ }
+
+ @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);";
+ 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..30d3b6660
--- /dev/null
+++ b/src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java
@@ -0,0 +1,13 @@
+package com.kakao.cafe.domain.reply;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface ReplyRepository {
+
+ Reply save(Reply reply);
+ Optional findWriterById(Integer id);
+ List findAllByArticleId(Integer articleId);
+ boolean deleteOne(Integer id);
+ boolean hasReplyOfAnotherWriter(Integer articleId, String writer);
+}
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/service/ReplyService.java b/src/main/java/com/kakao/cafe/service/ReplyService.java
new file mode 100644
index 000000000..140d25439
--- /dev/null
+++ b/src/main/java/com/kakao/cafe/service/ReplyService.java
@@ -0,0 +1,61 @@
+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.ReplyResponseDto;
+import com.kakao.cafe.web.dto.ReplyUpdateDto;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class ReplyService {
+
+ private final ReplyRepository replyRepository;
+
+ public ReplyService(ReplyRepository replyRepository) {
+ this.replyRepository = replyRepository;
+ }
+
+ public ReplyResponseDto write(Integer articleId, String writer, String contents) {
+ Reply reply = replyRepository.save(Reply.newInstance(articleId, writer, contents));
+ 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, "접근 권한이 없습니다.");
+ }
+
+ 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, "댓글을 찾을 수 없습니다."));
+
+ return targetWriter.equals(writer);
+ }
+
+
+}
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/ArticleController.java b/src/main/java/com/kakao/cafe/web/ArticleController.java
index a600ed555..1bd72bc09 100644
--- a/src/main/java/com/kakao/cafe/web/ArticleController.java
+++ b/src/main/java/com/kakao/cafe/web/ArticleController.java
@@ -1,12 +1,9 @@
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.web.dto.ArticleDto;
-import com.kakao.cafe.web.dto.ArticleResponseDto;
-import com.kakao.cafe.web.dto.UserResponseDto;
+import com.kakao.cafe.service.ReplyService;
+import com.kakao.cafe.web.dto.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -15,6 +12,7 @@
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
+import java.util.List;
@Controller
@RequestMapping("/qna")
@@ -23,9 +21,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")
@@ -37,10 +37,10 @@ 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);
+ SessionUser sessionedUser = SessionUser.from(httpSession);
+ String sessionedUserId = sessionedUser.getUserId();
+ logger.info("[{}] writing qna{}", sessionedUserId, articleDto);
+ articleService.write(sessionedUserId, articleDto);
return "redirect:/qna/all";
}
@@ -55,30 +55,37 @@ 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);
- logger.info("Show article{}", result);
+
+ List replyResponseDtos = replyService.showAllInArticle(id);
+ model.addAttribute("size", replyResponseDtos.size());
+ if(!replyResponseDtos.isEmpty()) {
+ model.addAttribute("replies", replyResponseDtos);
+ }
+
+ logger.info("Show article[{}] with [{}] replies ", result, replyResponseDtos.size());
return "qna/show";
}
@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) {
- UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER);
- logger.info("[{}] request updateForm qna{}", sessionedUser.getUserId(), id);
+ SessionUser sessionedUser = SessionUser.from(httpSession);
+ logger.info("[{}] request updateForm qna[{}]", sessionedUser.getUserId(), id);
ArticleResponseDto result = articleService.findOne(id);
checkAccessPermission(result, sessionedUser);
@@ -88,20 +95,28 @@ public String updateForm(@PathVariable Integer id, HttpSession httpSession, Mode
}
@PutMapping("/update/{id}")
- public String updateArticle(@PathVariable Integer id, ArticleDto articleDto, HttpSession httpSession) {
- UserResponseDto sessionedUser = (UserResponseDto) httpSession.getAttribute(LoginConstants.SESSIONED_USER);
- articleService.updateOne(sessionedUser.getUserId(), id, articleDto);
+ 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;
}
// 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)){
- 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
new file mode 100644
index 000000000..cf7af1e35
--- /dev/null
+++ b/src/main/java/com/kakao/cafe/web/ReplyController.java
@@ -0,0 +1,53 @@
+package com.kakao.cafe.web;
+
+import com.kakao.cafe.service.ReplyService;
+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.*;
+
+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(@PathVariable Integer articleId, String contents, HttpSession httpSession) {
+ SessionUser sessionedUser = SessionUser.from(httpSession);
+ String sessionedUserId = sessionedUser.getUserId();
+ logger.info("[{}] write reply[{}] in [article{}]", sessionedUserId, contents, articleId);
+
+ replyService.write(articleId, sessionedUserId, contents);
+
+ return "redirect:/qna/show/" + articleId;
+ }
+
+ @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;
+ }
+
+ @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/UserController.java b/src/main/java/com/kakao/cafe/web/UserController.java
index 68ef5558e..61164fdde 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);
@@ -101,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());
@@ -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..672c9b8b8 100644
--- a/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java
+++ b/src/main/java/com/kakao/cafe/web/dto/ArticleResponseDto.java
@@ -50,12 +50,12 @@ 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());
+ 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(id, writer, title, contents, writtenTime);
+ return Objects.hash(getId(), getWriter(), getTitle(), getContents(), getWrittenTime());
}
@Override
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/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/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;
+ }
+}
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..0e437d99c
--- /dev/null
+++ b/src/main/java/com/kakao/cafe/web/dto/SessionUser.java
@@ -0,0 +1,58 @@
+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 {
+
+ private final String userId;
+ 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;
+ 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;
}
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/main/resources/schema.sql b/src/main/resources/schema.sql
index c1a95f08b..5475d3a6c 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) on delete cascade
+)
+
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}}개의 의견
이 글만으로는 원인 파악하기 힘들겠다. 소스 코드와 설정을 단순화해서 공유해 주면 같이 디버깅해줄 수도 있겠다.
---
- 수정
-
- -
-
-
-
-이 글만으로는 원인 파악하기 힘들겠다. 소스 코드와 설정을 단순화해서 공유해 주면 같이 디버깅해줄 수도 있겠다.
+{{contents}}
{{title}}