Skip to content

Commit

Permalink
[댓글 기능] 베디 미션 제출합니다. (#105)
Browse files Browse the repository at this point in the history
* remove : 기존 로직 삭제

* feat : MainController 구현

* feat: Article 구현

* feat: ArticleReposiory 구현

* feat: ArticleController.write()

* feat: ArticleController.show()

* feat: ArticleController.editForm()

* ArticleController.edit()

* feat: ArticleController.delete()

* feat: ArticleController.writeForm()

* rename: ArticleControllerTests

* feat: MainController Aritcles 조회

* refactor: @transactional 추가

* rename: template 이름 변경

* feat: UserController.signupForm()

* feat: 회원가입 유효성 검사

* feat: 회원가입 이메일 중복, 패스워드 중복 검사

* feat: 회원가입 프론트 구현

* feat: 로그인 구현

* feat: login 구현

* feat: 로그아웃

* feat: 회원 목록 조회

* feat: 마이페이지, 수정폼 구현

* feat: 회원정보 수정

* feat: 회원탈퇴

* refactor: UserDto

* refactor: 테스트코드 중복제거

* feat: interceptor

* feat: logger 추가

* test: LoginInterceptor 세션 테스트 추가

* feat: jupiter-params 추가

* refactor: html 중복 제거

* feat: Articles Paging 구현

* test: UserControllerTest 세션 테스트 추가

* refactor: uri 상수 처리

* test: UserServiceTest

* style: 포맷팅

* refactor: 샘플데이터 수정

* refactor: user session 상수 처리

* rename: ValidSignupException -> ValidUserException

* refactor: LoginInterceptor 매직넘버 상수처리

* fix: logout AuthInterceptor에서 튕기는 문제 해결

* style: build.gradle 정리

* refactor: errorAttributes() 를 ErrorConfig.class 로 분리

* refactor: uri 상수 제거

* refactor: Article.update() 수정

* refactor: test 코드 필요 없는 어노테이션 제거

* refactor: 인터셉터 클래스 commons 패키지에서 interceptor 로 변경

* refactor: BeanUtils 제거, User setter 제거

* refactor: MainController.index Pageable 파라미터로 변경

* feat: User.authenticate 추가

* refactor: ControllerTests 에서 service 빈 제거하고 api를 직접 호출하는 방식으로 변경

* refactor: user session 처리용 UserSession 생성

* style: 포맷팅

* feat: User 유효성검사 추가

* refactor: Article @builder 방식 변경

* fix: LoginInterceptor 적용 패스 및 테스트코드 추가

* fix: UserDto.Update.toUser()

UserDto.Update.toUser() 에서 User를 생성할 때 유효성검사 때문에 생성 안되던 문제 해결

* fix: test코드 session 추가

* docs: application.properties spring.jpa.show-sql=true 제거

* refactor: UserDto.Update.toUser() 방식 수정

* refactor: UserDto.Update에 id 필드 추가

* refactor: User unique 설정 방식 변경

alter table user add constraint email unique (email)
@column으로 이름 설정하면 이름이 이상하게 설정된다.
@table(uniqueConstraints = @UniqueConstraint(name = "email", columnNames = {"email"}))
를 사용하면 이름이 제대로 표시된다

* refactor: SessionId 상수 UserSession 으로 이동

* refactor: UserService.login() UserSession 반환 하는 부분 수정

UserSession은 DTO가 아니므로 Controller에서 생성되게 변경

* feat: UserSessionArgumentResolver

* refactor: Entity 기본생성자 private으로 변경

* remove : 모두 삭제하고 처음부터 시작

* feat: User 유효성 검사

* Revert "feat: User 유효성 검사"

This reverts commit cdf240c.

* Revert "remove : 모두 삭제하고 처음부터 시작"

This reverts commit 41598ee.

* feat: UserValidator

* refactor: User.authenticate() 에서 로그인 검증하게 변경

* refactor: LoginInterceptor 역할 분리

로그인 전, 후 처리 인터셉터 분리
이유 : 하나로 관리하니까 interceptor에 직접 패스를 적어주는 경우가 많아져서 나눔

* feat: Article -> User 단방향 관계 추가 + @column 설정 추가

* feat: BaseControllerTests 공통 기능(회원가입, getJSession) 추상클래스 추가

* feat: Article 글쓴이 추가

* feat: app-head.html 프론트 디자인 변경

* feat: Article 수정시 작성자 아닌 경우 예외처리

* feat: ArticleController Delete 작성자 검증 추가

* refactor: MainController 패키지 수정

* rename: NotLoginInterceptor -> GuestInterceptor

* refactor: Article.User nullable = false 로 변경

* feat: AuthExceptionAdvice 작성자 예외처리 해주는

* fix: @ExceptionHandler(AuthException.class) Illigal인거 수정

* rename: AuthExceptionAdvice -> ExceptionAdvice

* style: ArticleService 메소드 순서 변경

* feat: Comment entity

* rename: Article.updateDate -> modifiedDate

* feat: CommentRepository

* feat: Comment 작성

* refactor: Comment 구조 Article 하위로 변경

* test: CommentControllerTests 수정, 작성 테스트코드 추가

* feat: Comment 수정, 삭제 다른 작성자 접근 금지 추가

* feat: ArticleController.show() 댓글 목록 보여주기 프론트까지 완료

* feat: article.html 댓글 수정 구현

* refactor: Comment 엔티티 어노테이션 수정

@CreatedDate -> @CreationTimestamp
@LastModifiedDate  -> @UpdateTimestamp

* feat: error.html 에러페이지 추가

* refactor: ExceptionAdvice.handleAuthException() 수정

* feat: ExceptionAdvice.handleRuntimeException()

* refactor: CommentService 중복 제거

* style: 포맷팅

* refactor: ExceptionAdvice RuntimeException -> IllegalArgumentException 처리로 변경

* feat: Article, Comment 양방향 관계 추가

* feat: CommentDto.Update 추가

* test: CommentServiceTest

* Revert "feat: CommentDto.Update 추가"

This reverts commit 8b0bea5.

* refactor: ExceptionAdvice.class log.info -> log.error

* refactor: 작성자 확인 후 예외처리 Comment, Article 에게 위임

* refactor: Comment, Article @manytoone(fetch = FetchType.LAZY) 추가

* refactor: 예외처리 메시지 영어에서 한글로 수정

* test: User.authenticate() 테스트 추가

* refactor: Comment.User FetchType.LAZY 제거

Comment는 항상 User와 함께 조회하므로 성능 최적화를 위해서 변경

* refactor: ValidUserException log.debug -> log.error 로 변경

* feat: BaseEntity (@MappedSuperclass) 구현

User, Article, Comment 상위 클래스

* feat: ArticleDto 추가

* refactor: 작성자 확인 isWrittenBy() 엔티티가 아닌 @id로 확인

* refactor: Article.getComments() Collections.unmodifiableList() 추가
  • Loading branch information
dpudpu authored and jihan805 committed Aug 2, 2019
1 parent d2d0a75 commit 455f5ba
Show file tree
Hide file tree
Showing 47 changed files with 1,223 additions and 314 deletions.
16 changes: 8 additions & 8 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
## TODO

- 게시글 생성
- 게시글 조회
- 게시글 수정
- 게시글 삭제
- HTML 중복제거
- 정적 파일 수정 시 재시작 하지 않고 변경사항 반영하기
- class 파일 수정 시 자동으로 재시작 하기

- 작성자 아닌 경우 예외처리용 ControllerAdvice
- 작성자 확인 로직 중복 처리
- 댓글

- Service에 userId 대신에 UserSession을 직접 넘기는게 좋을까?
- userId와 articleId가 헷갈린다.
- 하지만 UserSession은 DTO가 아니다.
- 좋은 해결책은?


# 게시글 생성/조회기능 구현하기
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/techcourse/myblog/advice/ExceptionAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package techcourse.myblog.advice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import techcourse.myblog.exception.AuthException;

@ControllerAdvice
public class ExceptionAdvice {
private static final Logger log = LoggerFactory.getLogger(ExceptionAdvice.class);

@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(AuthException.class)
public String handleAuthException(Exception e, Model model) {

log.error(e.toString());

model.addAttribute("errorMessage", e.getMessage());
model.addAttribute("path", "/");
return "error";
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public String handleRuntimeException(Exception e, Model model) {

log.error(e.toString());

model.addAttribute("errorMessage", e.getMessage());
model.addAttribute("path", "/");
return "error";
}


}
58 changes: 34 additions & 24 deletions src/main/java/techcourse/myblog/articles/Article.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
package techcourse.myblog.articles;

import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import techcourse.myblog.articles.comments.Comment;
import techcourse.myblog.exception.AuthException;
import techcourse.myblog.users.User;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(of = "id")
public class Article {
@NoArgsConstructor
public class Article extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column
@Column(nullable = false, length = 100)
private String title;

@Column
@Lob
private String contents;

@Column
@Column(nullable = false)
private String coverUrl;

@CreatedDate
@Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP", updatable = false)
private LocalDateTime regDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_article_to_user"), nullable = false)
private User author;

@LastModifiedDate
@Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime updateDate;
@OneToMany(mappedBy = "article", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();

@Builder
private Article(final String title, final String contents, final String coverUrl) {
public Article(final String title, final String contents, final String coverUrl, final User author) {
this.title = title;
this.contents = contents;
this.coverUrl = coverUrl;
this.author = author;
}

public List<Comment> getComments() {
return Collections.unmodifiableList(comments);
}

public void update(Article other) {
this.updateDate = LocalDateTime.now();
void update(Article other) {
this.title = other.title;
this.coverUrl = other.coverUrl;
this.contents = other.contents;
}

boolean isWrittenBy(final Long other) {
if (author.getId().equals(other)) {
return true;
}
throw new AuthException("작성자가 아닙니다.");
}
}


42 changes: 29 additions & 13 deletions src/main/java/techcourse/myblog/articles/ArticleController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import techcourse.myblog.articles.comments.Comment;
import techcourse.myblog.users.UserSession;

import javax.servlet.http.HttpSession;
import java.util.List;

@Controller
@RequestMapping("/articles")
Expand All @@ -18,34 +23,45 @@ public String writeForm() {
}

@PostMapping
public String write(Article article) {
Article savedArticle = articleService.save(article);
return "redirect:/articles/" + savedArticle.getId();
public String write(UserSession userSession, ArticleDto.Request articleDto) {
Long userId = userSession.getId();
Long articleId = articleService.save(userId, articleDto);

return "redirect:/articles/" + articleId;
}

@GetMapping("/{id}")
public String show(@PathVariable Long id, Model model) {
Article article = articleService.findById(id);
model.addAttribute(article);
final ArticleDto.Response articleDto = articleService.getOne(id);
final List<Comment> comments = articleDto.getComments();

model.addAttribute("article", articleDto);
model.addAttribute("comments", comments);
return "article";
}

@GetMapping("/{id}/edit")
public String editForm(@PathVariable Long id, Model model) {
Article article = articleService.findById(id);
model.addAttribute(article);
public String editForm(@PathVariable Long id, UserSession userSession, Model model) {
Long userId = userSession.getId();
ArticleDto.Response articleDto = articleService.getOne(userId, id);

model.addAttribute("article", articleDto);
return "article-edit";
}

@PutMapping("/{id}")
public String edit(Article editedArticle) {
Article article = articleService.edit(editedArticle);
return "redirect:/articles/" + article.getId();
public String edit(UserSession userSession, ArticleDto.Request editedArticle) {
Long userId = userSession.getId();
Long articleId = articleService.edit(userId, editedArticle);

return "redirect:/articles/" + articleId;
}

@DeleteMapping("/{id}")
public String delete(@PathVariable Long id) {
articleService.deleteById(id);
public String delete(@PathVariable Long id, UserSession userSession) {
Long userId = userSession.getId();
articleService.deleteById(userId, id);

return "redirect:/";
}
}
65 changes: 65 additions & 0 deletions src/main/java/techcourse/myblog/articles/ArticleDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package techcourse.myblog.articles;


import lombok.*;
import techcourse.myblog.articles.comments.Comment;
import techcourse.myblog.users.User;

import java.util.List;

public class ArticleDto {

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Request {
private Long id;
private String contents;
private String title;
private String coverUrl;

public Article toArticle(final User author) {
return Article.builder()
.contents(contents)
.coverUrl(coverUrl)
.title(title)
.author(author)
.build();
}

public Article toArticle() {
return Article.builder()
.contents(contents)
.coverUrl(coverUrl)
.title(title)
.build();
}
}

@Getter
@NoArgsConstructor
public static class Response {
private Long id;
private String contents;
private String title;
private String coverUrl;
private List<Comment> comments;

Response(Article article) {
id = article.getId();
contents = article.getContents();
title = article.getTitle();
coverUrl = article.getCoverUrl();
}

public static Response createBy(Article article) {
return new ArticleDto.Response(article);
}

public void setComments(List<Comment> comments) {
this.comments = comments;
}
}
}
56 changes: 43 additions & 13 deletions src/main/java/techcourse/myblog/articles/ArticleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,67 @@
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import techcourse.myblog.users.User;
import techcourse.myblog.users.UserRepository;

@Service
@Transactional
@RequiredArgsConstructor
public class ArticleService {
private final ArticleRepository articleRepository;
private final UserRepository userRepository;

public Article save(Article article) {
return articleRepository.save(article);
}
public Long save(final Long userId, final ArticleDto.Request articleDto) {
User author = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("등록된 유저가 아닙니다."));

@Transactional(readOnly = true)
public Article findById(Long id) {
return articleRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Can't find Article : " + id));
Article article = articleDto.toArticle(author);

return articleRepository.save(article).getId();
}

public Article edit(Article editedArticle) {
Article article = findById(editedArticle.getId());
public Long edit(final Long userId, final ArticleDto.Request articleDto) {
Article article = findById(articleDto.getId());

article.update(editedArticle);
article.isWrittenBy(userId);
article.update(articleDto.toArticle());

return article;
return article.getId();
}

public void deleteById(Long id) {
articleRepository.deleteById(id);
public void deleteById(Long userId, Long articleId) {
Article article = findById(articleId);

article.isWrittenBy(userId);
articleRepository.deleteById(articleId);
}

@Transactional(readOnly = true)
public Page<Article> findAll(Pageable pageable) {
return articleRepository.findAll(pageable);
}

@Transactional(readOnly = true)
public ArticleDto.Response getOne(Long id) {
final Article article = findById(id);

ArticleDto.Response articleDto = ArticleDto.Response.createBy(article);
articleDto.setComments(article.getComments());
return articleDto;
}

@Transactional(readOnly = true)
public ArticleDto.Response getOne(final Long userId, final Long articleId) {
Article article = findById(articleId);

article.isWrittenBy(userId);
ArticleDto.Response articleDto = ArticleDto.Response.createBy(article);
articleDto.setComments(article.getComments());
return articleDto;
}

private Article findById(Long id) {
return articleRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("없는 글입니다." + id));
}
}
26 changes: 26 additions & 0 deletions src/main/java/techcourse/myblog/articles/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package techcourse.myblog.articles;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EqualsAndHashCode(of = "id")
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@CreationTimestamp
@Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP", updatable = false)
private LocalDateTime regDate;

@UpdateTimestamp
@Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime modifiedDate;
}
Loading

0 comments on commit 455f5ba

Please sign in to comment.