21.12.06 리팩토링, 버그 수정

This commit is contained in:
jinia91
2021-12-06 21:47:10 +09:00
parent f45bfa1571
commit 96b3fa4eee
28 changed files with 608 additions and 541 deletions

View File

@@ -1,7 +1,6 @@
package myblog.blog.article.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import myblog.blog.article.domain.Article;
import myblog.blog.article.dto.*;
import myblog.blog.article.service.ArticleService;
@@ -18,15 +17,15 @@ import myblog.blog.tags.service.TagsService;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
@@ -36,210 +35,222 @@ import java.util.stream.Collectors;
@Controller
@RequiredArgsConstructor
@Slf4j
public class ArticleController {
private final ModelMapper modelMapper;
private final ArticleService articleService;
private final TagsService tagsService;
private final CategoryService categoryService;
private final CommentService commentService;
private final Parser parser;
private final HtmlRenderer htmlRenderer;
private final TempArticleService tempArticleService;
private final ModelMapper modelMapper;
private final Parser parser;
private final HtmlRenderer htmlRenderer;
/*
- 아티클 작성 폼 조회
*/
@GetMapping("article/write")
public String writeArticleForm(ArticleForm articleForm, Model model) {
public String writeArticleForm(Model model) {
List<CategoryNormalDto> categoryForInput =
categoryService
.findCategoryByTier(2)
.stream()
.map(category -> modelMapper.map(category, CategoryNormalDto.class))
.collect(Collectors.toList());
model.addAttribute("categoryInput", categoryForInput);
List<TagsDto> tagsForInput =
tagsService
.findAllTags()
.stream()
.map(tag -> new TagsDto(tag.getName()))
.collect(Collectors.toList());
model.addAttribute("tagsInput", tagsForInput);
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret() ))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
model.addAttribute("articleDto", articleForm);
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", new ArticleForm());
return "article/articleWriteForm";
}
/*
- 넘어온 articleForm을 저장
- 아티클 작성 post 요청
*/
@PostMapping("article/write")
@Transactional
public String writeArticle(ArticleForm articleForm, @AuthenticationPrincipal PrincipalDetails principal) {
public String writeArticle(@Validated ArticleForm articleForm,
Errors errors,
@AuthenticationPrincipal PrincipalDetails principal,
Model model) {
Article article = articleService.writeArticle(articleForm, principal.getMember());
if (errors.hasErrors()) {
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", new ArticleForm());
return "article/articleWriteForm";
}
Long articleId = articleService.writeArticle(articleForm, principal.getMember());
// articleService.pushArticleToGithub(article);
tempArticleService.deleteTemp();
return "redirect:/article/view?articleId=" + article.getId();
return "redirect:/article/view?articleId=" + articleId;
}
/*
- 아티클 수정 폼 조회
*/
@GetMapping("/article/edit")
public String updateArticle(@RequestParam Long articleId,
Model model) {
// 기존 아티클 DTO 전처리
Article article = articleService.readArticle(articleId);
ArticleDtoForEdit articleDto = modelMapper.map(article, ArticleDtoForEdit.class);
articleDto.setArticleTagList(article.getArticleTagLists()
.stream()
.map(articleTag -> articleTag.getTags().getName())
.collect(Collectors.toList()));
//
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", articleDto);
return "article/articleEditForm";
}
/*
- 아티클 수정 요청
*/
@PostMapping("/article/edit")
@Transactional
public String editArticle(@RequestParam Long articleId,
@ModelAttribute ArticleForm articleForm) {
articleService.editArticle(articleId, articleForm);
return "redirect:/article/view?articleId=" + articleId;
}
/*
- 아티클 삭제 요청
*/
@PostMapping("/article/delete")
@Transactional
public String deleteArticle(@RequestParam Long articleId) {
articleService.deleteArticle(articleId);
return "redirect:/";
}
/*
- 카테고리별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list")
public String getArticlesList(@RequestParam String category,
@RequestParam Integer tier,
@RequestParam Integer page,
Model model) {
public String getArticlesListByCategory(@RequestParam String category,
@RequestParam Integer tier,
@RequestParam Integer page,
Model model) {
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret()))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
// DTO 매핑 전처리
CategoryForView categoryForView = modelsForLayout(model);
PagingBoxDto pagingBoxDto = PagingBoxDto
.createOf(page, getTotalArticleCntByCategory(category, categoryForView));
Slice<ArticleDtoForMain> articleList = articleService.getArticlesByCategory(category, tier, pagingBoxDto.getCurPageNum())
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
//
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, articleService.getTotalArticleCntByCategory(category, categoryForView));
model.addAttribute("pagingBox", pagingBoxDto);
Slice<ArticleDtoForMain> articleList = articleService.getArticlesByCategory(category, tier, pagingBoxDto.getCurPageNum());
model.addAttribute("articleList", articleList);
return "article/articleList";
}
/*
- 태그별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list/tag/")
public String getArticlesListByTag(@RequestParam Integer page,
@RequestParam String tagName,
Model model) {
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret()))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
// DTO 매핑 전처리
Page<ArticleDtoForMain> articleList =
articleService.getArticlesByTag(tagName, page)
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
model.addAttribute("articleList", articleList);
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, articleList.getTotalPages());
modelsForLayout(model);
//
model.addAttribute("articleList", articleList);
model.addAttribute("pagingBox", pagingBoxDto);
return "article/articleListByTag";
}
/*
- 검색어별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list/search/")
public String getArticlesListByKeyword(@RequestParam Integer page,
@RequestParam String keyword,
Model model) {
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret()))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
// DTO 매핑 전처리
Page<ArticleDtoForMain> articleList =
articleService.getArticlesByKeyword(keyword, page)
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
model.addAttribute("articleList", articleList);
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, articleList.getTotalPages());
modelsForLayout(model);
//
model.addAttribute("articleList", articleList);
model.addAttribute("pagingBox", pagingBoxDto);
return "article/articleListByKeyword";
}
/*
- 아티클 상세 조회
1. 로그인여부 검토
2. 게시물 상세조회에 필요한 Dto 전처리
3. 메타태그 작성위한 Dto 전처리
4. Dto 담기
5. 조회수 증가 검토
*/
@GetMapping("/article/view")
public String readArticle(@RequestParam Long articleId,
Authentication authentication,
@CookieValue(required = false, name = "view") String cookie, HttpServletResponse response,
@AuthenticationPrincipal PrincipalDetails principal,
@CookieValue(required = false, name = "view") String cookie,
HttpServletResponse response,
Model model) {
if (authentication != null) {
PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
MemberDto memberDto = modelMapper.map(principal.getMember(), MemberDto.class);
model.addAttribute("member", memberDto);
// 1. 로그인 여부에 따라 뷰단에 출력 여부 결정
if (principal != null) {
model.addAttribute("member", modelMapper.map(principal.getMember(), MemberDto.class));
} else {
model.addAttribute("member", null);
}
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret()))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
/*
DTO 매핑 전처리
2. 게시물 상세조회용
*/
Article article = articleService.readArticle(articleId);
ArticleDtoForDetail articleDtoForDetail = modelMapper.map(article, ArticleDtoForDetail.class);
ArticleDtoForDetail articleDtoForDetail =
modelMapper.map(article, ArticleDtoForDetail.class);
List<String> tags = article.getArticleTagLists()
.stream()
.map(tag -> tag.getTags().getName())
.collect(Collectors.toList());
articleDtoForDetail
.setTags(tags);
articleDtoForDetail
.setContent(
htmlRenderer.render(parser.parse(article.getContent()))
);
model.addAttribute("article", articleDtoForDetail);
// 메타태그 삽입
StringBuilder sb = new StringBuilder();
for (String tag : tags) {
sb.append(tag).append(", ");
}
model.addAttribute("metaTags",sb);
String substringContents = null;
if(articleDtoForDetail.getContent().length()>200) {
substringContents = articleDtoForDetail.getContent().substring(0, 200);
}
else substringContents = articleDtoForDetail.getContent();
model.addAttribute("metaContents",Jsoup.parse(substringContents).text());
//
articleDtoForDetail.setTags(tags);
articleDtoForDetail.setContent(htmlRenderer.render(parser.parse(article.getContent())));
List<ArticleDtoByCategory> articleTitlesSortByCategory =
articleService
@@ -247,96 +258,80 @@ public class ArticleController {
.stream()
.map(article1 -> modelMapper.map(article1, ArticleDtoByCategory.class))
.collect(Collectors.toList());
// 3. 메타 태그용 Dto 전처리
StringBuilder metaTags = new StringBuilder();
for (String tag : tags) {
metaTags.append(tag).append(", ");
}
String substringContents = null;
if(articleDtoForDetail.getContent().length()>200) {
substringContents = articleDtoForDetail.getContent().substring(0, 200);
}
else substringContents = articleDtoForDetail.getContent();
// 4. 모델 담기
modelsForLayout(model);
model.addAttribute("article", articleDtoForDetail);
model.addAttribute("metaTags",metaTags);
model.addAttribute("metaContents",Jsoup.parse(substringContents).text());
model.addAttribute("articlesSortBycategory", articleTitlesSortByCategory);
// 5. 조회수 증가 검토
addHitWithCookie(article, cookie, response);
return "article/articleView";
}
@GetMapping("/article/edit")
public String updateArticle(@RequestParam Long articleId,
Authentication authentication,
Model model) {
List<CategoryNormalDto> categoryForInput =
/*
- 아티클 폼에 필요한 모델 담기
*/
private void modelsForArticleForm(Model model) {
List<CategoryNormalDto> categoryForForm =
categoryService
.findCategoryByTier(2)
.stream()
.map(c -> modelMapper.map(c, CategoryNormalDto.class))
.map(category -> modelMapper.map(category, CategoryNormalDto.class))
.collect(Collectors.toList());
model.addAttribute("categoryInput", categoryForInput);
model.addAttribute("categoryInput", categoryForForm);
List<TagsDto> tagsForInput =
List<TagsDto> tagsForForm =
tagsService
.findAllTags()
.stream()
.map(c -> modelMapper.map(c, TagsDto.class))
.map(tag -> new TagsDto(tag.getName()))
.collect(Collectors.toList());
model.addAttribute("tagsInput", tagsForInput);
Article article = articleService.getArticleForEdit(articleId);
ArticleDtoForEdit articleDto = modelMapper.map(article, ArticleDtoForEdit.class);
List<String> tagList = article.getArticleTagLists()
.stream()
.map(articleTag -> articleTag.getTags().getName())
.collect(Collectors.toList());
articleDto.setArticleTagList(tagList);
model.addAttribute("articleDto", articleDto);
model.addAttribute("tagsInput", tagsForForm);
}
/*
- 레이아웃에 필요한 모델 담기
*/
private CategoryForView modelsForLayout(Model model) {
CategoryForView categoryForView = CategoryForView.createCategory(categoryService.getCategoryForView());
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret()))
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret()))
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
return "article/articleEditForm";
}
@PostMapping("/article/delete")
@Transactional
public String deleteArticle(@RequestParam Long articleId,
Authentication authentication) {
articleService.deleteArticle(articleId);
return "redirect:/";
return categoryForView;
}
@PostMapping("/article/edit")
@Transactional
public String editArticle(@RequestParam Long articleId,
@ModelAttribute ArticleForm articleForm, @AuthenticationPrincipal PrincipalDetails principal) {
articleService.editArticle(articleId, articleForm);
return "redirect:/article/view?articleId=" + articleId;
}
@GetMapping("/main/article/{pageNum}")
public @ResponseBody
List<ArticleDtoForMain> mainNextPage(@PathVariable int pageNum) {
return articleService.getRecentArticles(pageNum).getContent();
}
/*
- 쿠키 추가 검토
*/
private void addHitWithCookie(Article article, String cookie, HttpServletResponse response) {
Long articleId = article.getId();
if (cookie == null) {
Cookie viewCookie = new Cookie("view", articleId + "/");
viewCookie.setComment("게시물 조회 확인용");
viewCookie.setMaxAge(60 * 60);
articleService.addHit(article);
article.addHit();
response.addCookie(viewCookie);
} else {
boolean isRead = false;
@@ -346,15 +341,33 @@ public class ArticleController {
isRead = true;
break;
}
;
}
if (!isRead) {
cookie += articleId + "/";
articleService.addHit(article);
article.addHit();
}
response.addCookie(new Cookie("view", cookie));
}
}
/*
- 카테고리별 아티클 갯수 구하기
*/
private int getTotalArticleCntByCategory(String category, CategoryForView categorys) {
if (categorys.getTitle().equals(category)) {
return categorys.getCount();
} else {
for (CategoryForView categoryCnt :
categorys.getCategoryTCountList()) {
if (categoryCnt.getTitle().equals(category))
return categoryCnt.getCount();
for (CategoryForView categoryCntSub : categoryCnt.getCategoryTCountList()) {
if (categoryCntSub.getTitle().equals(category))
return categoryCntSub.getCount();
}
}
}
throw new IllegalArgumentException("카테고리별 아티클 수 에러");
}
}

