From 6da34c0fb801f6e1f5b07e78e35526450134e2ff Mon Sep 17 00:00:00 2001 From: jinia91 Date: Tue, 7 Dec 2021 11:59:01 +0900 Subject: [PATCH] =?UTF-8?q?21.12.07=20=EB=B0=B1=EC=97=94=EB=93=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=201?= =?UTF-8?q?=EC=B0=A8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/ArticleController.java | 6 +- .../myblog/blog/article/domain/Article.java | 2 +- .../blog/base/config/SecurityConfig.java | 1 + .../controller/CategoryController.java | 22 ++++-- .../blog/category/dto/CategoryNormalDto.java | 4 + .../comment/controller/CommentController.java | 43 ++++++----- .../myblog/blog/comment/domain/Comment.java | 10 +-- .../myblog/blog/comment/dto/CommentDto.java | 29 ++++--- ...oForSide.java => CommentDtoForLayout.java} | 8 +- .../myblog/blog/comment/dto/CommentForm.java | 12 ++- .../comment/repository/CommentRepository.java | 9 +++ .../repository/NaCommentRepository.java | 4 +- .../blog/comment/service/CommentService.java | 77 ++++++++++++++----- .../blog/exception/CustomFormException.java | 9 +++ .../blog/exception/ExceptionController.java | 23 ------ .../exception/ExceptionRestController.java | 18 +++++ .../myblog/blog/exception/ListValidator.java | 32 ++++++++ .../blog/exception/LoginFailHandler.java | 8 +- .../img/controller/UploadImgController.java | 7 +- .../myblog/blog/img/domain/UploadedImg.java | 18 ----- .../myblog/blog/img/dto/UploadImgDto.java | 5 +- .../blog/img/service/UploadImgService.java | 29 ++++--- .../java/myblog/blog/main/MainController.java | 23 ++++-- .../blog/member/auth/PrincipalDetails.java | 10 +-- .../blog/member/auth/UserInfoFactory.java | 16 +++- .../auth/userinfo/FacebookUserInfo.java | 2 +- .../member/auth/userinfo/KakaoUserInfo.java | 52 ------------- .../member/auth/userinfo/NaverUserInfo.java | 2 +- .../member/auth/userinfo/Oauth2UserInfo.java | 3 + .../member/auth/userinfo/ProviderType.java | 5 +- .../member/controller/MemberController.java | 20 +++-- .../myblog/blog/member/doamin/Member.java | 12 ++- .../java/myblog/blog/member/doamin/Role.java | 5 ++ .../myblog/blog/member/dto/MemberDto.java | 4 +- .../member/repository/MemberRepository.java | 7 +- .../member/service/Oauth2MemberService.java | 13 ++++ .../blog/tags/domain/ArticleTagList.java | 8 +- .../java/myblog/blog/tags/domain/Tags.java | 4 +- .../java/myblog/blog/tags/dto/TagsDto.java | 3 + .../repository/ArticleTagListsRepository.java | 7 +- .../blog/tags/repository/TagsRepository.java | 4 +- .../myblog/blog/tags/service/TagsService.java | 30 +++++--- src/main/resources/static/css/articleView.css | 1 + src/main/resources/static/css/login.css | 5 -- src/main/resources/static/css/mainCss.css | 13 ++-- src/main/resources/static/js/tags.js | 14 ---- .../templates/admin/categoryEdit.html | 17 ++-- .../templates/article/articleEditForm.html | 1 - .../templates/article/articleWriteForm.html | 11 +-- .../resources/templates/layout/fragments.html | 70 +++++++++++------ src/main/resources/templates/login.html | 22 ------ 51 files changed, 442 insertions(+), 318 deletions(-) rename src/main/java/myblog/blog/comment/dto/{CommentDtoForSide.java => CommentDtoForLayout.java} (68%) create mode 100644 src/main/java/myblog/blog/exception/CustomFormException.java delete mode 100644 src/main/java/myblog/blog/exception/ExceptionController.java create mode 100644 src/main/java/myblog/blog/exception/ExceptionRestController.java create mode 100644 src/main/java/myblog/blog/exception/ListValidator.java delete mode 100644 src/main/java/myblog/blog/img/domain/UploadedImg.java delete mode 100644 src/main/java/myblog/blog/member/auth/userinfo/KakaoUserInfo.java delete mode 100644 src/main/resources/static/js/tags.js diff --git a/src/main/java/myblog/blog/article/controller/ArticleController.java b/src/main/java/myblog/blog/article/controller/ArticleController.java index b99e152..cfeb5c6 100644 --- a/src/main/java/myblog/blog/article/controller/ArticleController.java +++ b/src/main/java/myblog/blog/article/controller/ArticleController.java @@ -8,7 +8,7 @@ import myblog.blog.article.service.TempArticleService; import myblog.blog.category.dto.CategoryForView; import myblog.blog.category.dto.CategoryNormalDto; import myblog.blog.category.service.CategoryService; -import myblog.blog.comment.dto.CommentDtoForSide; +import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.service.CommentService; import myblog.blog.member.auth.PrincipalDetails; import myblog.blog.member.dto.MemberDto; @@ -311,10 +311,10 @@ public class ArticleController { CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView()); model.addAttribute("category", categoryForView); - List comments = commentService.recentCommentList() + List comments = commentService.recentCommentList() .stream() .map(comment -> - new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret())) + new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret())) .collect(Collectors.toList()); model.addAttribute("commentsList", comments); diff --git a/src/main/java/myblog/blog/article/domain/Article.java b/src/main/java/myblog/blog/article/domain/Article.java index bb2389c..0b10803 100644 --- a/src/main/java/myblog/blog/article/domain/Article.java +++ b/src/main/java/myblog/blog/article/domain/Article.java @@ -36,7 +36,7 @@ public class Article extends BasicEntity { @Column(nullable = false) private String title; - @Column(nullable = false, length = 10000) + @Column(nullable = false, columnDefinition = "TEXT") private String content; @Column(columnDefinition = "bigint default 0",nullable = false) diff --git a/src/main/java/myblog/blog/base/config/SecurityConfig.java b/src/main/java/myblog/blog/base/config/SecurityConfig.java index 8504745..c3200d7 100644 --- a/src/main/java/myblog/blog/base/config/SecurityConfig.java +++ b/src/main/java/myblog/blog/base/config/SecurityConfig.java @@ -43,6 +43,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { // 인가 .authorizeRequests() .antMatchers("/article/write", "/article/edit","/article/delete","/edit/category", "/category/edit").hasRole(Role.ADMIN.name()) + .antMatchers("/comment/write","/comment/delete").authenticated() .anyRequest().permitAll() // 로그아웃 diff --git a/src/main/java/myblog/blog/category/controller/CategoryController.java b/src/main/java/myblog/blog/category/controller/CategoryController.java index 36f97a4..8589e98 100644 --- a/src/main/java/myblog/blog/category/controller/CategoryController.java +++ b/src/main/java/myblog/blog/category/controller/CategoryController.java @@ -1,20 +1,24 @@ package myblog.blog.category.controller; import lombok.RequiredArgsConstructor; +import myblog.blog.exception.CustomFormException; +import myblog.blog.exception.ListValidator; import myblog.blog.category.dto.CategoryForView; import myblog.blog.category.dto.CategoryNormalDto; import myblog.blog.category.service.CategoryService; -import myblog.blog.comment.dto.CommentDtoForSide; +import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.service.CommentService; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Controller @@ -24,6 +28,7 @@ public class CategoryController { private final CategoryService categoryService; private final CommentService commentService; private final ModelMapper modelMapper; + private final ListValidator listValidator; /* - 카테고리 수정폼 조회 @@ -39,10 +44,10 @@ public class CategoryController { CategoryForView categoryForView = CategoryForView.createCategory(categoryList); - List comments = commentService.recentCommentList() + List comments = commentService.recentCommentList() .stream() .map(comment -> - new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret())) + new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret())) .collect(Collectors.toList()); // @@ -58,11 +63,18 @@ public class CategoryController { - 카테고리 수정 요청 */ @PostMapping("/category/edit") - public @ResponseBody String editCategory(@RequestBody List categoryList){ + public @ResponseBody + String editCategory(@RequestBody List categoryList, Errors errors) { + // List DTO 검증을 위한 커스텀 validator + listValidator.validate(categoryList, errors); + + if (errors.hasErrors()) { + throw new CustomFormException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage()); + } + categoryService.changeCategory(categoryList); return "변경 성공"; } - private List cloneList(List categoryList) { return categoryList .stream() diff --git a/src/main/java/myblog/blog/category/dto/CategoryNormalDto.java b/src/main/java/myblog/blog/category/dto/CategoryNormalDto.java index c61839d..70949d4 100644 --- a/src/main/java/myblog/blog/category/dto/CategoryNormalDto.java +++ b/src/main/java/myblog/blog/category/dto/CategoryNormalDto.java @@ -3,6 +3,9 @@ package myblog.blog.category.dto; import lombok.Getter; import lombok.Setter; import lombok.ToString; + +import javax.validation.constraints.NotBlank; + /* - 범용 카테고리 DTO */ @@ -12,6 +15,7 @@ import lombok.ToString; public class CategoryNormalDto { private Long id; + @NotBlank(message = "카테고리명은 공백일 수 없습니다.") private String title; private int tier; private int count; diff --git a/src/main/java/myblog/blog/comment/controller/CommentController.java b/src/main/java/myblog/blog/comment/controller/CommentController.java index 92b32aa..b753b2b 100644 --- a/src/main/java/myblog/blog/comment/controller/CommentController.java +++ b/src/main/java/myblog/blog/comment/controller/CommentController.java @@ -6,12 +6,17 @@ import myblog.blog.article.service.ArticleService; import myblog.blog.comment.dto.CommentDto; import myblog.blog.comment.dto.CommentForm; import myblog.blog.comment.service.CommentService; +import myblog.blog.exception.CustomFormException; import myblog.blog.member.auth.PrincipalDetails; import myblog.blog.member.doamin.Member; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.Errors; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Objects; @RestController @RequiredArgsConstructor @@ -20,25 +25,31 @@ public class CommentController { private final CommentService commentService; private final ArticleService articleService; + /* + - 아티클 조회시 아티클에 달린 댓글들 전체 조회 + */ @GetMapping("/comment/list/{articleId}") public List getCommentList(@PathVariable Long articleId){ - - List commentList = commentService.getCommentList(articleId); - - return commentList; - + return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); } + /* + - 댓글 작성 요청 + */ @PostMapping("/comment/write") public List getCommentList(@RequestParam Long articleId, @RequestParam(required = false) Long parentId, - @RequestBody CommentForm commentForm, - Authentication authentication){ + @Validated @RequestBody CommentForm commentForm, Errors errors, + @AuthenticationPrincipal PrincipalDetails principal){ + + if (errors.hasErrors()) { + throw new CustomFormException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage()); + } - PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal(); Member member = principal.getMember(); Article article = articleService.readArticle(articleId); + // 부모 댓글인지 자식댓글인지 분기로 저장 if(parentId != null){ commentService.saveCComment(commentForm, member, article, parentId); } @@ -46,20 +57,16 @@ public class CommentController { commentService.savePComment(commentForm, member, article); } - List commentList = commentService.getCommentList(articleId); - return commentList; - - + return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); } + /* + - 댓글 삭제 요청 + */ @PostMapping("/comment/delete") public List deleteComment(@RequestParam Long articleId, - @RequestParam Long commentId, - Authentication authentication) { - + @RequestParam Long commentId) { commentService.deleteComment(commentId); - List commentList = commentService.getCommentList(articleId); - return commentList; + return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); } - } diff --git a/src/main/java/myblog/blog/comment/domain/Comment.java b/src/main/java/myblog/blog/comment/domain/Comment.java index 53e0132..b642973 100644 --- a/src/main/java/myblog/blog/comment/domain/Comment.java +++ b/src/main/java/myblog/blog/comment/domain/Comment.java @@ -1,6 +1,5 @@ package myblog.blog.comment.domain; - import lombok.Builder; import lombok.Getter; import myblog.blog.article.domain.Article; @@ -35,7 +34,7 @@ public class Comment extends BasicEntity { private int tier; - // 댓글 표시 순서 + // 댓글 표시 순서 - 자식댓글은 ID순으로 처리 private int pOrder; // 셀프조인 @@ -47,7 +46,6 @@ public class Comment extends BasicEntity { @OnDelete(action = OnDeleteAction.CASCADE) private List child = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; @@ -57,8 +55,6 @@ public class Comment extends BasicEntity { @Column(columnDefinition = "boolean default false") private boolean secret; - - @Builder public Comment(Article article, int tier, Comment parents,int pOrder, Member member, String content, boolean secret) { @@ -71,7 +67,5 @@ public class Comment extends BasicEntity { this.secret = secret; } - protected Comment() { - - } + protected Comment() {} } diff --git a/src/main/java/myblog/blog/comment/dto/CommentDto.java b/src/main/java/myblog/blog/comment/dto/CommentDto.java index 12348a3..0598829 100644 --- a/src/main/java/myblog/blog/comment/dto/CommentDto.java +++ b/src/main/java/myblog/blog/comment/dto/CommentDto.java @@ -1,6 +1,5 @@ package myblog.blog.comment.dto; -import lombok.Builder; import lombok.Getter; import lombok.Setter; import myblog.blog.comment.domain.Comment; @@ -10,6 +9,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; + +/* + - 트리구조 댓글 DTO +*/ @Getter @Setter public class CommentDto { @@ -22,26 +25,33 @@ public class CommentDto { private String picUrl; private String content; private boolean secret; - private List commentDtoList = new ArrayList<>(); private LocalDateTime createdDate; - public static List createFrom(List commentList, int dept) { + // 트리 구조를 갖기위한 리스트 + private List commentDtoList = new ArrayList<>(); + + /* + - 재귀 호출용 스태틱 생성 메서드 + 1. DTO객체 생성후 소스를 큐처리로 순차적 매핑 + 2. Depth 변화시 재귀 호출 / 재귀 탈출 + 3. 탈출시 상위 카테고리 list로 삽입하여 트리구조 작성 + */ + public static List listCreateFrom(List commentSource, int dept) { ArrayList commentDtoList = new ArrayList<>(); while (true) { - - if (commentList.isEmpty()) { + if (commentSource.isEmpty()) { return commentDtoList; } - Comment comment = commentList.get(0); + Comment comment = commentSource.get(0); if (comment.getTier() == dept) { commentDtoList.add(new CommentDto(comment)); - commentList.remove(0); + commentSource.remove(0); } else if (comment.getTier() > dept) { - List childList = createFrom(commentList, dept + 1); + List childList = listCreateFrom(commentSource, dept + 1); commentDtoList.get(commentDtoList.size() - 1) .setCommentDtoList(childList); } else { @@ -50,7 +60,7 @@ public class CommentDto { } } - + // 매핑 생성 public CommentDto(Comment comment) { this.id = comment.getId(); this.tier = comment.getTier(); @@ -63,5 +73,4 @@ public class CommentDto { this.memberId = comment.getMember().getId(); } - } diff --git a/src/main/java/myblog/blog/comment/dto/CommentDtoForSide.java b/src/main/java/myblog/blog/comment/dto/CommentDtoForLayout.java similarity index 68% rename from src/main/java/myblog/blog/comment/dto/CommentDtoForSide.java rename to src/main/java/myblog/blog/comment/dto/CommentDtoForLayout.java index 159b6dc..ef3bad5 100644 --- a/src/main/java/myblog/blog/comment/dto/CommentDtoForSide.java +++ b/src/main/java/myblog/blog/comment/dto/CommentDtoForLayout.java @@ -4,14 +4,18 @@ import lombok.Getter; import lombok.Setter; import myblog.blog.article.domain.Article; + +/* + - 레이아웃 노출용 댓글 DTO +*/ @Getter @Setter -public class CommentDtoForSide { +public class CommentDtoForLayout { private Long id; private Long articleId; private String content; private boolean secret; - public CommentDtoForSide(Long id, Long articleId, String content, boolean secret) { + public CommentDtoForLayout(Long id, Long articleId, String content, boolean secret) { this.id = id; this.secret = secret; this.articleId = articleId; diff --git a/src/main/java/myblog/blog/comment/dto/CommentForm.java b/src/main/java/myblog/blog/comment/dto/CommentForm.java index c120d0d..4a8ed0c 100644 --- a/src/main/java/myblog/blog/comment/dto/CommentForm.java +++ b/src/main/java/myblog/blog/comment/dto/CommentForm.java @@ -2,10 +2,18 @@ package myblog.blog.comment.dto; import lombok.Data; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +/* + - 댓글 작성 폼 +*/ @Data public class CommentForm { -private String content; -private boolean secret; + @NotBlank(message = "댓글 내용을 작성해주세요") + @Size(min = 1,max = 250, message = "댓글은 255자 이내로 작성해주세요") + private String content; + private boolean secret; } diff --git a/src/main/java/myblog/blog/comment/repository/CommentRepository.java b/src/main/java/myblog/blog/comment/repository/CommentRepository.java index 4a960b2..84dbdd7 100644 --- a/src/main/java/myblog/blog/comment/repository/CommentRepository.java +++ b/src/main/java/myblog/blog/comment/repository/CommentRepository.java @@ -10,6 +10,9 @@ import java.util.List; public interface CommentRepository extends JpaRepository { + /* + - 특정 아티클에 해당하는 댓글 리스트 가져오기 + */ @Query("select c " + "from Comment c " + "left join fetch c.member " + @@ -19,7 +22,13 @@ public interface CommentRepository extends JpaRepository { "order by c.pOrder, c.id asc") List findCommentsByArticleId(@Param("articleId") Long articleId); + /* + - 특정 아티클의 부모 댓글 총 갯수 + */ int countCommentsByArticleAndTier(Article article,int tier); + /* + - 전체 댓글중 최신 댓글 5개 + */ List findTop5ByOrderByIdDesc(); } diff --git a/src/main/java/myblog/blog/comment/repository/NaCommentRepository.java b/src/main/java/myblog/blog/comment/repository/NaCommentRepository.java index 5600d4b..0a3d740 100644 --- a/src/main/java/myblog/blog/comment/repository/NaCommentRepository.java +++ b/src/main/java/myblog/blog/comment/repository/NaCommentRepository.java @@ -6,7 +6,9 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface NaCommentRepository { - + /* + - cascade 삭제처리 + */ @Delete("delete from comment " + "where comment_id = #{commentId} ") void deleteComment(Long commentId); diff --git a/src/main/java/myblog/blog/comment/service/CommentService.java b/src/main/java/myblog/blog/comment/service/CommentService.java index bb22bac..c5db40e 100644 --- a/src/main/java/myblog/blog/comment/service/CommentService.java +++ b/src/main/java/myblog/blog/comment/service/CommentService.java @@ -3,38 +3,36 @@ package myblog.blog.comment.service; import lombok.RequiredArgsConstructor; import myblog.blog.article.domain.Article; import myblog.blog.comment.domain.Comment; -import myblog.blog.comment.dto.CommentDto; import myblog.blog.comment.dto.CommentForm; import myblog.blog.comment.repository.CommentRepository; import myblog.blog.comment.repository.NaCommentRepository; import myblog.blog.member.doamin.Member; -import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import java.util.List; -import java.util.Optional; @Service @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; - private final ModelMapper modelMapper; private final NaCommentRepository naCommentRepository; - public List getCommentList(Long articleId){ - - List commentsByArticleId = commentRepository.findCommentsByArticleId(articleId); - - return CommentDto.createFrom(commentsByArticleId,0); - + /* + - 아티클에 달린 댓글 전체 가져오기 + */ + public List getCommentList(Long articleId){ + return commentRepository.findCommentsByArticleId(articleId); } + /* + - 부모 댓글 저장 + */ public void savePComment(CommentForm commentForm, Member member, Article article){ Comment comment = Comment.builder() .article(article) - .content(commentForm.getContent()) + .content(removeDuplicatedEnter(commentForm)) .tier(0) .pOrder(commentRepository.countCommentsByArticleAndTier(article,0)+1) .member(member) @@ -45,19 +43,16 @@ public class CommentService { } - public void deleteComment(Long commentId){ - - naCommentRepository.deleteComment(commentId); - - } - + /* + - 자식 댓글 저장 + */ public void saveCComment(CommentForm commentForm, Member member, Article article, Long parentId) { Comment pComment = commentRepository.findById(parentId).get(); Comment comment = Comment.builder() .article(article) - .content(commentForm.getContent()) + .content(removeDuplicatedEnter(commentForm)) .tier(1) .pOrder(pComment.getPOrder()) .member(member) @@ -69,7 +64,53 @@ public class CommentService { } + /* + - 댓글 삭제 + */ + public void deleteComment(Long commentId){ + naCommentRepository.deleteComment(commentId); + } + + /* + - 최신 댓글 5개 가져오기 + */ public List recentCommentList(){ return commentRepository.findTop5ByOrderByIdDesc(); } + + /* + - 중복 개행 개행 하나로 압축 알고리즘 + */ + private String removeDuplicatedEnter(CommentForm commentForm) { + + char[] contentBox = new char[commentForm.getContent().length()]; + int idx = 0; + String zipWord = "\n\n"; + + for(int i = 0; i< commentForm.getContent().length(); i++){ + + contentBox[idx] = commentForm.getContent().charAt(i); + + if(contentBox[idx] == '\n'&&idx >= 1){ + + int tempIdx = idx; + int length = 1; + boolean isRemove = true; + + for(int j = 0; j<2; j++){ + + if(contentBox[tempIdx--] != zipWord.charAt(length--)){ + + isRemove = false; + break; + + } + + } + if(isRemove) idx -= 1; + } + idx++; + } + return String.valueOf(contentBox).trim(); + } } diff --git a/src/main/java/myblog/blog/exception/CustomFormException.java b/src/main/java/myblog/blog/exception/CustomFormException.java new file mode 100644 index 0000000..ade75b8 --- /dev/null +++ b/src/main/java/myblog/blog/exception/CustomFormException.java @@ -0,0 +1,9 @@ +package myblog.blog.exception; +/* + - REST 컨트롤러 상태 메세지 전송용 커스텀 에러 +*/ +public class CustomFormException extends RuntimeException { + public CustomFormException(String message) { + super(message); + } +} diff --git a/src/main/java/myblog/blog/exception/ExceptionController.java b/src/main/java/myblog/blog/exception/ExceptionController.java deleted file mode 100644 index 5e4ac64..0000000 --- a/src/main/java/myblog/blog/exception/ExceptionController.java +++ /dev/null @@ -1,23 +0,0 @@ -package myblog.blog.exception; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.ControllerAdvice; - -@ControllerAdvice -@Slf4j -public class ExceptionController { - -// @ExceptionHandler -// public String handleRuntimeException(Principal principal, HttpServletRequest req, RuntimeException e) { -// if (principal != null) { -// log.info("'{}' requested '{}' ", principal.getName(), req.getRequestURI()); -// } else { -// log.info("requested '{}'", req.getRequestURI()); -// } -// -// log.error("bad request", e); -// return ""; -// } - - -} diff --git a/src/main/java/myblog/blog/exception/ExceptionRestController.java b/src/main/java/myblog/blog/exception/ExceptionRestController.java new file mode 100644 index 0000000..0836632 --- /dev/null +++ b/src/main/java/myblog/blog/exception/ExceptionRestController.java @@ -0,0 +1,18 @@ +package myblog.blog.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/* + - REST 컨트롤러 요청 에러 제어 +*/ +@RestControllerAdvice +public class ExceptionRestController { + @ExceptionHandler(CustomFormException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public String handleCategoryControllerException(RuntimeException e) { + return e.getMessage(); + } +} diff --git a/src/main/java/myblog/blog/exception/ListValidator.java b/src/main/java/myblog/blog/exception/ListValidator.java new file mode 100644 index 0000000..196b424 --- /dev/null +++ b/src/main/java/myblog/blog/exception/ListValidator.java @@ -0,0 +1,32 @@ +package myblog.blog.exception; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +import java.util.List; + + +/* + - List 순환으로 유효성 검사하게하는 커스텀 Validator +*/ +@Component +@RequiredArgsConstructor +public class ListValidator implements Validator { + + private final SpringValidatorAdapter springValidatorAdapter; + + @Override + public boolean supports(Class clazz) { + return List.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + for(Object object : (List)target){ + springValidatorAdapter.validate(object,errors); + } + } +} diff --git a/src/main/java/myblog/blog/exception/LoginFailHandler.java b/src/main/java/myblog/blog/exception/LoginFailHandler.java index 510231d..ff88d1e 100644 --- a/src/main/java/myblog/blog/exception/LoginFailHandler.java +++ b/src/main/java/myblog/blog/exception/LoginFailHandler.java @@ -13,19 +13,21 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +/* + - 회원가입 실패 핸들러 + 이메일 중복시 간편 회원가입 실패 +*/ @Component -@Slf4j public class LoginFailHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String errMsg = "error"; + String errMsg = ""; if(exception instanceof OAuth2AuthenticationException){ errMsg = "duplicatedEmail"; request.setAttribute("errMsg", errMsg); - } setDefaultFailureUrl("/login?error="+errMsg); diff --git a/src/main/java/myblog/blog/img/controller/UploadImgController.java b/src/main/java/myblog/blog/img/controller/UploadImgController.java index 979d841..aa4a35a 100644 --- a/src/main/java/myblog/blog/img/controller/UploadImgController.java +++ b/src/main/java/myblog/blog/img/controller/UploadImgController.java @@ -1,7 +1,6 @@ package myblog.blog.img.controller; import lombok.RequiredArgsConstructor; -import myblog.blog.img.domain.UploadedImg; import myblog.blog.img.dto.UploadImgDto; import myblog.blog.img.service.UploadImgService; import org.springframework.web.bind.annotation.ModelAttribute; @@ -17,12 +16,12 @@ public class UploadImgController { private final UploadImgService uploadImgService; + /* + - 썸네일 업로드 요청 + */ @PostMapping("/article/uploadImg") public @ResponseBody String imgUpload(@ModelAttribute UploadImgDto uploadImgDto) throws IOException { - return uploadImgService.storeImg(uploadImgDto.getImg()); - } - } diff --git a/src/main/java/myblog/blog/img/domain/UploadedImg.java b/src/main/java/myblog/blog/img/domain/UploadedImg.java deleted file mode 100644 index 4e5cc31..0000000 --- a/src/main/java/myblog/blog/img/domain/UploadedImg.java +++ /dev/null @@ -1,18 +0,0 @@ -package myblog.blog.img.domain; - -import lombok.Getter; -import lombok.Setter; - -@Getter -public class UploadedImg { - - private String uploadFileName; - private String storeFileName; - private String uploadUrl; - - public UploadedImg(String uploadFileName, String storeFileName, String uploadUrl) { - this.uploadFileName = uploadFileName; - this.storeFileName = storeFileName; - this.uploadUrl = uploadUrl; - } -} diff --git a/src/main/java/myblog/blog/img/dto/UploadImgDto.java b/src/main/java/myblog/blog/img/dto/UploadImgDto.java index d77e900..a63fbec 100644 --- a/src/main/java/myblog/blog/img/dto/UploadImgDto.java +++ b/src/main/java/myblog/blog/img/dto/UploadImgDto.java @@ -4,10 +4,11 @@ import lombok.Getter; import lombok.Setter; import org.springframework.web.multipart.MultipartFile; +/* + - 멀티파트 파일 래핑용 DTO +*/ @Getter @Setter public class UploadImgDto { - private MultipartFile img; - } diff --git a/src/main/java/myblog/blog/img/service/UploadImgService.java b/src/main/java/myblog/blog/img/service/UploadImgService.java index 69ed90d..1821167 100644 --- a/src/main/java/myblog/blog/img/service/UploadImgService.java +++ b/src/main/java/myblog/blog/img/service/UploadImgService.java @@ -1,11 +1,9 @@ package myblog.blog.img.service; import lombok.RequiredArgsConstructor; -import myblog.blog.img.domain.UploadedImg; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; -import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -17,6 +15,9 @@ import java.util.UUID; @RequiredArgsConstructor public class UploadImgService { + /* + - 설정 파일로 잡아놓은 깃헙 이미지 레포지토리와 토큰 + */ @Value("${git.gitToken}") private String gitToken; @@ -26,32 +27,38 @@ public class UploadImgService { @Value("${git.imgUrl}") private String imgUrl; + /* + - 이미지 저장 로직 + 1. 깃허브 Repo에 이미지 업로드 + 2. 업로드된 Url 반환 + */ public String storeImg(MultipartFile multipartFile) throws IOException { if (multipartFile.isEmpty()) { throw new IllegalArgumentException("이미지가 존재하지 않습니다."); } + String storeFileName = createStoreFileName(multipartFile.getOriginalFilename()); + GitHub gitHub = new GitHubBuilder().withOAuthToken(gitToken).build(); GHRepository repository = gitHub.getRepository(gitRepo); - - String originalFilename = multipartFile.getOriginalFilename(); - String storeFileName = createStoreFileName(originalFilename); - repository.createContent().path("img/"+storeFileName) - .content(multipartFile.getBytes()).message("test").branch("main").commit(); - - UploadedImg uploadedImg = new UploadedImg(originalFilename, storeFileName, imgUrl + storeFileName + "?raw=true"); - - return uploadedImg.getUploadUrl(); + .content(multipartFile.getBytes()).message("thumbnail").branch("main").commit(); + return imgUrl + storeFileName + "?raw=true"; } + /* + - 이미지 중복 방지용 무작위 파일 이름 생성기 + */ private String createStoreFileName(String originalFilename) { String ext = extractExt(originalFilename); String uuid = UUID.randomUUID().toString(); return uuid + "." + ext; } + /* + - 파일 이름 추출 + */ private String extractExt(String originalFilename) { int pos = originalFilename.lastIndexOf("."); return originalFilename.substring(pos + 1); diff --git a/src/main/java/myblog/blog/main/MainController.java b/src/main/java/myblog/blog/main/MainController.java index 9b88a77..78e388d 100644 --- a/src/main/java/myblog/blog/main/MainController.java +++ b/src/main/java/myblog/blog/main/MainController.java @@ -6,7 +6,7 @@ import myblog.blog.article.dto.ArticleDtoForMain; import myblog.blog.article.service.ArticleService; import myblog.blog.category.dto.CategoryForView; import myblog.blog.category.service.CategoryService; -import myblog.blog.comment.dto.CommentDtoForSide; +import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.service.CommentService; import org.modelmapper.ModelMapper; import org.springframework.data.domain.Slice; @@ -29,33 +29,41 @@ public class MainController { private final CommentService commentService; private final ModelMapper modelMapper; + /* + - 메인 화면 제어용 컨트롤러 + */ @GetMapping("/") public String main(Model model) { + // Dto 전처리 CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView()); - model.addAttribute("category",categoryForView); - List comments = commentService.recentCommentList() + List comments = commentService.recentCommentList() .stream() .map(comment -> - new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret())) + new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret())) .collect(Collectors.toList()); - model.addAttribute("commentsList", comments); List popularArticles = articleService.getPopularArticles() .stream() .map(article -> modelMapper.map(article, ArticleDtoForMain.class)) .collect(Collectors.toList()); - model.addAttribute("popularArticles", popularArticles); Slice recentArticles = articleService.getRecentArticles(0) .map(article -> modelMapper.map(article, ArticleDtoForMain.class)); + // + + model.addAttribute("category",categoryForView); + model.addAttribute("commentsList", comments); + model.addAttribute("popularArticles", popularArticles); model.addAttribute("recentArticles",recentArticles); return "index"; - } + /* + - 최신 아티클 무한스크롤로 조회 + */ @GetMapping("/main/article/{pageNum}") public @ResponseBody List mainNextPage(@PathVariable int pageNum) { @@ -66,5 +74,4 @@ public class MainController { .collect(Collectors.toList()); } - } diff --git a/src/main/java/myblog/blog/member/auth/PrincipalDetails.java b/src/main/java/myblog/blog/member/auth/PrincipalDetails.java index 28ca96d..997c215 100644 --- a/src/main/java/myblog/blog/member/auth/PrincipalDetails.java +++ b/src/main/java/myblog/blog/member/auth/PrincipalDetails.java @@ -1,6 +1,5 @@ package myblog.blog.member.auth; - import myblog.blog.member.doamin.Member; import myblog.blog.member.doamin.Role; import org.springframework.security.core.GrantedAuthority; @@ -12,27 +11,28 @@ import java.util.Arrays; import java.util.Collection; import java.util.Map; +/* + - 멤버 객체를 래핑한 커스텀 Principal 클래스 +*/ public class PrincipalDetails implements OAuth2User { private final Map attributes; private final Member member; - public PrincipalDetails(Member member, Map attributes) { this.member = member; this.attributes = attributes; } + public Member getMember(){return member;} + // 타임리프 뷰단처리를 위한 편의 메소드 public Long getMemberId(){ return member.getId(); } - public String getMemberPicUrl(){ return member.getPicUrl(); } - public Member getMember(){return member;} - // Oauth2 @Override public Map getAttributes() { diff --git a/src/main/java/myblog/blog/member/auth/UserInfoFactory.java b/src/main/java/myblog/blog/member/auth/UserInfoFactory.java index 890c86e..bda5f36 100644 --- a/src/main/java/myblog/blog/member/auth/UserInfoFactory.java +++ b/src/main/java/myblog/blog/member/auth/UserInfoFactory.java @@ -10,27 +10,33 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +/* + - 로그인 루트에 따른 UserInfo 생성 클래스 + 팩터리 메서드 패턴으로 하위타입 반환 +*/ @Component public class UserInfoFactory { + // 하위타입 생성 메소드와 열거타입 매핑 private final static Map> userInfoFactoryMap; - static { userInfoFactoryMap = new EnumMap<>(ProviderType.class); userInfoFactoryMap.put(ProviderType.GOOGLE, GoogleUserInfo::new); userInfoFactoryMap.put(ProviderType.FACEBOOK, FacebookUserInfo::new); - userInfoFactoryMap.put(ProviderType.KAKAO, KakaoUserInfo::new); userInfoFactoryMap.put(ProviderType.NAVER, NaverUserInfo::new); } + // String 파라미터를 열거타입으로 컨버팅하기위한 매핑 private static final Map stringToEnum; - static { stringToEnum = Stream.of(ProviderType.values()) .collect(Collectors.toMap(ProviderType::getValue, providerType->providerType)); } + /* + - 팩토리 메소드 + */ public Oauth2UserInfo makeOauth2UserinfoOf(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) { Optional providerTypeOptional = fromString(oAuth2UserRequest.getClientRegistration().getRegistrationId()); @@ -41,6 +47,10 @@ public class UserInfoFactory { } + /* + - String을 열거타입으로 컨버팅 로직 + 존재하지 않는 요청위험 고려해서 Optional처리 + */ private Optional fromString(String provider){ return Optional.ofNullable(stringToEnum.get(provider)); } diff --git a/src/main/java/myblog/blog/member/auth/userinfo/FacebookUserInfo.java b/src/main/java/myblog/blog/member/auth/userinfo/FacebookUserInfo.java index a57c015..6f4200c 100644 --- a/src/main/java/myblog/blog/member/auth/userinfo/FacebookUserInfo.java +++ b/src/main/java/myblog/blog/member/auth/userinfo/FacebookUserInfo.java @@ -8,7 +8,7 @@ import java.util.Map; public class FacebookUserInfo implements Oauth2UserInfo { - private Map attributes; + private final Map attributes; public FacebookUserInfo(OAuth2User oAuth2User) { this.attributes = oAuth2User.getAttributes(); diff --git a/src/main/java/myblog/blog/member/auth/userinfo/KakaoUserInfo.java b/src/main/java/myblog/blog/member/auth/userinfo/KakaoUserInfo.java deleted file mode 100644 index 3f26479..0000000 --- a/src/main/java/myblog/blog/member/auth/userinfo/KakaoUserInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -package myblog.blog.member.auth.userinfo; - -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Component; - -import java.util.Map; - - -public class KakaoUserInfo implements Oauth2UserInfo { - - private Map attributes; - - public KakaoUserInfo(OAuth2User oAuth2User) { - this.attributes = oAuth2User.getAttributes(); - } - - @Override - public Map getAttributes() { - return this.attributes; - } - - @Override - public String getProviderId() { - return attributes.get("id").toString(); - } - - @Override - public String getProvider() { - return ProviderType.KAKAO.getValue(); - } - - @Override - public String getEmail() { - Map kakao_account = (Map)attributes.get("kakao_account"); - return kakao_account.get("email").toString(); - } - - @Override - public String getUserName() { - Map kakao_account = (Map)attributes.get("kakao_account"); - Map profile = (Map) kakao_account.get("profile"); - return profile.get("nickname").toString()+ "#"+ ((String) attributes.get("id")).substring(0,5); - } - - @Override - public String getPicture() { - Map kakao_account = (Map)attributes.get("kakao_account"); - Map profile = (Map) kakao_account.get("profile"); - return profile.get("profile_image_url").toString(); - - } -} diff --git a/src/main/java/myblog/blog/member/auth/userinfo/NaverUserInfo.java b/src/main/java/myblog/blog/member/auth/userinfo/NaverUserInfo.java index c9ac660..a8b8c20 100644 --- a/src/main/java/myblog/blog/member/auth/userinfo/NaverUserInfo.java +++ b/src/main/java/myblog/blog/member/auth/userinfo/NaverUserInfo.java @@ -8,7 +8,7 @@ import java.util.Map; public class NaverUserInfo implements Oauth2UserInfo { - private Map attributes; + private final Map attributes; public NaverUserInfo(OAuth2User oAuth2User) { this.attributes = oAuth2User.getAttribute("response"); diff --git a/src/main/java/myblog/blog/member/auth/userinfo/Oauth2UserInfo.java b/src/main/java/myblog/blog/member/auth/userinfo/Oauth2UserInfo.java index def7eb0..8b90f15 100644 --- a/src/main/java/myblog/blog/member/auth/userinfo/Oauth2UserInfo.java +++ b/src/main/java/myblog/blog/member/auth/userinfo/Oauth2UserInfo.java @@ -2,6 +2,9 @@ package myblog.blog.member.auth.userinfo; import java.util.Map; +/* + - 팩토리 메서드 패턴을 위한 상위타입 인터페이스 +*/ public interface Oauth2UserInfo { String getProviderId(); diff --git a/src/main/java/myblog/blog/member/auth/userinfo/ProviderType.java b/src/main/java/myblog/blog/member/auth/userinfo/ProviderType.java index 5e8aa17..c5a8f98 100644 --- a/src/main/java/myblog/blog/member/auth/userinfo/ProviderType.java +++ b/src/main/java/myblog/blog/member/auth/userinfo/ProviderType.java @@ -3,13 +3,16 @@ package myblog.blog.member.auth.userinfo; import lombok.Getter; import lombok.RequiredArgsConstructor; + +/* + - 소셜 로그인 API 열거타입 정의 +*/ @RequiredArgsConstructor @Getter public enum ProviderType { FACEBOOK("facebook"), GOOGLE("google"), - KAKAO("kakao"), NAVER("naver"); private final String value; diff --git a/src/main/java/myblog/blog/member/controller/MemberController.java b/src/main/java/myblog/blog/member/controller/MemberController.java index aabb0f6..d33ea74 100644 --- a/src/main/java/myblog/blog/member/controller/MemberController.java +++ b/src/main/java/myblog/blog/member/controller/MemberController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import myblog.blog.category.dto.CategoryForView; import myblog.blog.category.service.CategoryService; -import myblog.blog.comment.dto.CommentDtoForSide; +import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.service.CommentService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -22,26 +22,30 @@ public class MemberController { private final CategoryService categoryService; private final CommentService commentService; + + /* + - 회원 로그인 폼 조회 + */ @GetMapping("/login") public String loginFrom(@RequestParam(value = "error",required = false) String error, Model model){ + // 가입 실패시 에러 메시지 처리 if(error!=null&&error.equals("duplicatedEmail")){ model.addAttribute("errMsg","이미 가입된 이메일입니다."); } + // 레이아웃 DTO 전처리 CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView()); - model.addAttribute("category",categoryForView); - List comments = commentService.recentCommentList() + List comments = commentService.recentCommentList() .stream() .map(comment -> - new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret())) + new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret())) .collect(Collectors.toList()); + // + + model.addAttribute("category",categoryForView); model.addAttribute("commentsList", comments); - return "login"; - } - - } diff --git a/src/main/java/myblog/blog/member/doamin/Member.java b/src/main/java/myblog/blog/member/doamin/Member.java index 0aed78e..6cf1331 100644 --- a/src/main/java/myblog/blog/member/doamin/Member.java +++ b/src/main/java/myblog/blog/member/doamin/Member.java @@ -10,6 +10,10 @@ import javax.persistence.*; import java.util.ArrayList; import java.util.List; + +/* + - 회원 엔티티 +*/ @Entity @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", @@ -47,8 +51,7 @@ public class Member extends BasicEntity { @OneToMany(mappedBy = "member") private List commentList = new ArrayList<>(); - protected Member() { - } + protected Member() {} @Builder public Member(String username, String email, String picUrl, Role role,String userId, String provider, String providerId) { @@ -61,6 +64,11 @@ public class Member extends BasicEntity { this.providerId = providerId; } + //비지니스로직 + + /* + - 유저명 변경 더티체킹 로직 + */ public void changeUsername(String username) { this.username = username; } diff --git a/src/main/java/myblog/blog/member/doamin/Role.java b/src/main/java/myblog/blog/member/doamin/Role.java index c7f8c65..cba0687 100644 --- a/src/main/java/myblog/blog/member/doamin/Role.java +++ b/src/main/java/myblog/blog/member/doamin/Role.java @@ -3,6 +3,11 @@ package myblog.blog.member.doamin; import lombok.Getter; import lombok.RequiredArgsConstructor; +/* + - 멤버 권한 열거 + - ADMIN 계정은 하나만 + - 나머지는 모두 USER계정 +*/ @RequiredArgsConstructor @Getter public enum Role { diff --git a/src/main/java/myblog/blog/member/dto/MemberDto.java b/src/main/java/myblog/blog/member/dto/MemberDto.java index 89d6894..73ea6c8 100644 --- a/src/main/java/myblog/blog/member/dto/MemberDto.java +++ b/src/main/java/myblog/blog/member/dto/MemberDto.java @@ -5,6 +5,9 @@ import lombok.Setter; import javax.persistence.Column; +/* + - 뷰단에 사용할 멈버 DTO +*/ @Getter @Setter public class MemberDto { @@ -18,5 +21,4 @@ public class MemberDto { private String picUrl; - } diff --git a/src/main/java/myblog/blog/member/repository/MemberRepository.java b/src/main/java/myblog/blog/member/repository/MemberRepository.java index c6dfa4f..6e55688 100644 --- a/src/main/java/myblog/blog/member/repository/MemberRepository.java +++ b/src/main/java/myblog/blog/member/repository/MemberRepository.java @@ -5,8 +5,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; public interface MemberRepository extends JpaRepository { - + /* + - Id로 유저 찾기 + */ Member findByUserId(String userId); + /* + - Email로 유저 찾기 + */ Member findByEmail(String email); } diff --git a/src/main/java/myblog/blog/member/service/Oauth2MemberService.java b/src/main/java/myblog/blog/member/service/Oauth2MemberService.java index e6a690f..e07e61f 100644 --- a/src/main/java/myblog/blog/member/service/Oauth2MemberService.java +++ b/src/main/java/myblog/blog/member/service/Oauth2MemberService.java @@ -36,6 +36,10 @@ public class Oauth2MemberService extends DefaultOAuth2UserService { @Value("${admin.provider}") private String adminProvider; + + /* + - OAuth2 인증 로그인 + */ @Override @Transactional public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { @@ -49,12 +53,17 @@ public class Oauth2MemberService extends DefaultOAuth2UserService { return new PrincipalDetails(member, userInfo.getAttributes()); } + /* + - 회원가입 or 로그인 로직 + */ private Member getOrJoinMember(Oauth2UserInfo userInfo) { + //DB에서 조회해서 존재시 로그인처리, 미존재시 가입처리 Member member = memberRepository.findByUserId(userInfo.getProviderId()); if(member == null) { + //Email 중복검증 if(memberRepository.findByEmail(userInfo.getEmail()) != null) throw new OAuth2AuthenticationException("duplicateEmail"); @@ -71,6 +80,7 @@ public class Oauth2MemberService extends DefaultOAuth2UserService { memberRepository.save(member); } + // 유저 네임 변경시 더티체킹으로 유저네임 변경 if(!member.getUsername().equals(userInfo.getUserName())){ member.changeUsername(userInfo.getUserName()); } @@ -78,6 +88,9 @@ public class Oauth2MemberService extends DefaultOAuth2UserService { return member; } + /* + - 앱 구동시 ADMIN 계정 INSERT + */ @PostConstruct public void insertAdmin(){ diff --git a/src/main/java/myblog/blog/tags/domain/ArticleTagList.java b/src/main/java/myblog/blog/tags/domain/ArticleTagList.java index 1feea4b..31f7f71 100644 --- a/src/main/java/myblog/blog/tags/domain/ArticleTagList.java +++ b/src/main/java/myblog/blog/tags/domain/ArticleTagList.java @@ -7,6 +7,9 @@ import myblog.blog.base.domain.BasicEntity; import javax.persistence.*; +/* + - 다 대 다 연관관계 해소 엔티티 +*/ @Entity @Getter @SequenceGenerator( @@ -14,6 +17,7 @@ import javax.persistence.*; sequenceName = "ARTICLE_TAG_LIST_SEQ", initialValue = 1, allocationSize = 50) public class ArticleTagList extends BasicEntity { + @Id @Column(name = "article_tag_list_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ARTICLE_TAG_LIST_SEQ_GENERATOR") @@ -33,7 +37,5 @@ public class ArticleTagList extends BasicEntity { this.tags = tags; } - protected ArticleTagList() { - - } + protected ArticleTagList() {} } diff --git a/src/main/java/myblog/blog/tags/domain/Tags.java b/src/main/java/myblog/blog/tags/domain/Tags.java index 2a81bb1..189dd72 100644 --- a/src/main/java/myblog/blog/tags/domain/Tags.java +++ b/src/main/java/myblog/blog/tags/domain/Tags.java @@ -30,7 +30,5 @@ public class Tags extends BasicEntity { this.name = name; } - protected Tags() { - - } + protected Tags() {} } diff --git a/src/main/java/myblog/blog/tags/dto/TagsDto.java b/src/main/java/myblog/blog/tags/dto/TagsDto.java index 2550f03..c070c9e 100644 --- a/src/main/java/myblog/blog/tags/dto/TagsDto.java +++ b/src/main/java/myblog/blog/tags/dto/TagsDto.java @@ -2,6 +2,9 @@ package myblog.blog.tags.dto; import lombok.Data; + /* + - 뷰단 사용을 위한 DTO + */ @Data public class TagsDto { diff --git a/src/main/java/myblog/blog/tags/repository/ArticleTagListsRepository.java b/src/main/java/myblog/blog/tags/repository/ArticleTagListsRepository.java index 0fccd81..4a02108 100644 --- a/src/main/java/myblog/blog/tags/repository/ArticleTagListsRepository.java +++ b/src/main/java/myblog/blog/tags/repository/ArticleTagListsRepository.java @@ -10,12 +10,13 @@ import org.springframework.transaction.annotation.Transactional; public interface ArticleTagListsRepository extends JpaRepository { - + /* + - 아티클 연관 태그 삭제 쿼리 + - cascade 필요시에는 아티클 삭제로 일괄 삭제하므로 해당쿼리는 연관태그 수정용 + */ @Transactional @Modifying @Query("delete from ArticleTagList t " + "where t.article =:article") void deleteByArticle(@Param("article") Article article); - - } diff --git a/src/main/java/myblog/blog/tags/repository/TagsRepository.java b/src/main/java/myblog/blog/tags/repository/TagsRepository.java index 4939471..5572401 100644 --- a/src/main/java/myblog/blog/tags/repository/TagsRepository.java +++ b/src/main/java/myblog/blog/tags/repository/TagsRepository.java @@ -3,8 +3,10 @@ package myblog.blog.tags.repository; import myblog.blog.tags.domain.Tags; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface TagsRepository extends JpaRepository { - Tags findByName(String name); + Optional findByName(String name); } diff --git a/src/main/java/myblog/blog/tags/service/TagsService.java b/src/main/java/myblog/blog/tags/service/TagsService.java index f59cf4e..89a1555 100644 --- a/src/main/java/myblog/blog/tags/service/TagsService.java +++ b/src/main/java/myblog/blog/tags/service/TagsService.java @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; @Service @Transactional @@ -22,31 +23,42 @@ public class TagsService { private final TagsRepository tagsRepository; private final ArticleTagListsRepository articleTagListsRepository; + /* + - Json 객체로 넘어온 태그들을 파싱해서 신규 태그인경우 저장 + */ public void createNewTagsAndArticleTagList(String names, Article article) { Gson gson = new Gson(); - ArrayList tagsDtoArrayList = gson.fromJson(names, ArrayList.class); + ArrayList> tagsDtoArrayList = gson.fromJson(names, ArrayList.class); // JsonString -> tag - for (Map tags : tagsDtoArrayList) { + for (Map tags : tagsDtoArrayList) { - Tags tag = tagsRepository.findByName(tags.get("value").toString()); - if (tag == null) { - tag = tagsRepository.save(Tags.builder().name(tags.get("value").toString()).build()); + // 신규태그인경우 저장 아닌경우 그대로 조회 + Tags tag = + tagsRepository + .findByName(tags.get("value")) + .orElseGet(() -> + tagsRepository + .save(Tags.builder().name(tags.get("value")).build())); - } + // 아티클 연관 태그로 저장 articleTagListsRepository.save(ArticleTagList.builder() .article(article) - .tags(tag) - .build()); + .tags(tag).build()); } - } + /* + - 전체 태그 조회 + */ public List findAllTags(){ return tagsRepository.findAll(); } + /* + - 아티클 연관 태그 모두 삭제 + */ public void deleteArticleTags(Article article){ articleTagListsRepository.deleteByArticle(article); } diff --git a/src/main/resources/static/css/articleView.css b/src/main/resources/static/css/articleView.css index f77c5ec..72736f6 100644 --- a/src/main/resources/static/css/articleView.css +++ b/src/main/resources/static/css/articleView.css @@ -7,6 +7,7 @@ font-family: 'RIDIBatang','Open Sans', 'Helvetica Neue', 'Helvetica', 'Arial', '나눔바른고딕', 'Nanum Barun Gothic', '맑은고딕', 'Malgun Gothic', sans-serif; z-index: 20; + word-break: break-all; } .toastui-editor-contents *:not(table) { diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css index ee87b73..a7c8881 100644 --- a/src/main/resources/static/css/login.css +++ b/src/main/resources/static/css/login.css @@ -49,11 +49,6 @@ button img{ background-color: #2d4373; } -.btn-kakao{ - background: none; - border: none; -} - .btn-naver{ background: none; border: none; diff --git a/src/main/resources/static/css/mainCss.css b/src/main/resources/static/css/mainCss.css index 5dd87a5..b9cb4b1 100644 --- a/src/main/resources/static/css/mainCss.css +++ b/src/main/resources/static/css/mainCss.css @@ -231,13 +231,16 @@ body::-webkit-scrollbar-track { .arrow-up { visibility: hidden; position: fixed; - bottom: 50px; - right: 50px; - font-size: 50px; border-radius: 100%; border: white; - width: 70px; - height: 70px; + + width: 50px; + height: 50px; + font-size: 30px; + line-height: 50px; + right: 16px; + bottom: 16px; + color: white; background-color: rgb(241, 226, 89); z-index: 5; diff --git a/src/main/resources/static/js/tags.js b/src/main/resources/static/js/tags.js deleted file mode 100644 index 68136a9..0000000 --- a/src/main/resources/static/js/tags.js +++ /dev/null @@ -1,14 +0,0 @@ -const input = document.querySelector('input[name="tags"]'); - -const tagify = new Tagify(input, { - whitelist:whitelist, - maxTags: 10, - dropdown: { - maxItems: 20, - classname: "tags-look", - enabled: 0, - closeOnSelect: true - } -}); - - diff --git a/src/main/resources/templates/admin/categoryEdit.html b/src/main/resources/templates/admin/categoryEdit.html index 8f00dc0..2ffb634 100644 --- a/src/main/resources/templates/admin/categoryEdit.html +++ b/src/main/resources/templates/admin/categoryEdit.html @@ -92,12 +92,17 @@ // DTO 카테고리 셀렉터 let category; - // 카테고리명 중복 검사 - function isDuplicatedCategoryName(){ + // 카테고리명 유효성 검사 + function checkCategoryName(){ const categoryList = document.getElementById("categoryBox").childNodes; for (let i = 0; i - diff --git a/src/main/resources/templates/article/articleWriteForm.html b/src/main/resources/templates/article/articleWriteForm.html index 68d6286..ab579c8 100644 --- a/src/main/resources/templates/article/articleWriteForm.html +++ b/src/main/resources/templates/article/articleWriteForm.html @@ -104,7 +104,6 @@ -
@@ -113,20 +112,14 @@ - + - - + diff --git a/src/main/resources/templates/layout/fragments.html b/src/main/resources/templates/layout/fragments.html index 9af306f..b8e3d1a 100644 --- a/src/main/resources/templates/layout/fragments.html +++ b/src/main/resources/templates/layout/fragments.html @@ -43,16 +43,18 @@ } ); - function commentWrite() { let token = getCsrfToken(); let content = document.getElementById("commentContent").value; - let secret = document.getElementById("isCommentSecret") - let isSecret = secret.checked; + let isSecret = document.getElementById("isCommentSecret").checked; - console.log(isSecret); + // 댓글 내용 유효성 검사 + if(content === ""|| content ===" "){ + alert("댓글을 작성해주세요"); + return; + } - let commentForm = new Object(); + let commentForm = {}; commentForm.content = content; commentForm.secret = isSecret; @@ -63,8 +65,13 @@ xhr.send(JSON.stringify(commentForm)); xhr.onload = () => { - makeCommentBox(xhr, replyBox); - document.getElementById("commentContent").value = ''; + if (xhr.status === 200 || xhr.status === 201 || xhr.status === 202) { + makeCommentBox(xhr, replyBox); + document.getElementById("commentContent").value = ''; + } + else{ + alert(xhr.response); + } } } @@ -74,7 +81,7 @@ let isSecret = document.getElementById("isCommentSecret-"+ parentCommentId).checked; - let commentForm = new Object(); + let commentForm = {}; commentForm.content = content; commentForm.secret = isSecret; @@ -92,7 +99,7 @@ function deleteCommentConfirm(commentId) { - if (confirm("댓글을 정말 삭제하시겠습니까?") == true) { + if (confirm("댓글을 정말 삭제하시겠습니까?") === true) { deleteComment(commentId); } else { return false; @@ -141,7 +148,7 @@
+ class="rounded-circle me-2" alt="">
@@ -159,20 +166,20 @@ `
` - // 비밀댓글 처리 + // 부모 댓글 비밀댓글 처리 if(parentComment.secret === true){ if(roleArr[0].authority === "ROLE_ADMIN") { - replyHtmlSource +=`

${parentComment.content}(비밀댓글입니다)

` + replyHtmlSource +=`

${parentComment.content}(비밀댓글입니다)

` } else if(member != null &&member.id === parentComment.memberId){ - replyHtmlSource +=`

${parentComment.content}(내가 쓴 비밀댓글입니다)

`; + replyHtmlSource +=`

${parentComment.content}(내가 쓴 비밀댓글입니다)

`; } else{ - replyHtmlSource +=`

비밀댓글입니다.

`; + replyHtmlSource +=`

비밀댓글입니다.

`; } } else{ - replyHtmlSource +=`

${parentComment.content}

` + replyHtmlSource +=`

${parentComment.content}

` } @@ -194,7 +201,7 @@
- +
@@ -225,7 +232,7 @@ `
+ height="40" class="rounded-circle me-2" alt="">
@@ -243,20 +250,20 @@ `
` - // 비밀댓글 처리 + // 자식댓글 비밀댓글 처리 if(childComment.secret === true){ - if(roleArr[0].authority === "ROLE_ADMIN") { - replyHtmlSource +=`

${childComment.content}(비밀댓글입니다)

` + if(roleArr[0].authority === "ROLE_ADMIN"||member != null &&member.id === parentComment.memberId) { + replyHtmlSource +=`

${childComment.content}(비밀댓글입니다)

` } else if(member != null &&member.id === childComment.memberId){ - replyHtmlSource +=`

${childComment.content}(내가 쓴 비밀댓글입니다)

`; + replyHtmlSource +=`

${childComment.content}(내가 쓴 비밀댓글입니다)

`; } else{ - replyHtmlSource +=`

비밀댓글입니다.

`; + replyHtmlSource +=`

비밀댓글입니다.

`; } } else{ - replyHtmlSource +=`

${childComment.content}

` + replyHtmlSource +=`

${childComment.content}

` } replyHtmlSource += @@ -402,11 +409,26 @@ @@ -549,7 +571,7 @@ function autoSave(){ let token = getCsrfToken(); - let tempDto = new Object(); + let tempDto = {}; tempDto.content = editor.getMarkdown(); const xhr = new XMLHttpRequest(); diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index df56d15..4079fa5 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -13,22 +13,6 @@ - - - - - - - - - - - - - - - - @@ -67,12 +51,6 @@
-
- - - -
-