Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
- [x] 게시글 목록 구현하기
- [x] 게시글 상세보기 구현하기
- [x] 사용자 정보 DB에 저장
- [ ] 배포
- [x] 배포

### 4단계

Expand All @@ -33,3 +33,19 @@
- [x] 세션을 통한 로그인 검증
- [x] 로그인된 유저만이 각 작성한 게시글 수정 및 삭제 기능

### 6단계

- [x] 게시글을 볼 때, 댓글까지 함께 표시
- [x] 로그인한 유저는 댓글 작성 가능
- [x] 자신의 작성한 댓글만 삭제 가능
- [x] 다른 유저의 댓글이 포함되어있는 게시글은 삭제 불가능하도록 구현
- [ ] 댓글 수정기능

> 댓글 수정 기능의 경우, softDeletion 혹은 수정하는 상태에 대한 필드를 추가해서 쿼리 및 로직을 수정해야 깔끔하게 구현할 수 있을 듯하다.

## 테스트

<img width="1074" alt="스크린샷 2022-07-28 오후 6 41 21" src="https://user-images.githubusercontent.com/81129309/181476198-c730bb6e-cb4d-4c9f-b393-bb2f79210e55.png">

<img width="1014" alt="스크린샷 2022-07-28 오후 6 45 58" src="https://user-images.githubusercontent.com/81129309/181476227-fd5fb1c9-d129-45fc-b87c-6dd77d08ecd5.png">

9 changes: 6 additions & 3 deletions src/main/java/com/kakao/cafe/config/MvcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");


}
}
19 changes: 10 additions & 9 deletions src/main/java/com/kakao/cafe/domain/article/Article.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Article> 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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<Reply> 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<Reply> replyRowMapper() {
return (rs, rowNum) -> Reply.of(rs.getInt("id"),
rs.getInt("articleId"),
rs.getString("writer"),
rs.getString("contents"),
rs.getObject("writtenTime", LocalDateTime.class));
}
}
69 changes: 69 additions & 0 deletions src/main/java/com/kakao/cafe/domain/reply/Reply.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/kakao/cafe/domain/reply/ReplyRepository.java
Original file line number Diff line number Diff line change
@@ -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<String> findWriterById(Integer id);
List<Reply> findAllByArticleId(Integer articleId);
boolean deleteOne(Integer id);
boolean hasReplyOfAnotherWriter(Integer articleId, String writer);
}
2 changes: 1 addition & 1 deletion src/main/java/com/kakao/cafe/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/kakao/cafe/service/ArticleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/com/kakao/cafe/service/ReplyService.java
Original file line number Diff line number Diff line change
@@ -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<ReplyResponseDto> 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);
}


}
5 changes: 3 additions & 2 deletions src/main/java/com/kakao/cafe/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,10 +59,10 @@ public User updateUserInfo(UserDto userDto) {
return user;
}

public Optional<UserResponseDto> login(LoginDto loginDto) {
public Optional<SessionUser> 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) {
Expand Down
Loading