View File

@@ -8,21 +8,29 @@ import org.springframework.web.bind.annotation.*;
import java.util.Optional;
/*
- 임시 게시물 조회, 저장을 위한 rest 컨트롤러
*/
@RestController
@RequiredArgsConstructor
public class TempArticleController {
private final TempArticleService tempArticleService;
/*
- 임시 아티클 저장 요청
*/
@PostMapping("/article/temp/autoSave")
public String autoSaveTemp(@RequestBody TempArticleDto tempArticleDto){
tempArticleService.saveTemp(tempArticleDto);
return "OK";
tempArticleService.saveTemp(new TempArticle(tempArticleDto.getContent()));
return "저장성공";
}
/*
- 임시 아티클 조회
*/
@GetMapping("/article/temp/getTemp")
public @ResponseBody TempArticleDto getTempArticle(){
@@ -32,7 +40,5 @@ public class TempArticleController {
tempArticleDto.setContent(tempArticle.orElse(new TempArticle()).getContent());
return tempArticleDto;
}
}

View File

@@ -15,6 +15,11 @@ import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/*
- 아티클 Entity
- toc 추후 개발 예정
*/
@Entity
@Getter
@SequenceGenerator(
@@ -37,6 +42,7 @@ public class Article extends BasicEntity {
@Column(columnDefinition = "bigint default 0",nullable = false)
private Long hit;
// 추후 개발 예정
private String toc;
@Column(nullable = false)
@@ -67,23 +73,42 @@ public class Article extends BasicEntity {
this.content = content;
this.toc = toc;
this.member = member;
this.thumbnailUrl = thumbnailUrl;
this.thumbnailUrl = makeDefaultThumb(thumbnailUrl);
this.category = category;
this.hit = 0L;
}
// 비지니스 로직 //
/*
- 아티클 수정을 위한 로직
*/
public void editArticle(ArticleForm articleForm, Category category){
this.content = articleForm.getContent();
this.title = articleForm.getTitle();
this.toc = articleForm.getToc();
this.category = category;
if(articleForm.getThumbnailUrl() != null){
this.thumbnailUrl = articleForm.getThumbnailUrl();
}
}
/*
- 아티클 조회수 증가
*/
public void addHit(){
this.hit++;
}
public void editArticle(ArticleForm articleForm, Category category){
this.content = articleForm.getContent();
this.title = articleForm.getTitle();
this.thumbnailUrl = articleForm.getThumbnailUrl();
this.toc = articleForm.getToc();
this.category = category;
}
/*
- 썸네일 기본 작성
*/
private String makeDefaultThumb(String thumbnailUrl) {
String defaultThumbUrl = "https://cdn.pixabay.com/photo/2020/11/08/13/28/tree-5723734_1280.jpg";
if (thumbnailUrl == null || thumbnailUrl.equals("")) {
thumbnailUrl = defaultThumbUrl;
}
return thumbnailUrl;
}
}

View File

@@ -1,20 +1,14 @@
package myblog.blog.article.domain;
import lombok.Builder;
import lombok.Getter;
import myblog.blog.article.dto.ArticleForm;
import myblog.blog.base.domain.BasicEntity;
import myblog.blog.category.domain.Category;
import myblog.blog.comment.domain.Comment;
import myblog.blog.member.doamin.Member;
import myblog.blog.tags.domain.ArticleTagList;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/*
- 임시 아티클 저장 Entity
- 임시 아티클은 한개만 유지할 예정
*/
@Entity
@Getter
public class TempArticle extends BasicEntity {

View File

@@ -3,6 +3,9 @@ package myblog.blog.article.dto;
import lombok.Getter;
import lombok.Setter;
/*
- 카테고리별 게시물 표시용 DTO
*/
@Getter @Setter
public class ArticleDtoByCategory {

View File

@@ -7,6 +7,9 @@ import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.List;
/*
- 아티클 상세조회용 DTO
*/
@Getter @Setter
public class ArticleDtoForDetail {
@@ -19,6 +22,4 @@ public class ArticleDtoForDetail {
private String category;
private List<String> tags;
private LocalDateTime createdDate;
}

View File

@@ -9,6 +9,9 @@ import myblog.blog.tags.dto.TagsDto;
import java.util.ArrayList;
import java.util.List;
/*
- 아티클 수정 폼을 위한 DTO
*/
@Getter @Setter
public class ArticleDtoForEdit {
@@ -20,6 +23,4 @@ public class ArticleDtoForEdit {
private List<String> articleTagList = new ArrayList<>();
private Category category;
}

View File

@@ -6,6 +6,9 @@ import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
/*
- 메인 화면 출력용 아티클 DTO
*/
@Getter
@Setter
public class ArticleDtoForMain {

View File

@@ -11,17 +11,17 @@ import java.util.Objects;
@Getter
public class ArticleForm {
@NotBlank
@NotBlank(message = "제목을 입력해주세요")
private String title;
@NotBlank
@NotBlank(message = "내용을 입력해주세요")
private String content;
private String toc;
private String thumbnailUrl;
@NotBlank
@NotBlank(message = "카테고리를 입력해주세요")
private String category;
@NotBlank
@NotBlank(message = "태그를 하나이상 입력해주세요")
private String tags;
}

View File

@@ -4,7 +4,9 @@ import lombok.Getter;
import lombok.Setter;
import org.springframework.data.domain.Page;
/*
- 뷰단 페이징 박스 처리를 위한 핸들러
*/
@Getter @Setter
public class PagingBoxDto {
@@ -18,6 +20,7 @@ public class PagingBoxDto {
private final int displayPageBoxCnt = 5;
private final int displayArticlePerPage = 5;
// 스태틱 생성 메소드
public static PagingBoxDto createOf(int page, int totalArticles) {
PagingBoxDto box = new PagingBoxDto();
@@ -39,7 +42,6 @@ public class PagingBoxDto {
box.boxStartNum = (box.curPageNum / box.displayPageBoxCnt) * box.displayPageBoxCnt +1;
}
// 페이징 박스 끝번호 계산
box.boxEndNum = (int) (Math.ceil(box.curPageNum / (double) box.displayPageBoxCnt) * box.displayPageBoxCnt);
@@ -51,12 +53,9 @@ public class PagingBoxDto {
box.boxEndNum = 1;
}
// 페이징박스 다음버튼 만드는 마지막 페이지 번호
box.pNumForNextBtn =box.boxEndNum - (box.boxEndNum % box.displayPageBoxCnt);
return box;
}
}

View File

@@ -2,10 +2,11 @@ package myblog.blog.article.dto;
import lombok.Getter;
import lombok.Setter;
/*
- 임시 저장 아티클 조회용 DTO
*/
@Getter
@Setter
public class TempArticleDto {
private String content;
}

View File

@@ -2,30 +2,24 @@ package myblog.blog.article.service;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.article.dto.ArticleDtoForMain;
import myblog.blog.category.domain.Category;
import myblog.blog.member.doamin.Member;
import myblog.blog.article.dto.ArticleForm;
import myblog.blog.article.repository.ArticleRepository;
import myblog.blog.article.repository.NaArticleRepository;
import myblog.blog.category.domain.Category;
import myblog.blog.category.dto.CategoryForView;
import myblog.blog.category.service.CategoryService;
import myblog.blog.member.doamin.Member;
import myblog.blog.member.repository.MemberRepository;
import myblog.blog.tags.service.TagsService;
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.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Value;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
@@ -41,18 +35,17 @@ public class ArticleService {
private final CategoryService categoryService;
private final ArticleRepository articleRepository;
private final NaArticleRepository naArticleRepository;
private final ModelMapper modelMapper;
/*
- 아티클 작성 로직
*/
public Article writeArticle(ArticleForm articleDto, Member writer) {
public Long writeArticle(ArticleForm articleDto, Member writer) {
Article newArticle = articleFrom(articleDto, writer);
articleRepository.save(newArticle);
tagsService.createNewTagsAndArticleTagList(articleDto.getTags(), newArticle);
return newArticle;
return newArticle.getId();
}
/*
@@ -64,7 +57,6 @@ public class ArticleService {
Category category = categoryService.findCategory(articleForm.getCategory());
tagsService.deleteArticleTags(article);
tagsService.createNewTagsAndArticleTagList(articleForm.getTags(), article);
articleForm.setThumbnailUrl(makeDefaultThumb(articleForm.getThumbnailUrl()));
// 더티 체킹으로 업데이트
article.editArticle(articleForm,category);
@@ -73,46 +65,24 @@ public class ArticleService {
/*
- 메인화면 위한 인기 아티클 6개 목록 가져오기
*/
public List<ArticleDtoForMain> getPopularArticles() {
List<Article> top6ByOrderByHitDesc = articleRepository.findTop6ByOrderByHitDesc();
public List<Article> getPopularArticles() {
List<ArticleDtoForMain> articles = top6ByOrderByHitDesc.stream()
.map(article -> modelMapper.map(article, ArticleDtoForMain.class))
.collect(Collectors.toList());
return articles;
return articleRepository.findTop6ByOrderByHitDesc();
}
public Slice<ArticleDtoForMain> getRecentArticles(int page) {
Slice<ArticleDtoForMain> articles = articleRepository
.findByOrderByIdDesc(PageRequest.of(page, 5))
.map(article -> modelMapper
.map(article, ArticleDtoForMain.class));
return articles;
/*
- 메인화면 위한 최신 아티클 페이징처리해서 가져오기
*/
public Slice<Article> getRecentArticles(int page) {
return articleRepository
.findByOrderByIdDesc(PageRequest.of(page, 5));
}
public int getTotalArticleCntByCategory(String category, CategoryForView categorys) {
if (categorys.getTitle().equals(category)) {
return categorys.getCount();
} else {
for (CategoryForView categoryCnt :
categorys.getCategoryTCountList()) {
if (categoryCnt.getTitle().equals(category))
return categoryCnt.getCount();
for (CategoryForView categoryCntSub : categoryCnt.getCategoryTCountList()) {
if (categoryCntSub.getTitle().equals(category))
return categoryCntSub.getCount();
}
}
}
throw new IllegalArgumentException("카테고리별 아티클 수 에러");
}
public Slice<ArticleDtoForMain> getArticlesByCategory(String category, Integer tier, Integer page) {
/*
- 카테고리별 게시물 페이징 처리해서 가져오기
*/
public Slice<Article> getArticlesByCategory(String category, Integer tier, Integer page) {
Slice<Article> articles = null;
@@ -131,57 +101,54 @@ public class ArticleService {
PageRequest.of(pageResolve(page), 5), category);
}
return articles.map(article -> modelMapper
.map(article, ArticleDtoForMain.class));
return articles;
}
/*
- 아티클 읽기 위한 페치로 전체 가져오기
*/
public Article readArticle(Long id){
return articleRepository.findArticleByIdFetchCategoryAndTags(id);
}
public void addHit(Article article) {
article.addHit();
}
public Article getArticleForEdit(Long articleId){
return articleRepository.findArticleByIdFetchCategoryAndTags(articleId);
}
/*
- 아티클 삭제 로직
*/
public void deleteArticle(Long articleId) {
naArticleRepository.deleteArticle(articleId);
}
/*
- 카테고리별 최신게시물 6개만 아티클 상세뷰 위해 가져오는로직
*/
public List<Article> getArticlesByCategoryForDetailView(Category category){
return articleRepository.findTop6ByCategoryOrderByIdDesc(category);
}
/*
- 태그별 게시물 페이징 처리해서 가져오기
*/
public Page<Article> getArticlesByTag(String tag, Integer page) {
Page<Article> articles =
articleRepository
.findAllByArticleTagsOrderById(PageRequest.of(pageResolve(page), 5), tag);
return articles;
return articleRepository
.findAllByArticleTagsOrderById(PageRequest.of(pageResolve(page), 5), tag);
}
/*
- 검색어별 게시물 페이징 처리해서 가져오기
*/
public Page<Article> getArticlesByKeyword(String keyword, Integer page) {
Page<Article> articles =
articleRepository
.findAllByKeywordOrderById(PageRequest.of(pageResolve(page),5), keyword);
return articles;
return articleRepository
.findAllByKeywordOrderById(PageRequest.of(pageResolve(page),5), keyword);
}
/*
- 깃헙에 아티클 푸시하기
*/
public void pushArticleToGithub(Article article) {
try {
GitHub gitHub = new GitHubBuilder().withOAuthToken(gitToken).build();
@@ -198,22 +165,18 @@ public class ArticleService {
}
private String makeDefaultThumb(String thumbnailUrl) {
// 메시지로 올리기
String defaultThumbUrl = "https://cdn.pixabay.com/photo/2020/11/08/13/28/tree-5723734_1280.jpg";
if (thumbnailUrl == null || thumbnailUrl.equals("")) {
thumbnailUrl = defaultThumbUrl;
}
return thumbnailUrl;
}
/*
- 페이지 시작점 0~1변경 메서드
*/
private int pageResolve(Integer rawPage) {
if (rawPage == null || rawPage == 1) {
return 0;
} else return rawPage - 1;
}
/*
- 새로운 아티클 도메인 생성 메서드
*/
private Article articleFrom(ArticleForm articleDto, Member writer) {
return Article.builder()
@@ -221,7 +184,7 @@ public class ArticleService {
.content(articleDto.getContent())
.toc(articleDto.getToc())
.member(writer)
.thumbnailUrl(makeDefaultThumb(articleDto.getThumbnailUrl()))
.thumbnailUrl(articleDto.getThumbnailUrl())
.category(categoryService.findCategory(articleDto.getCategory()))
.build();
}

View File

@@ -2,7 +2,6 @@ package myblog.blog.article.service;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.TempArticle;
import myblog.blog.article.dto.TempArticleDto;
import myblog.blog.article.repository.TempArticleRepository;
import org.springframework.stereotype.Service;
import java.util.Optional;
@@ -17,18 +16,14 @@ public class TempArticleService {
- 자동 저장 로직
- ID값 고정으로 머지를 작동시켜 임시글 DB에 1개 유지
*/
public void saveTemp(TempArticleDto tempArticleDto){
TempArticle tempArticle = new TempArticle(tempArticleDto.getContent());
public void saveTemp(TempArticle tempArticle){
tempArticleRepository.save(tempArticle);
}
/*
- 임시글 가져오기
*/
public Optional<TempArticle> getTempArticle(){
return tempArticleRepository.findById(1L);
}

View File

@@ -8,22 +8,39 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
@Configuration
public class AppConfig {
/*
- DTO <-> 엔티티 매퍼 빈등록
*/
@Bean
public ModelMapper modelMapper(){ return new ModelMapper();}
public ModelMapper modelMapper(){
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true);
return modelMapper;
}
/*
- HTML -> 마크다운 파싱 & 렌더러 빈등록
*/
@Bean
public Parser parser(){
return Parser.builder()
.extensions(List.of(TablesExtension.create()))
.build();
}
@Bean
public Parser parser(){return Parser.builder()
.extensions(Arrays.asList(TablesExtension.create()))
.build();}
@Bean
public HtmlRenderer htmlRenderer(){return HtmlRenderer.builder()
.extensions(Arrays.asList(TablesExtension.create()))
.build();}
public HtmlRenderer htmlRenderer(){
return HtmlRenderer.builder()
.extensions(List.of(TablesExtension.create()))
.build();
}
}

View File

@@ -26,6 +26,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LoginFailHandler loginFailHandler;
private final DataSource dataSource;
/*
- 인가 절차 제외 리소스
*/
@Override
public void configure(WebSecurity web) throws Exception {
web
@@ -37,43 +40,29 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 인가
.authorizeRequests()
.antMatchers("/article/write").hasRole(Role.ADMIN.name())
.antMatchers("/article/write", "/article/edit","/article/delete","/edit/category", "/category/edit").hasRole(Role.ADMIN.name())
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
// 로그아웃
.and()
.logout()
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID","remember-me")
//csrf
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// oauth2 로그인 인증
.and()
.oauth2Login()
.loginPage("/login")
.failureHandler(loginFailHandler)
.userInfoEndpoint()
.userService(oauth2MemberService)
;
}
@Bean
public PersistentTokenRepository tokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
}

View File

@@ -9,7 +9,9 @@ import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
/*
- auditing 용 추상 엔티티
*/
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter

View File

@@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Controller
@RequiredArgsConstructor
@@ -26,41 +25,49 @@ public class CategoryController {
private final CommentService commentService;
private final ModelMapper modelMapper;
/*
- 카테고리 수정폼 조회
*/
@GetMapping("/edit/category")
public String editCategoryForm(Model model) {
// DTO 매핑 전처리
List<CategoryNormalDto> categoryList = categoryService.getCategoryForView();
List<CategoryNormalDto> copyList = categoryList
.stream()
.map(categoryNormalDto ->
modelMapper.map(categoryNormalDto, CategoryNormalDto.class))
.collect(Collectors.toList());
List<CategoryNormalDto> copyList = cloneList(categoryList);
copyList.remove(0);
model.addAttribute("categoryForEdit", copyList);
CategoryForView categoryForView = CategoryForView.createCategory(categoryList);
model.addAttribute("category", categoryForView);
List<CommentDtoForSide> comments = commentService.recentCommentList()
.stream()
.map(comment ->
new CommentDtoForSide(comment.getId(), comment.getArticle().getId(), comment.getContent(),comment.isSecret()))
.collect(Collectors.toList());
//
model.addAttribute("categoryForEdit", copyList);
model.addAttribute("category", categoryForView);
model.addAttribute("commentsList", comments);
return "admin/categoryEdit";
}
/*
- 카테고리 수정 요청
*/
@PostMapping("/category/edit")
public @ResponseBody String editCategory(@RequestBody List<CategoryNormalDto> categoryList){
categoryService.changeCategory(categoryList);
return "ok";
return "변경 성공";
}
private List<CategoryNormalDto> cloneList(List<CategoryNormalDto> categoryList) {
return categoryList
.stream()
.map(categoryNormalDto ->
modelMapper.map(categoryNormalDto, CategoryNormalDto.class))
.collect(Collectors.toList());
}
}

View File

@@ -10,6 +10,9 @@ import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/*
- 카테고리 엔티티
*/
@Entity
@Getter
@SequenceGenerator(
@@ -23,6 +26,7 @@ public class Category extends BasicEntity {
@Column(name = "category_id")
private Long id;
@Column(nullable = false, unique = true)
private String title;
@OneToMany(mappedBy = "category")
@@ -59,14 +63,17 @@ public class Category extends BasicEntity {
return title;
}
public void updateCategory(String title, int tier, int pSortNum, int cSortNum, Category parents){
// 도메인 비지니스 로직
/*
- 카테고리 더티체킹 업데이트
*/
public void updateCategory(String title, int tier, int pSortNum, int cSortNum, Category parents){
this.title = title;
this.tier = tier;
this.pSortNum = pSortNum;
this.cSortNum = cSortNum;
this.parents = parents;
}
}

View File

@@ -7,6 +7,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
- 레이아웃용 트리구조 카테고리 리스트
*/
@Getter
@Setter
public class CategoryForView {
@@ -16,24 +19,30 @@ public class CategoryForView {
private Long id;
private int pOrder;
private int cOrder;
// 트리구조를 갖기 위한 리스트
private List<CategoryForView> categoryTCountList = new ArrayList<>();
/*
- 스태틱 생성 메서드
*/
public static CategoryForView createCategory(List<CategoryNormalDto> crList) {
return recursiveBuildFromCategoryDto(0, crList);
}
private CategoryForView() {
}
private static CategoryForView recursiveBuildFromCategoryDto(int d, List<CategoryNormalDto> crList) {
/*
- 재귀호출로 트리구조 생성
1. DTO객체 생성후 소스를 큐처리로 순차적 매핑
2. Depth 변화시 재귀 호출 / 재귀 탈출
3. 탈출시 상위 카테고리 list로 삽입하여 트리구조 작성
*/
private static CategoryForView recursiveBuildFromCategoryDto(int tier, List<CategoryNormalDto> source) {
CategoryForView categoryForView = new CategoryForView();
while (!crList.isEmpty()) {
CategoryNormalDto cSource = crList.get(0);
while (!source.isEmpty()) {
CategoryNormalDto cSource = source.get(0);
if (cSource.getTier() == d) {
if (cSource.getTier() == tier) {
if(categoryForView.getTitle() != null
&& !categoryForView.getTitle().equals(cSource.getTitle())){
return categoryForView;
@@ -43,17 +52,18 @@ public class CategoryForView {
categoryForView.setId(cSource.getId());
categoryForView.setCOrder(cSource.getCOrder());
categoryForView.setPOrder(cSource.getPOrder());
crList.remove(0);
} else if (cSource.getTier() > d) {
CategoryForView sub = recursiveBuildFromCategoryDto(d + 1, crList);
source.remove(0);
} else if (cSource.getTier() > tier) {
CategoryForView sub = recursiveBuildFromCategoryDto(tier + 1, source);
categoryForView.getCategoryTCountList().add(sub);
} else {
return categoryForView;
}
}
return categoryForView;
}
private CategoryForView() {
}
}

View File

@@ -3,7 +3,9 @@ package myblog.blog.category.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/*
- 범용 카테고리 DTO
*/
@Getter
@Setter
@ToString

View File

@@ -8,10 +8,19 @@ import java.util.List;
public interface CategoryRepository extends JpaRepository<Category, Long> {
/*
- 카테고리 이름으로 카테고리 찾기
*/
Category findByTitle(String title);
/*
- 티어별 카테고리들 가져오기
*/
List<Category> findAllByTierIs(int tier);
/*
- ID == 0 인 더미카테고리를 제외한 모든 카테고리 불러오기
*/
@Query("select c " +
"from Category c " +
"where c.id >0 ")

View File

@@ -11,6 +11,9 @@ import java.util.List;
@Repository
public interface NaCategoryRepository {
/*
- 카테고리별 아티클 갯수 통계 쿼리
*/
@Select("select ifnull(f.title,'total') as title, ifnull(tier,0) as tier, ifnull(f.category_id, 0) as id, ifnull(count,0) as count, ifnull(f.p_sort_num, 0) as pOrder, ifnull(f.c_sort_num, 0) as cOrder\n" +
" from \n" +
" (select ifnull(ifnull(b.title, c.title),'total') as title, count(*) as 'count'\n" +

View File

@@ -21,7 +21,11 @@ public class CategoryService {
private final CategoryRepository categoryRepository;
private final NaCategoryRepository naCategoryRepository;
public Long createNewCategory(String title, String parent, int pOrder, int cOrder, int tier) {
/*
- 새로운 카테고리 생성하기
- 상위 카테고리 존재 유무 분기
*/
public Category createNewCategory(String title, String parent, int pOrder, int cOrder, int tier) {
Category parentCategory = null;
if (parent != null) {
@@ -38,44 +42,73 @@ public class CategoryService {
categoryRepository.save(category);
return category.getId();
return category;
}
/*
- 카테고리 이름으로 카테고리 찾기
*/
public Category findCategory(String title) {
return categoryRepository.findByTitle(title);
}
/*
- 카테고리와 카테고리별 아티클 수 찾기
*/
public List<CategoryNormalDto> getCategoryForView() {
return naCategoryRepository.getCategoryCount();
}
/*
- 티어별 카테고리 목록 찾기
*/
public List<Category> findCategoryByTier(int tier) {
return categoryRepository.findAllByTierIs(tier);
}
/*
- 카테고리 변경 로직
1. 카테고리 리스트의 순서 작성
2. 입력받은 카테고리리스트와 DB의 전체카테고리 리스트 두개를 큐로 처리하여 비교대조
3. 해당 카테고리 변경처리
3-1 해당 카테고리 존재시 더티체킹으로 업데이트 처리
3-2 DB에 존재하지 않는경우 새로 생성
3-3 DB에만 존재하는 카테고리는 삭제처리
*/
@Transactional
public void changeCategory(List<CategoryNormalDto> categoryList) {
// 1.카테고리 리스트 순서 작성
sortingOrder(categoryList);
// 2. 기존 DB 저장된 카테고리 리스트 불러오기
List<Category> allWithoutDummy = categoryRepository.findAllWithoutDummy();
// 3. 카테고리 변경 루프
while (!categoryList.isEmpty()) {
CategoryNormalDto categoryNormalDto = categoryList.get(0);
categoryList.remove(0);
// 부모카테고리인경우
if (categoryNormalDto.getTier() == 1) {
Category pCategory = null;
if (categoryNormalDto.getId() == null) {
Long newCategoryId = createNewCategory(categoryNormalDto.getTitle(), null, categoryNormalDto.getPOrder(), categoryNormalDto.getCOrder(), categoryNormalDto.getTier());
pCategory = categoryRepository.findById(newCategoryId).get();
} else {
// 부모카테고리가 기존에 존재 x
if (categoryNormalDto.getId() == null) {
pCategory
= createNewCategory(categoryNormalDto.getTitle(),
null,
categoryNormalDto.getPOrder(),
categoryNormalDto.getCOrder(),
categoryNormalDto.getTier());
}
// 부모카테고리가 기존에 존재 o
else {
for (int i = 0; i < allWithoutDummy.size(); i++) {
if (allWithoutDummy.get(i).getId().equals(categoryNormalDto.getId())) {
pCategory = allWithoutDummy.get(i);
@@ -83,7 +116,6 @@ public class CategoryService {
break;
}
}
pCategory.updateCategory(categoryNormalDto.getTitle(),
categoryNormalDto.getTier(),
categoryNormalDto.getPOrder(),
@@ -95,18 +127,21 @@ public class CategoryService {
CategoryNormalDto subCategoryDto = categoryList.get(0);
if (subCategoryDto.getTier() == 1) break;
categoryList.remove(0);
// 자식 카테고리인경우
Category cCategory = null;
// 카테고리가 기존에 존재 x
if (subCategoryDto.getId() == null) {
Long newCategoryId = createNewCategory(subCategoryDto.getTitle(),
cCategory = createNewCategory(subCategoryDto.getTitle(),
pCategory.getTitle(),
subCategoryDto.getPOrder(),
subCategoryDto.getCOrder(), subCategoryDto.getTier());
cCategory = categoryRepository.findById(newCategoryId).get();
subCategoryDto.getCOrder(),
subCategoryDto.getTier());
} else {
}
// 카테고리가 기존에 존재 o
else {
for (int i = 0; i < allWithoutDummy.size(); i++) {
if (allWithoutDummy.get(i).getId().equals(subCategoryDto.getId())) {
cCategory = allWithoutDummy.get(i);
@@ -120,32 +155,22 @@ public class CategoryService {
subCategoryDto.getCOrder(),
pCategory);
}
}
}
}
// 3-3 불일치 카테고리 전부 삭제
categoryRepository.deleteAll(allWithoutDummy);
}
/*
- 카테고리 변경을 위해 카테고리의 순번을 작성하는 로직
*/
private void sortingOrder(List<CategoryNormalDto> categoryList) {
int pOrderIndex = 0;
int cOrderIndex = 0;
boolean isTier1 = false;
for (CategoryNormalDto categoryNormalDto : categoryList) {
System.out.println("categoryNormalDto = " + categoryNormalDto);
}
for (int i = 0; i < categoryList.size(); i++) {
CategoryNormalDto categoryDto = categoryList.get(i);
//티어별 트리구조로 순서 작성 로직
for (CategoryNormalDto categoryDto : categoryList) {
if (categoryDto.getTier() == 1) {
cOrderIndex = 0;
@@ -155,21 +180,12 @@ public class CategoryService {
categoryDto.setPOrder(pOrderIndex);
categoryDto.setCOrder(++cOrderIndex);
}
}
for (CategoryNormalDto categoryNormalDto : categoryList) {
System.out.println("categoryNormalDto = " + categoryNormalDto);
}
}
/*
- 최초 더미 카테고리 추가 코드
*/
@PostConstruct
public void insertCategory() {

View File

@@ -8,10 +8,13 @@ import myblog.blog.category.dto.CategoryForView;
import myblog.blog.category.service.CategoryService;
import myblog.blog.comment.dto.CommentDtoForSide;
import myblog.blog.comment.service.CommentService;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.stream.Collectors;
@@ -24,6 +27,7 @@ public class MainController {
private final ArticleService articleService;
private final CategoryService categoryService;
private final CommentService commentService;
private final ModelMapper modelMapper;
@GetMapping("/")
public String main(Model model) {
@@ -38,16 +42,29 @@ public class MainController {
.collect(Collectors.toList());
model.addAttribute("commentsList", comments);
List<ArticleDtoForMain> popularArticles = articleService.getPopularArticles();
List<ArticleDtoForMain> popularArticles = articleService.getPopularArticles()
.stream()
.map(article -> modelMapper.map(article, ArticleDtoForMain.class))
.collect(Collectors.toList());
model.addAttribute("popularArticles", popularArticles);
Slice<ArticleDtoForMain> recentArticles = articleService.getRecentArticles(0);
Slice<ArticleDtoForMain> recentArticles = articleService.getRecentArticles(0)
.map(article -> modelMapper.map(article, ArticleDtoForMain.class));
model.addAttribute("recentArticles",recentArticles);
return "index";
}
@GetMapping("/main/article/{pageNum}")
public @ResponseBody
List<ArticleDtoForMain> mainNextPage(@PathVariable int pageNum) {
return articleService.getRecentArticles(pageNum).getContent()
.stream()
.map(article -> modelMapper.map(article, ArticleDtoForMain.class))
.collect(Collectors.toList());
}
}

View File

@@ -6,40 +6,21 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jinia's Log - 카테고리 편집</title>
<!-- SEO -->
<meta name="description" content=""/>
<meta name="keyword" content=""/>
<meta name="author" content="jinia"/>
<title>카테고리 편집 - Jinia's Log</title>
<meta name="viewport" content="width=device-width, user-scalable = no, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!-- OPEN GRAPH(FACEBOOK, LINKEDIN) -->
<meta property="og:type" content=""/>
<meta property="og:description" content=""/>
<meta property="og:title" content=""/>
<meta property="og:image" content=""/>
<meta property="og:url" content=""/>
<meta property="og:site_name" content=""/>
<!-- twitter -->
<meta property="twitter:card" content=""/>
<meta property="twitter:title" content=""/>
<meta property="twitter:description" content=""/>
<meta property="twitter:image" content=""/>
<meta property="twitter:url" content=""/>
<meta property="twitter:creator" content=""/>
<link rel="icon" href=""/>
<link rel="apple-touch-icon" href=""/>
<link rel="short icon" type="image/x-icon" href=""/>
<link rel="icon" href="/img/favicon-16x16.png"/>
<link rel="apple-touch-icon" href="/img/favicon-16x16.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<!-- CSS RESET -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"/>
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/css/mainCss.css"/>
<link rel="stylesheet" href="/css/login.css"/>
<script src="https://kit.fontawesome.com/233840a552.js" crossorigin="anonymous"></script>
</head>
@@ -65,7 +46,6 @@
th:id="|subCategory-${subCategory.getId()}|"
onclick="clickCategory(this)"></button></th:block></th:block></div>
<div class="col-sm g-0 ms-sm-5">
<div class="mb-5 ">
<label for="categoryName">카테고리 이름 변경</label>
@@ -107,54 +87,72 @@
let idxNewCategoryNum = 0;
const categoryName = document.getElementById("categoryName");
// 셀렉터
let seclector;
function clickCategory(div) {
selector = div;
console.log(categorysList);
categoryName.value = selector.innerText;
// DIV 셀렉터
let selector;
// DTO 카테고리 셀렉터
let category;
// 카테고리명 중복 검사
function isDuplicatedCategoryName(){
const categoryList = document.getElementById("categoryBox").childNodes;
for (let i = 0; i<categoryList.length; i++) {
const curNode = categoryList[i];
for(let j = i+1; j<categoryList.length; j++){
if(curNode.textContent === categoryList[j].textContent){
alert("카테고리 명이 중복되면 안됩니다.");
return true;
}
}
}
return false;
}
function clickCategory(div) {
if(isDuplicatedCategoryName()){
return;
}
selector = div;
categoryName.value = selector.innerText;
category = categorysList.find(x => x.title === selector.innerText);
const childNodes = selector.parentNode.childNodes;
for (const childNode of childNodes) {
childNode.classList.remove("active");
}
selector.classList.add("active");
console.log(selector);
}
categoryName.addEventListener("keyup", () => {
const category = categorysList.find(x => x.title == selector.innerText);
selector.innerText = categoryName.value;
category.title = selector.innerText;
})
function swapToPrevious(title){
const index = categorysList.findIndex(x=>x.title == title);
const index = categorysList.findIndex(x=>x.title === title);
let tmp = categorysList[index-1];
categorysList[index-1] = categorysList[index];
categorysList[index] = tmp;
}
function swapToNextInDto(title){
const index = categorysList.findIndex(x=>x.title == title);
const index = categorysList.findIndex(x=>x.title === title);
let tmp = categorysList[index+1];
categorysList[index+1] = categorysList[index];
categorysList[index] = tmp;
}
function categoryUp() {
const categoryTitle = selector.innerText;
const category = categorysList.find(x => x.title == selector.innerText);
// 선택 카테고리가 부모일때
if (selector.previousSibling != null) {
@@ -168,7 +166,6 @@
function categoryDown() {
const categoryTitle = selector.innerText;
const category = categorysList.find(x => x.title == selector.innerText);
// 선택 카테고리가 부모일때
if (selector.nextSibling.nextSibling != null) {
@@ -183,25 +180,22 @@
}
function tierUp(){
const category = categorysList.find(x => x.title == selector.innerText);
category.tier = 1;
selector.classList.add("fw-bold");
}
function tierDown(){
const category = categorysList.find(x => x.title == selector.innerText);
category.tier = 2;
selector.classList.remove("fw-bold");
}
function addCategory(){
const box = document.getElementById("categoryBox");
box.innerHTML +=`<button class="list-group-item-action list-group-item-light list-group-item" onclick="clickCategory(this)">새 카테고리${idxNewCategoryNum}</button>`
let newCategory = new Object();
let newCategory = {};
newCategory.id = null;
newCategory.title = '새 카테고리'+idxNewCategoryNum++;
newCategory.tier = 2;
@@ -212,17 +206,19 @@
categorysList.push(newCategory);
}
function deleteCategory(){
const categoryIndex = categorysList.findIndex(x=>x.title == selector.innerText);
function deleteCategory(){
const categoryIndex = categorysList.findIndex(x=>x.title === selector.innerText);
categorysList.splice(categoryIndex,1);
selector.remove();
}
function changeCategory(){
if(isDuplicatedCategoryName()){
return;
}
let token = getCsrfToken();
const xhr = new XMLHttpRequest();
xhr.open("POST", "/category/edit");
xhr.setRequestHeader("content-type", "application/json");
@@ -231,7 +227,6 @@
xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 201 || xhr.status === 202) {
location.href='/';
}
else{
@@ -242,7 +237,6 @@
</script>
</section>
</body>

View File

@@ -6,33 +6,15 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jinia's Log - 글 수정</title>
<!-- SEO -->
<meta name="description" content=""/>
<meta name="keyword" content=""/>
<meta name="author" content="jinia"/>
<title>글 수정 - Jinia's Log</title>
<meta name="viewport" content="width=device-width, user-scalable = no, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!-- OPEN GRAPH(FACEBOOK, LINKEDIN) -->
<meta property="og:type" content=""/>
<meta property="og:description" content=""/>
<meta property="og:title" content=""/>
<meta property="og:image" content=""/>
<meta property="og:url" content=""/>
<meta property="og:site_name" content=""/>
<!-- twitter -->
<meta property="twitter:card" content=""/>
<meta property="twitter:title" content=""/>
<meta property="twitter:description" content=""/>
<meta property="twitter:image" content=""/>
<meta property="twitter:url" content=""/>
<meta property="twitter:creator" content=""/>
<link rel="icon" href=""/>
<link rel="apple-touch-icon" href=""/>
<link rel="short icon" type="image/x-icon" href=""/>
<link rel="icon" href="/img/favicon-16x16.png"/>
<link rel="apple-touch-icon" href="/img/favicon-16x16.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<!-- CSS RESET -->
<link rel="stylesheet" href="/node_modules/@toast-ui/editor/dist/toastui-editor.css"/>

View File

@@ -6,33 +6,16 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jinia's Log - 글 작성</title>
<!-- SEO -->
<meta name="description" content=""/>
<meta name="keyword" content=""/>
<meta name="author" content="jinia"/>
<title>글 작성 - Jinia's Log</title>
<meta name="viewport" content="width=device-width, user-scalable = no, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!-- OPEN GRAPH(FACEBOOK, LINKEDIN) -->
<meta property="og:type" content=""/>
<meta property="og:description" content=""/>
<meta property="og:title" content=""/>
<meta property="og:image" content=""/>
<meta property="og:url" content=""/>
<meta property="og:site_name" content=""/>
<!-- twitter -->
<meta property="twitter:card" content=""/>
<meta property="twitter:title" content=""/>
<meta property="twitter:description" content=""/>
<meta property="twitter:image" content=""/>
<meta property="twitter:url" content=""/>
<meta property="twitter:creator" content=""/>
<link rel="icon" href=""/>
<link rel="apple-touch-icon" href=""/>
<link rel="short icon" type="image/x-icon" href=""/>
<link rel="icon" href="/img/favicon-16x16.png"/>
<link rel="apple-touch-icon" href="/img/favicon-16x16.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<!-- CSS RESET -->
<link rel="stylesheet" href="/node_modules/@toast-ui/editor/dist/toastui-editor.css"/>

View File

@@ -356,34 +356,48 @@
}
});
// 유효성 검사
function checkTitle() {
let title = document.getElementById("title");
if (title.value === "") {
return false;
}
return true;
return document.getElementById("title").value !== "";
}
function checkContent() {
return contents.value !== "";
}
function checkCategory() {
return document.getElementById("category").value !== "카테고리를 선택해주세요";
}
function checkTags() {
return document.getElementById("tags").value !== "";
}
// 전송
function post() {
contents.value = editor.getMarkdown();
if (!checkTitle()) {
alert("제목을 입력해주세요")
return;
}
else if (!checkContent()) {
alert("내용을 입력해주세요")
return;
}else if (!checkCategory()) {
alert("카테고리를 입력해주세요")
return;
}else if (!checkTags()) {
alert("태그를 입력해주세요")
return;
}
console.log(thumbUrl.value + "????????")
contents.value = editor.getMarkdown();
document.getElementById("writeArticleForm").submit();
}
editor.setMarkdown([[${articleDto.getContent()}]]);
</script>
@@ -396,10 +410,6 @@
</script>
<script th:fragment="articleWrite" th:inline="javascript" type="application/javascript">
const contents = document.getElementById("content");
@@ -476,7 +486,6 @@
}
});
function uploadImage(blob) {
let token = getCsrfToken();
let formData = new FormData();
@@ -496,30 +505,46 @@
}
// 유효성 검사
function checkTitle() {
let title = document.getElementById("title");
if (title.value === "") {
return false;
}
return true;
return document.getElementById("title").value !== "";
}
function checkContent() {
return contents.value !== "";
}
function checkCategory() {
return document.getElementById("category").value !== "카테고리를 선택해주세요";
}
function checkTags() {
return document.getElementById("tags").value !== "";
}
// 전송
function post() {
contents.value = editor.getMarkdown();
if (!checkTitle()) {
alert("제목을 입력해주세요")
return;
}
else if (!checkContent()) {
alert("내용을 입력해주세요")
return;
}else if (!checkCategory()) {
alert("카테고리를 입력해주세요")
return;
}else if (!checkTags()) {
alert("태그를 입력해주세요")
return;
}
contents.value = editor.getMarkdown();
document.getElementById("writeArticleForm").submit();
}
// 자동 저장 기능
function autoSave(){
@@ -557,7 +582,7 @@
let tmp = JSON.parse(xhr.response);
if(tmp.content != null&& tmp.content != ''){
if(tmp.content != null&& tmp.content !== ''){
let isLoadTmp = confirm("이전에 작성하던 글이 있습니다. 이어서 작성하시겠습니까?");
if(isLoadTmp){
editor.setMarkdown(tmp.content);