From 65199282518f555ffd2f6db00500f3d1d06608ff Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 21:58:50 +0900 Subject: [PATCH 01/28] =?UTF-8?q?feat=20:=20No=20Handler=20Found=20Excepti?= =?UTF-8?q?on=EA=B3=BC=20=EA=B4=80=EB=A0=A8=ED=95=9C=20=EC=85=8B=ED=8C=85?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/controller/RestApiControllerAdvice.java | 9 +-------- src/main/resources/application.yml | 7 +++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java index f8dc0692..5f06dc30 100644 --- a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java +++ b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java @@ -99,14 +99,7 @@ public ResponseDto methodValidException( return ResponseEnum.BAD_REQUEST.getResponse(); } - /* - TODO : [22. 08. 25] NoHandlerFoundException 설정을 진행하여 Parameter가 없는 경우 해당 Exception으로 Return 할 수 있도록 계획함. -

- 테스트는 두가지로 진행 해봄 - 1. DispatcherServlet Bean을 가져와 dispatcherServlet.setThrowExceptionIfNoHandlerFound의 설정을 바꾸는 방법 → Fail - 2. application.yml에서 `spring.mvc.throw-exception-if-no-handler-found`의 설정과, `dispatch-options-request`을 진행하고서 - 해당 메소드로 Handling하려 하였으나 안됨. 추후 다른 방법을 찾아봐야 한다. - */ + //PathVariable의 파라미터가 없는경우 해당 Exception이 실행 된다. @ExceptionHandler({NoHandlerFoundException.class}) public ResponseDto noHandlerFoundExceptionHandling(NoHandlerFoundException e) { log.warn("[NoHandlerFoundExceptionHandling]", e); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3194b8fb..35de242a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,6 +24,13 @@ spring: starttls: enable: true + #NoHandlerException Setting + mvc: + throw-exception-if-no-handler-found: true + web: + resources: + add-mappings: false + #Embedded Tomcat Session TimeOut server: servlet: From 9b22936799bd12af957a04a5c599497a63f705a2 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 22:22:18 +0900 Subject: [PATCH 02/28] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 43 +++++++++++++++++ .../com/nooblol/board/dto/ArticleDto.java | 27 +++++++++++ .../nooblol/board/mapper/ArticleMapper.java | 14 ++++++ .../nooblol/board/service/ArticleService.java | 31 ++++++++++++ .../service/impl/ArticleServiceImpl.java | 48 +++++++++++++++++++ .../board/utils/ArticleAuthMessage.java | 6 +++ .../nooblol/global/utils/SessionUtils.java | 20 ++++++++ src/main/resources/data.sql | 11 +++++ .../mybatis/mapper/board/ArticleMapper.xml | 30 ++++++++++++ 9 files changed, 230 insertions(+) create mode 100644 src/main/java/com/nooblol/board/controller/ArticleController.java create mode 100644 src/main/java/com/nooblol/board/dto/ArticleDto.java create mode 100644 src/main/java/com/nooblol/board/mapper/ArticleMapper.java create mode 100644 src/main/java/com/nooblol/board/service/ArticleService.java create mode 100644 src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java create mode 100644 src/main/java/com/nooblol/board/utils/ArticleAuthMessage.java create mode 100644 src/main/java/com/nooblol/global/utils/SessionUtils.java create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/mybatis/mapper/board/ArticleMapper.xml diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java new file mode 100644 index 00000000..e1468783 --- /dev/null +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -0,0 +1,43 @@ +package com.nooblol.board.controller; + +import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.service.ArticleService; +import com.nooblol.global.dto.ResponseDto; +import com.nooblol.global.utils.ResponseEnum; +import com.nooblol.global.utils.SessionUtils; +import javax.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/article") +@RequiredArgsConstructor +public class ArticleController { + + private final ArticleService articleService; + + /** + * articleId의 게시물 정보와 현재 요청한 사용자의 권한을 같이 Return한다 + * + * @param articleId + * @param session + * @return + */ + @GetMapping("/{articleId}") + public ResponseDto getArticle( + @PathVariable int articleId, HttpSession session + ) { + ArticleDto article = articleService.getArticleInfo( + articleId, SessionUtils.getSessionUserId(session) + ); + + ResponseDto result = ResponseEnum.OK.getResponse(); + result.setResult(article); + return result; + } +} diff --git a/src/main/java/com/nooblol/board/dto/ArticleDto.java b/src/main/java/com/nooblol/board/dto/ArticleDto.java new file mode 100644 index 00000000..9542c09e --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/ArticleDto.java @@ -0,0 +1,27 @@ +package com.nooblol.board.dto; + +import java.sql.Timestamp; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ArticleDto { + + private int articleId; + private int bbsId; + private String articleTitle; + private int articleReadCount; + private String articleContent; + private int status; + private String createdUserId; + private Timestamp createdAt; + private Timestamp updatedAt; + private String authMessage; +} diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java new file mode 100644 index 00000000..075eed4e --- /dev/null +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -0,0 +1,14 @@ +package com.nooblol.board.mapper; + +import com.nooblol.board.dto.ArticleDto; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ArticleMapper { + + ArticleDto selectArticleByArticleId(int articleId); + + void addReadCount(int articleId); + + int selectUserAuth(String userId); +} diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java new file mode 100644 index 00000000..d7bccf0e --- /dev/null +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -0,0 +1,31 @@ +package com.nooblol.board.service; + +import com.nooblol.board.dto.ArticleDto; +import javax.servlet.http.HttpSession; + +public interface ArticleService { + + /** + * 받은 articleId로 조회수를 증가한 이후 게시물정보를 반환한다. + * + * @param articleId 조회해야 하는 게시물 ID + * @param userId 로그인을 한 경우, Session에 저장된 UserId + * @return + */ + ArticleDto getArticleInfo(int articleId, String userId); + + /** + * 해당 사용자가 실제 사용자인지확인후, 게시물에서 행동할 수 있는 권한을 Return한다 + * + * @param userId + * @return + */ + String getUserArticleAuth(String userId); + + /** + * 받은 articleId의 조회수를 1 증가시킨다 + * + * @param articleId + */ + void addReadCount(int articleId); +} diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java new file mode 100644 index 00000000..5f85ebec --- /dev/null +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -0,0 +1,48 @@ +package com.nooblol.board.service.impl; + +import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.service.ArticleService; +import com.nooblol.board.mapper.ArticleMapper; +import com.nooblol.board.utils.ArticleAuthMessage; +import com.nooblol.global.utils.SessionUtils; +import com.nooblol.user.utils.UserRoleStatus; +import javax.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ArticleServiceImpl implements ArticleService { + + private final ArticleMapper articleMapper; + + @Override + public ArticleDto getArticleInfo(int articleId, String userId) { + articleMapper.addReadCount(articleId); + ArticleDto result = articleMapper.selectArticleByArticleId(articleId); + result.setAuthMessage(getUserArticleAuth(userId)); + return result; + } + + @Override + public String getUserArticleAuth(String userId) { + if (StringUtils.isBlank(userId)) { + return ArticleAuthMessage.GUEST.name(); + } + + int userAuthData = articleMapper.selectUserAuth(userId); + if (userAuthData == UserRoleStatus.ADMIN.getRoleValue()) { + return ArticleAuthMessage.ADMIN.name(); + } + + return ArticleAuthMessage.GUEST.name(); + } + + @Override + public void addReadCount(int articleId) { + articleMapper.addReadCount(articleId); + } +} diff --git a/src/main/java/com/nooblol/board/utils/ArticleAuthMessage.java b/src/main/java/com/nooblol/board/utils/ArticleAuthMessage.java new file mode 100644 index 00000000..1f548ba6 --- /dev/null +++ b/src/main/java/com/nooblol/board/utils/ArticleAuthMessage.java @@ -0,0 +1,6 @@ +package com.nooblol.board.utils; + + +public enum ArticleAuthMessage { + GUEST, USER, ADMIN; +} diff --git a/src/main/java/com/nooblol/global/utils/SessionUtils.java b/src/main/java/com/nooblol/global/utils/SessionUtils.java new file mode 100644 index 00000000..5149d07f --- /dev/null +++ b/src/main/java/com/nooblol/global/utils/SessionUtils.java @@ -0,0 +1,20 @@ +package com.nooblol.global.utils; + +import com.nooblol.user.dto.UserDto; +import javax.servlet.http.HttpSession; +import org.springframework.util.ObjectUtils; + +public class SessionUtils { + + + private SessionUtils() { + } + + public static String getSessionUserId(HttpSession session) { + UserDto userAttribute = (UserDto) session.getAttribute(SessionEnum.USER_LOGIN.getValue()); + if (ObjectUtils.isEmpty(userAttribute)) { + return null; + } + return userAttribute.getUserId(); + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 00000000..fcef6a7a --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,11 @@ +INSERT INTO bbs_category(category_name, status, created_user_id, created_at, updated_user_id, + updated_at) +VALUES ('Test Active Category1', 1, 'test-admin-user', now(), 'test-admin-user', now()); + +INSERT INTO bbs (category_id, bbs_name, status, created_user_id, created_at, updated_user_id, + updated_at) +VALUES (1, 'Test Active BBS', 1, 'test-admin-user', now(), 'test-admin-user', now()); + +INSERT INTO bbs_articles(bbs_id, article_title, article_read_count, article_content, status, + created_user_id, created_at, updated_at) +VALUES (1, 'Test Article Title - 1', 0, '내용이웨요', 1, 'test-admin-user', now(), now()); \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml new file mode 100644 index 00000000..b428c0c6 --- /dev/null +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + UPDATE bbs_articles + SET article_read_count = article_read_count + 1 + WHERE article_id = #{articleId} + + \ No newline at end of file From 6f5171bd0ef6f0a3778d5121cccee4a69c602f4f Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 22:23:19 +0900 Subject: [PATCH 03/28] =?UTF-8?q?refactor=20:=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20Import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/board/service/ArticleService.java | 1 - .../java/com/nooblol/board/service/impl/ArticleServiceImpl.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index d7bccf0e..3430f4e6 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -1,7 +1,6 @@ package com.nooblol.board.service; import com.nooblol.board.dto.ArticleDto; -import javax.servlet.http.HttpSession; public interface ArticleService { diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 5f85ebec..5a550030 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -4,9 +4,7 @@ import com.nooblol.board.service.ArticleService; import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; -import com.nooblol.global.utils.SessionUtils; import com.nooblol.user.utils.UserRoleStatus; -import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; From 459a1f25b77127ebf1b328e331506d3748fb624d Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 22:46:02 +0900 Subject: [PATCH 04/28] =?UTF-8?q?refactor=20:=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B8=B0=EB=8A=A5=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nooblol/board/service/impl/ArticleServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 5a550030..664bd297 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -36,7 +36,7 @@ public String getUserArticleAuth(String userId) { return ArticleAuthMessage.ADMIN.name(); } - return ArticleAuthMessage.GUEST.name(); + return ArticleAuthMessage.USER.name(); } @Override From 493fdabd7af80af6eb1770320e4581b62eb61262 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 23:14:47 +0900 Subject: [PATCH 05/28] =?UTF-8?q?feat=20:=20Article=EC=9D=B4=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20Exception=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nooblol/board/service/impl/ArticleServiceImpl.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 664bd297..51b877ac 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -4,11 +4,13 @@ import com.nooblol.board.service.ArticleService; import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; +import com.nooblol.global.exception.ExceptionMessage; import com.nooblol.user.utils.UserRoleStatus; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; @Slf4j @Service @@ -21,6 +23,11 @@ public class ArticleServiceImpl implements ArticleService { public ArticleDto getArticleInfo(int articleId, String userId) { articleMapper.addReadCount(articleId); ArticleDto result = articleMapper.selectArticleByArticleId(articleId); + + if (ObjectUtils.isEmpty(result)) { + throw new IllegalArgumentException(ExceptionMessage.NO_DATA); + } + result.setAuthMessage(getUserArticleAuth(userId)); return result; } From 4f18f054c9961dfab195fda2fa8e8c534dde8848 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 23:15:07 +0900 Subject: [PATCH 06/28] =?UTF-8?q?test=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=99=80=20=EA=B4=80=EB=A0=A8=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ArticleServiceImplTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java new file mode 100644 index 00000000..71077f04 --- /dev/null +++ b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java @@ -0,0 +1,111 @@ +package com.nooblol.board.service.impl; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.mapper.ArticleMapper; +import com.nooblol.board.utils.ArticleAuthMessage; +import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.user.utils.UserRoleStatus; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ArticleServiceImplTest { + + @Mock + private ArticleMapper articleMapper; + + @InjectMocks + private ArticleServiceImpl articleService; + + @Test + @DisplayName("UserId가 공백인 경우, 결과값이 GUEST로 반환된다") + void getUserArticleAuth_WhenUserIdIsBlankThenReturnGuest() { + //given + + //when + String result = articleService.getUserArticleAuth(""); + + //then + assertEquals(result, ArticleAuthMessage.GUEST.name()); + } + + @Test + @DisplayName("UserId가 관리자인 경우, 결과값이 ADMIN으로 반환된다.") + void getUserArticleAuth_WhenUserIdAdminThenReturnAdmin() { + //given + String adminUserId = "test-admin-user"; + + //mock + when(articleMapper.selectUserAuth(adminUserId)).thenReturn(UserRoleStatus.ADMIN.getRoleValue()); + + //when + String result = articleService.getUserArticleAuth(adminUserId); + + //then + assertEquals(result, ArticleAuthMessage.ADMIN.name()); + } + + @Test + @DisplayName("UserId가 일반 사용자 경우, 결과값이 USER로 반환된다.") + void getUserArticleAuth_WhenUserIdUserThenReturnUser() { + //given + String userId = "test-user"; + + //mock + when(articleMapper.selectUserAuth(userId)).thenReturn(UserRoleStatus.AUTH_USER.getRoleValue()); + + //when + String result = articleService.getUserArticleAuth(userId); + + //then + assertEquals(result, ArticleAuthMessage.USER.name()); + } + + @Test + @DisplayName("articleId가 실제로 존재하지 않는 경우 Exception이 발생한다") + void getArticleInfo_WhenArticleIsNullThenNoDataException() { + //given + int emptyArticleId = 99999; + + //mock + when(articleMapper.selectArticleByArticleId(emptyArticleId)).thenReturn(null); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleService.getArticleInfo(emptyArticleId, ""); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.NO_DATA); + } + + @Test + @DisplayName("articleId가 실제로 존재하는 경우, 게시물 정보를 반환한다.") + void getArticleInfo_WhenArticleIdHaveDataThenReturnArticleDto() { + //given + int articleId = 1; + + ArticleDto mockData = new ArticleDto().builder() + .articleId(articleId) + .bbsId(1) + .articleTitle("Test Title") + .status(1) + .build(); + + //mock + when(articleMapper.selectArticleByArticleId(articleId)).thenReturn(mockData); + + //when + ArticleDto result = articleService.getArticleInfo(articleId, ""); + + //then + assertEquals(result, mockData); + } +} \ No newline at end of file From d081ecfbb82b86928b72e4731e66f005f7999733 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Wed, 7 Sep 2022 23:53:38 +0900 Subject: [PATCH 07/28] =?UTF-8?q?feat=20:=20timestamp=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/board/dto/ArticleDto.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/nooblol/board/dto/ArticleDto.java b/src/main/java/com/nooblol/board/dto/ArticleDto.java index 9542c09e..82c74c33 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleDto.java @@ -1,6 +1,7 @@ package com.nooblol.board.dto; import java.sql.Timestamp; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -21,7 +22,7 @@ public class ArticleDto { private String articleContent; private int status; private String createdUserId; - private Timestamp createdAt; - private Timestamp updatedAt; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; private String authMessage; } From 53486b241f2cfda50fd658eeb263aa3eeab72b6a Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Thu, 8 Sep 2022 16:25:22 +0900 Subject: [PATCH 08/28] =?UTF-8?q?feat=20:=20Session=EC=9D=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=97=AC=EB=B6=80=20Check=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20AOP=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../global/annotation/UserLoginCheck.java | 5 +++ .../global/config/AuthCheckAspect.java | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/main/java/com/nooblol/global/annotation/UserLoginCheck.java create mode 100644 src/main/java/com/nooblol/global/config/AuthCheckAspect.java diff --git a/build.gradle b/build.gradle index a8e74caf..9be71c06 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail' + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop' implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.0.6' implementation group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.2.2' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis' diff --git a/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java b/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java new file mode 100644 index 00000000..a88fcbb6 --- /dev/null +++ b/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java @@ -0,0 +1,5 @@ +package com.nooblol.global.annotation; + +public @interface UserLoginCheck { + +} diff --git a/src/main/java/com/nooblol/global/config/AuthCheckAspect.java b/src/main/java/com/nooblol/global/config/AuthCheckAspect.java new file mode 100644 index 00000000..ab45b28f --- /dev/null +++ b/src/main/java/com/nooblol/global/config/AuthCheckAspect.java @@ -0,0 +1,36 @@ +package com.nooblol.global.config; + +import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.global.utils.SessionUtils; +import javax.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Aspect +@Slf4j +@Component +public class AuthCheckAspect { + + /** + * 사용자로그인이 필요한 기능에서 AOP를 활용하여 사전에 로그인이 안된 사용자를 거름 + * @param jp + */ + @Before("@annotation(com.nooblol.global.annotation.UserLoginCheck)") + public void memberLoginCheck(JoinPoint jp) throws Throwable { + log.debug("AOP - memberLoginCheck Running"); + + HttpSession session = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest() + .getSession(); + + String userId = SessionUtils.getSessionUserId(session); + if (StringUtils.isBlank(userId)) { + throw new IllegalArgumentException(ExceptionMessage.UNAUTHORIZED); + } + } +} From b82547374da4e870f4145d7a72f6c833711baf7b Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Thu, 8 Sep 2022 16:26:48 +0900 Subject: [PATCH 09/28] =?UTF-8?q?feat=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=B4=20=EC=95=88=EB=90=9C=EA=B2=BD=EC=9A=B0=20UNAUTH?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20Return?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20AOP=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/NoobLoLApplication.java | 8 +++++--- .../global/controller/RestApiControllerAdvice.java | 11 +++++++++-- .../nooblol/global/exception/ExceptionMessage.java | 2 +- .../java/com/nooblol/global/utils/ResponseEnum.java | 4 ++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/nooblol/NoobLoLApplication.java b/src/main/java/com/nooblol/NoobLoLApplication.java index f87b5fef..f499c8dc 100644 --- a/src/main/java/com/nooblol/NoobLoLApplication.java +++ b/src/main/java/com/nooblol/NoobLoLApplication.java @@ -2,12 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +@EnableAspectJAutoProxy @SpringBootApplication public class NoobLoLApplication { - public static void main(String[] args) { - SpringApplication.run(NoobLoLApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(NoobLoLApplication.class, args); + } } diff --git a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java index 5f06dc30..1560781c 100644 --- a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java +++ b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java @@ -43,10 +43,14 @@ public ResponseDto constraintViolationException( return ResponseEnum.BAD_REQUEST.getResponse(); } + /* + TODO : [22. 09. 08] + 지금 현재 전부 IllegalArgumentException으로 방향을 잡고있는데 그러면 이 Exception을 처리하는 메소드가 + 혼자서 모든 부하를 담당하는게 되는데 처리에 있어서 분산을 하는게 맞지 않을까? 하는 고민이 든다. + */ @ExceptionHandler({IllegalArgumentException.class}) public ResponseDto illegalArgumentException( - IllegalArgumentException e, - HttpServletRequest request + IllegalArgumentException e, HttpServletRequest request ) { if (!ObjectUtils.isEmpty(e)) { log.warn( @@ -68,6 +72,9 @@ public ResponseDto illegalArgumentException( rtn.setResult("이미 존재하는 데이터 입니다"); return rtn; + case ExceptionMessage.UNAUTHORIZED: + return ResponseEnum.UNAUTHORIZED.getResponse(); + default: return ResponseEnum.BAD_REQUEST.getResponse(); } diff --git a/src/main/java/com/nooblol/global/exception/ExceptionMessage.java b/src/main/java/com/nooblol/global/exception/ExceptionMessage.java index 37a176c9..4cea47b9 100644 --- a/src/main/java/com/nooblol/global/exception/ExceptionMessage.java +++ b/src/main/java/com/nooblol/global/exception/ExceptionMessage.java @@ -10,6 +10,6 @@ public class ExceptionMessage { public static final String BAD_REQUEST = "BAD_REQUEST"; public static final String SERVER_ERROR = "SERVER_ERROR"; public static final String HAVE_DATA = "HAVE_DATA"; - public static final String FORBIDDEN = "FORBIDDEN"; + public static final String UNAUTHORIZED = "UNAUTHORIZED"; } diff --git a/src/main/java/com/nooblol/global/utils/ResponseEnum.java b/src/main/java/com/nooblol/global/utils/ResponseEnum.java index 7111de35..9989ef75 100644 --- a/src/main/java/com/nooblol/global/utils/ResponseEnum.java +++ b/src/main/java/com/nooblol/global/utils/ResponseEnum.java @@ -10,8 +10,12 @@ @Getter public enum ResponseEnum { BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST), + NOT_FOUND(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR), + + UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED), + OK(HttpStatus.OK.value(), null), CONFLICT(HttpStatus.CONFLICT.value(), null); From 14468d33a8595693be0d9121052136ae3f94c904 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:06:24 +0900 Subject: [PATCH 10/28] =?UTF-8?q?feat=20:=20SessionUtils=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20UserRole?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/global/utils/SessionUtils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/nooblol/global/utils/SessionUtils.java b/src/main/java/com/nooblol/global/utils/SessionUtils.java index 5149d07f..d1613a7d 100644 --- a/src/main/java/com/nooblol/global/utils/SessionUtils.java +++ b/src/main/java/com/nooblol/global/utils/SessionUtils.java @@ -17,4 +17,12 @@ public static String getSessionUserId(HttpSession session) { } return userAttribute.getUserId(); } + + public static Integer getSessionUserRole(HttpSession session) { + UserDto userAttribute = (UserDto) session.getAttribute(SessionEnum.USER_LOGIN.getValue()); + if (ObjectUtils.isEmpty(userAttribute)) { + return null; + } + return userAttribute.getUserRole(); + } } From 17ac112463df8e59228f7a8718135ee09794f934 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:08:19 +0900 Subject: [PATCH 11/28] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=EC=9D=98=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 63 +++++++++++++++++++ .../com/nooblol/board/dto/ArticleDto.java | 8 ++- .../board/dto/ArticleInsertRequestDto.java | 23 +++++++ .../board/dto/ArticleRequestBaseDto.java | 29 +++++++++ .../board/dto/ArticleUpdateRequestDto.java | 24 +++++++ .../nooblol/board/mapper/ArticleMapper.java | 6 ++ .../nooblol/board/service/ArticleService.java | 16 +++++ .../service/impl/ArticleServiceImpl.java | 30 +++++++++ .../nooblol/board/utils/ArticleMessage.java | 18 ++++++ .../board/utils/ArticleStatusEnum.java | 17 +++++ .../nooblol/board/utils/BoardStatusEnum.java | 1 + src/main/resources/data.sql | 17 ++++- .../mybatis/mapper/board/ArticleMapper.xml | 29 +++++++++ src/main/resources/schema.sql | 3 +- 14 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/nooblol/board/dto/ArticleInsertRequestDto.java create mode 100644 src/main/java/com/nooblol/board/dto/ArticleRequestBaseDto.java create mode 100644 src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java create mode 100644 src/main/java/com/nooblol/board/utils/ArticleMessage.java create mode 100644 src/main/java/com/nooblol/board/utils/ArticleStatusEnum.java diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index e1468783..34de1f21 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -1,15 +1,21 @@ package com.nooblol.board.controller; import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.ArticleInsertRequestDto; +import com.nooblol.board.dto.ArticleUpdateRequestDto; import com.nooblol.board.service.ArticleService; +import com.nooblol.global.annotation.UserLoginCheck; import com.nooblol.global.dto.ResponseDto; import com.nooblol.global.utils.ResponseEnum; import com.nooblol.global.utils.SessionUtils; import javax.servlet.http.HttpSession; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -40,4 +46,61 @@ public ResponseDto getArticle( result.setResult(article); return result; } + + /** + * 게시물 등록 + * + * @param articleDto + * @return + */ + @UserLoginCheck + @PostMapping("/insert") + public ResponseDto insertArticle( + @Valid @RequestBody ArticleInsertRequestDto articleDto, HttpSession session + ) { + ArticleDto upsertArticle = new ArticleDto().builder() + .articleId(articleService.getNewArticleId()) + .bbsId(articleDto.getBbsId()) + .articleTitle(articleDto.getArticleTitle()) + .articleContent(articleDto.getArticleContent()) + .articleReadCount(articleDto.getArticleReadCount()) + .status(articleDto.getStatus()) + .createdUserId(SessionUtils.getSessionUserId(session)) + .createdAt(articleDto.getCreatedAt()) + .updatedAt(articleDto.getUpdatedAt()) + .build(); + + boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); + + ResponseDto result = ResponseEnum.OK.getResponse(); + result.setResult(upsertResult); + return result; + } + + /** + * 게시물 수정 + * + * @param articleDto + * @return + */ + @UserLoginCheck + @PostMapping("/update") + public ResponseDto updateArticle( + @Valid @RequestBody ArticleUpdateRequestDto articleDto, HttpSession session + ) { + ArticleDto upsertArticle = new ArticleDto().builder() + .articleId(articleDto.getArticleId()) + .bbsId(articleDto.getBbsId()) + .articleTitle(articleDto.getArticleTitle()) + .articleContent(articleDto.getArticleContent()) + .status(articleDto.getStatus()) + .updatedAt(articleDto.getUpdatedAt()) + .build(); + + boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); + + ResponseDto result = ResponseEnum.OK.getResponse(); + result.setResult(upsertResult); + return result; + } } diff --git a/src/main/java/com/nooblol/board/dto/ArticleDto.java b/src/main/java/com/nooblol/board/dto/ArticleDto.java index 82c74c33..158d710a 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleDto.java @@ -15,12 +15,16 @@ @Builder public class ArticleDto { + /* + Integer로 변경하는 이유는 기본값 0이 들어감으로 인해서 Mybatis에서 NullCheck를 못하는 경우를 제외하기 위해 변경 + */ + private int articleId; - private int bbsId; + private Integer bbsId; private String articleTitle; private int articleReadCount; private String articleContent; - private int status; + private Integer status; private String createdUserId; private LocalDateTime createdAt; private LocalDateTime updatedAt; diff --git a/src/main/java/com/nooblol/board/dto/ArticleInsertRequestDto.java b/src/main/java/com/nooblol/board/dto/ArticleInsertRequestDto.java new file mode 100644 index 00000000..17b26933 --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/ArticleInsertRequestDto.java @@ -0,0 +1,23 @@ +package com.nooblol.board.dto; + +import java.time.LocalDateTime; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ArticleInsertRequestDto extends ArticleRequestBaseDto { + + private String createdUserId; + private Integer articleReadCount = 0; + private LocalDateTime createdAt = LocalDateTime.now(); + private LocalDateTime updatedAt = LocalDateTime.now(); +} + diff --git a/src/main/java/com/nooblol/board/dto/ArticleRequestBaseDto.java b/src/main/java/com/nooblol/board/dto/ArticleRequestBaseDto.java new file mode 100644 index 00000000..b6f2ab73 --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/ArticleRequestBaseDto.java @@ -0,0 +1,29 @@ +package com.nooblol.board.dto; + + +import com.nooblol.board.utils.ArticleMessage; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ArticleRequestBaseDto { + + @NotNull(message = ArticleMessage.BBS_ID_NULL) + private Integer bbsId; + + @NotBlank(message = ArticleMessage.ARTICLE_TITLE_NULL) + private String articleTitle; + + @NotBlank(message = ArticleMessage.ARTICLE_CONTENT_NULL) + private String articleContent; + + @NotNull(message = ArticleMessage.ARTICLE_STATUS_NULL) + private Integer status; +} diff --git a/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java new file mode 100644 index 00000000..f1d5dceb --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java @@ -0,0 +1,24 @@ +package com.nooblol.board.dto; + +import com.nooblol.board.utils.ArticleMessage; +import java.time.LocalDateTime; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ArticleUpdateRequestDto extends ArticleRequestBaseDto { + + @NotNull(message = ArticleMessage.ARTICLE_ID_NULL) + private Integer articleId; + + private LocalDateTime updatedAt = LocalDateTime.now(); +} + diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index 075eed4e..0149d9fa 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -11,4 +11,10 @@ public interface ArticleMapper { void addReadCount(int articleId); int selectUserAuth(String userId); + + int upsertArticle(ArticleDto articleDto); + + int selectMaxArticleId(); + + String selectCreatedUserId(int articleId); } diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index 3430f4e6..153ff15c 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -1,6 +1,7 @@ package com.nooblol.board.service; import com.nooblol.board.dto.ArticleDto; +import javax.servlet.http.HttpSession; public interface ArticleService { @@ -27,4 +28,19 @@ public interface ArticleService { * @param articleId */ void addReadCount(int articleId); + + /** + * 게시물 Upsert + * + * @param articleDto + * @return + */ + boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isUpdate); + + /** + * 현재 DB에서 사용주인 ArticleId의 최대값에 + 1을 하여 return한다. 만약 ArticleId가 없는 경우에는 1을 반환한다. + * + * @return + */ + int getNewArticleId(); } diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 51b877ac..cab5e690 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -5,7 +5,9 @@ import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.global.utils.SessionUtils; import com.nooblol.user.utils.UserRoleStatus; +import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -50,4 +52,32 @@ public String getUserArticleAuth(String userId) { public void addReadCount(int articleId) { articleMapper.addReadCount(articleId); } + + @Override + public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isUpdate) { + + Integer userRole = SessionUtils.getSessionUserRole(session); + + //UserLoginCheck의 Annotation을 통해 무조건 Session로그인이 확인된 상황이기에, Role이 Null이 올 수 없음 + if (userRole == UserRoleStatus.ADMIN.getRoleValue() || !isUpdate) { + return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + } + + String dbCreatedUserId = articleMapper.selectCreatedUserId(articleDto.getArticleId()); + try { + if (StringUtils.isBlank(dbCreatedUserId) || + !dbCreatedUserId.equals(SessionUtils.getSessionUserId(session))) { + throw new IllegalArgumentException(ExceptionMessage.FORBIDDEN); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + } + + @Override + public int getNewArticleId() { + return articleMapper.selectMaxArticleId(); + } } diff --git a/src/main/java/com/nooblol/board/utils/ArticleMessage.java b/src/main/java/com/nooblol/board/utils/ArticleMessage.java new file mode 100644 index 00000000..2d11a46e --- /dev/null +++ b/src/main/java/com/nooblol/board/utils/ArticleMessage.java @@ -0,0 +1,18 @@ +package com.nooblol.board.utils; + +public class ArticleMessage { + + private ArticleMessage() { + } + + public static final String BBS_ID_NULL = "게시판ID가 입력되지 않았습니다."; + + public static final String ARTICLE_ID_NULL = "게시글ID가 입력되지 않았습니다."; + + public static final String ARTICLE_TITLE_NULL = "게시글 제목이 입력되지 않았습니다."; + + public static final String ARTICLE_CONTENT_NULL = "게시글 제목이 입력되지 않았습니다."; + + public static final String ARTICLE_STATUS_NULL = "해당 게시물의 타입이 입력되지 않았습니다."; + +} diff --git a/src/main/java/com/nooblol/board/utils/ArticleStatusEnum.java b/src/main/java/com/nooblol/board/utils/ArticleStatusEnum.java new file mode 100644 index 00000000..f8be4683 --- /dev/null +++ b/src/main/java/com/nooblol/board/utils/ArticleStatusEnum.java @@ -0,0 +1,17 @@ +package com.nooblol.board.utils; + +import lombok.Getter; + +@Getter +public enum ArticleStatusEnum { + ACTIVE(1), SECRET(2); + + + ArticleStatusEnum(int status) { + this.status = status; + } + + int status; + + +} diff --git a/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java b/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java index 85744699..4cedec9c 100644 --- a/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java +++ b/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java @@ -6,6 +6,7 @@ public enum BoardStatusEnum { ACTIVE(1), DEACTIVE(2); + BoardStatusEnum(int status) { this.status = status; } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index fcef6a7a..4995bddf 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,3 +1,10 @@ +/*password : abc*/ +INSERT INTO users(user_id, user_email, user_name, user_password_hash, user_role, level, exp, + created_at, updated_at) +VALUES ('test', 'test@test.com', 'test', + '3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==', + 1, 1, 0, now(), now()); + INSERT INTO bbs_category(category_name, status, created_user_id, created_at, updated_user_id, updated_at) VALUES ('Test Active Category1', 1, 'test-admin-user', now(), 'test-admin-user', now()); @@ -6,6 +13,12 @@ INSERT INTO bbs (category_id, bbs_name, status, created_user_id, created_at, upd updated_at) VALUES (1, 'Test Active BBS', 1, 'test-admin-user', now(), 'test-admin-user', now()); -INSERT INTO bbs_articles(bbs_id, article_title, article_read_count, article_content, status, +INSERT INTO bbs_articles(article_id, bbs_id, article_title, article_read_count, article_content, + status, + created_user_id, created_at, updated_at) +VALUES (1, 1, 'Test Article Title - 1', 0, '내용이웨요', 1, 'test-admin-user', now(), now()); + +INSERT INTO bbs_articles(article_id, bbs_id, article_title, article_read_count, article_content, + status, created_user_id, created_at, updated_at) -VALUES (1, 'Test Article Title - 1', 0, '내용이웨요', 1, 'test-admin-user', now(), now()); \ No newline at end of file +VALUES (2, 1, 'Test Article Title - 22', 0, '내용이웨요22', 1, 'test', now(), now()); \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml index b428c0c6..9a43e6c9 100644 --- a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -27,4 +27,33 @@ SET article_read_count = article_read_count + 1 WHERE article_id = #{articleId} + + + + INSERT INTO bbs_articles(article_id, bbs_id, article_title, article_read_count, article_content, + status, created_user_id, created_at, updated_at) + VALUES (#{articleId}, #{bbsId}, #{articleTitle}, #{articleReadCount}, #{articleContent}, + #{status}, #{createdUserId}, #{createdAt}, #{updatedAt}) ON DUPLICATE KEY + UPDATE + updated_at = IFNULL(VALUES (updated_at), updated_at) + , bbs_id = IFNULL(VALUES (bbs_id), bbs_id) + , article_title = IFNULL(VALUES (article_title), article_title) + , article_content = IFNULL(VALUES (article_content), article_content) + , status = IFNULL(VALUES (status), status) + + + + + + \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 430ec92b..af943ab0 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -137,9 +137,10 @@ CREATE TABLE `bbs` `updated_at` datetime ); +/* Upsert시 자동증가인 경우 Update도 값을 증기사키는 문제로 인한 수정*/ CREATE TABLE `bbs_articles` ( - `article_id` int PRIMARY KEY AUTO_INCREMENT, + `article_id` int PRIMARY KEY, `bbs_id` int, `article_title` varchar(255), `article_read_count` int, From ce4025550f395182d79613a7e17f932ff1b2c6eb Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:13:05 +0900 Subject: [PATCH 12/28] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nooblol/board/service/impl/ArticleServiceImpl.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index cab5e690..320cbd98 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -64,13 +64,9 @@ public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean } String dbCreatedUserId = articleMapper.selectCreatedUserId(articleDto.getArticleId()); - try { - if (StringUtils.isBlank(dbCreatedUserId) || - !dbCreatedUserId.equals(SessionUtils.getSessionUserId(session))) { - throw new IllegalArgumentException(ExceptionMessage.FORBIDDEN); - } - } catch (Exception e) { - e.printStackTrace(); + if (StringUtils.isBlank(dbCreatedUserId) || + !dbCreatedUserId.equals(SessionUtils.getSessionUserId(session))) { + throw new IllegalArgumentException(ExceptionMessage.FORBIDDEN); } return articleMapper.upsertArticle(articleDto) == 0 ? false : true; From 3d25c6e3e3192c7162a3cf2241c565d5216ea07e Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:26:45 +0900 Subject: [PATCH 13/28] =?UTF-8?q?refactor=20:=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=20=EC=9D=B4?= =?UTF-8?q?=ED=95=B4=ED=95=98=EA=B8=B0=20=EC=89=BD=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=AA=85=EC=B9=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 4 +- .../nooblol/board/service/ArticleService.java | 2 +- .../service/impl/ArticleServiceImpl.java | 47 +++++++++++++++---- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index 34de1f21..0763e46e 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -70,7 +70,7 @@ public ResponseDto insertArticle( .updatedAt(articleDto.getUpdatedAt()) .build(); - boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); + boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); ResponseDto result = ResponseEnum.OK.getResponse(); result.setResult(upsertResult); @@ -97,7 +97,7 @@ public ResponseDto updateArticle( .updatedAt(articleDto.getUpdatedAt()) .build(); - boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); + boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); ResponseDto result = ResponseEnum.OK.getResponse(); result.setResult(upsertResult); diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index 153ff15c..0188c2d6 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -35,7 +35,7 @@ public interface ArticleService { * @param articleDto * @return */ - boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isUpdate); + boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isInsert); /** * 현재 DB에서 사용주인 ArticleId의 최대값에 + 1을 하여 return한다. 만약 ArticleId가 없는 경우에는 1을 반환한다. diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 320cbd98..2cd6bd0c 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -54,26 +54,53 @@ public void addReadCount(int articleId) { } @Override - public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isUpdate) { - - Integer userRole = SessionUtils.getSessionUserRole(session); - + public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isInsert) { //UserLoginCheck의 Annotation을 통해 무조건 Session로그인이 확인된 상황이기에, Role이 Null이 올 수 없음 - if (userRole == UserRoleStatus.ADMIN.getRoleValue() || !isUpdate) { - return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + if (isUserAdmin(SessionUtils.getSessionUserRole(session)) || isInsert) { + return isArticleUpsertSuccess(articleDto); } - String dbCreatedUserId = articleMapper.selectCreatedUserId(articleDto.getArticleId()); - if (StringUtils.isBlank(dbCreatedUserId) || - !dbCreatedUserId.equals(SessionUtils.getSessionUserId(session))) { + //일반 사용자이면서, 게시물의 원작자 여부 확인 + boolean isNotCreatedUser = isNotArticleCreatedUser( + articleMapper.selectCreatedUserId(articleDto.getArticleId()), + SessionUtils.getSessionUserId(session) + ); + + if (isNotCreatedUser) { throw new IllegalArgumentException(ExceptionMessage.FORBIDDEN); } - return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + return isArticleUpsertSuccess(articleDto); } @Override public int getNewArticleId() { return articleMapper.selectMaxArticleId(); } + + /** + * Upsert가 정상적으로 진행된 경우 True를 Return한다. + * + * @param articleDto + * @return + */ + private boolean isArticleUpsertSuccess(ArticleDto articleDto) { + return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + } + + /** + * 게시글을 작성한 사용자가 Session에 저장된 사용자가 아닌 경우 True를 Return한다. + * + * @param dbCreatedUserId 데이터가 없는 경우 빈값이 올 수 있기에 무조건 첫번쨰 파라미터는 DB의 CreatedUserId를 넣어야 한다. + * @param sessionUserId Session에 존재하는 로그인된 사용자 Id + * @return + */ + private boolean isNotArticleCreatedUser(String dbCreatedUserId, String sessionUserId) { + return StringUtils.isBlank(dbCreatedUserId) || !dbCreatedUserId.equals(sessionUserId); + } + + + private boolean isUserAdmin(int userRole) { + return userRole == UserRoleStatus.ADMIN.getRoleValue(); + } } From 54a4223a7310543a29db8be71659ebcc1f8cb910 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:27:06 +0900 Subject: [PATCH 14/28] =?UTF-8?q?docs=20:=20=EC=A3=BC=EC=84=9D=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/board/service/ArticleService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index 0188c2d6..5b963419 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -30,9 +30,11 @@ public interface ArticleService { void addReadCount(int articleId); /** - * 게시물 Upsert + * 게시물의 삽입 또는 수정을 진행한다. * * @param articleDto + * @param session + * @param isInsert 삽입인 경우 True, Update인 경우 False * @return */ boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isInsert); From 127f6360aacc309d36a2f21082780ef46d293f8c Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:55:31 +0900 Subject: [PATCH 15/28] =?UTF-8?q?feat=20:=20UserDto=20TestCase=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=8B=9C=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20Builder=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/nooblol/user/dto/UserDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/nooblol/user/dto/UserDto.java b/src/main/java/com/nooblol/user/dto/UserDto.java index d2ad22df..c92c8f2c 100644 --- a/src/main/java/com/nooblol/user/dto/UserDto.java +++ b/src/main/java/com/nooblol/user/dto/UserDto.java @@ -2,6 +2,7 @@ import java.sql.Timestamp; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -10,6 +11,7 @@ @Setter @NoArgsConstructor @AllArgsConstructor +@Builder public class UserDto { private String userId; From ebb297286ee5690adcfe32d5b929be338b8bb350 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 02:55:53 +0900 Subject: [PATCH 16/28] =?UTF-8?q?test=20:=20UpsertArticle=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20TestCase=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ArticleServiceImplTest.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java index 71077f04..19ac8740 100644 --- a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java +++ b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java @@ -7,13 +7,17 @@ import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.global.utils.SessionEnum; +import com.nooblol.user.dto.UserDto; import com.nooblol.user.utils.UserRoleStatus; +import javax.servlet.http.HttpSession; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpSession; @ExtendWith(MockitoExtension.class) class ArticleServiceImplTest { @@ -108,4 +112,121 @@ void getArticleInfo_WhenArticleIdHaveDataThenReturnArticleDto() { //then assertEquals(result, mockData); } + + @Test + @DisplayName("일반 사용자이며 게시물을 등록하는 경우에 DB에 정상적으로 데이터가 삽입되면 결과로 True를 Return받는다") + void upsertArticle_WhenUserIsAuthUserAndInsertIsSuccessThenReturnTrue() { + //given + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockArticleDto = new ArticleDto(); + + //mock + when(articleMapper.upsertArticle(mockArticleDto)).thenReturn(1); + + //when + boolean result = articleService.upsertArticle(mockArticleDto, session, true); + + //then + assertEquals(result, true); + } + + @Test + @DisplayName("사용자 관리자이며 게시물을 등록이나 수정을 하는 경우에 DB에 정상적으로 데이터가 삽입되면 결과로 True를 Return받는다") + void upsertArticle_WhenUserIsAdminAndUpsertIsSuccessThenReturnTrue() { + //given + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.ADMIN.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockArticleDto = new ArticleDto(); + + //mock + when(articleMapper.upsertArticle(mockArticleDto)).thenReturn(1); + + //when + boolean result = articleService.upsertArticle(mockArticleDto, session, false); + + //then + assertEquals(result, true); + } + + @Test + @DisplayName("일반 사용자이며 게시물을 수정하는 경우에 기존 글과 동일한 사용자이면, Return값으로 True를 받는다") + void upsertArticle_WhenUserIsAuthUserAndUpdateIsSuccessThenReturnTrue() { + //given + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockArticleDto = new ArticleDto().builder().articleId(1).build(); + + //mock + when(articleMapper.upsertArticle(mockArticleDto)).thenReturn(1); + when(articleMapper.selectCreatedUserId(mockArticleDto.getArticleId())) + .thenReturn(sessionUserData.getUserId()); + + //when + boolean result = articleService.upsertArticle(mockArticleDto, session, false); + + //then + assertEquals(result, true); + } + + @Test + @DisplayName("일반 사용자이며 게시물을 수정하는 경우에 기존 글과 다른 사용자이면, Forbidden Exception이 발생한다") + void upsertArticle_WhenUserIsAuthUserAndNotEqualCreatedUserThenForbiddenException() { + //given + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockArticleDto = new ArticleDto().builder().articleId(1).build(); + + //mock + when(articleMapper.selectCreatedUserId(mockArticleDto.getArticleId())) + .thenReturn("different User"); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleService.upsertArticle(mockArticleDto, session, false); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.FORBIDDEN); + } } \ No newline at end of file From 0d7afc038a5c6fa6e4153f7d0773827a5b75ffef Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 04:22:37 +0900 Subject: [PATCH 17/28] =?UTF-8?q?feat=20:=20ResponseDto=EC=9D=98=20Ok?= =?UTF-8?q?=EC=9D=98=20=EA=B2=B0=EA=B3=BC=EC=97=90=20=EB=8C=80=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EC=9E=91=ED=95=98=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Enum?= =?UTF-8?q?=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nooblol/global/utils/CommonUtils.java | 17 +++++++++++++++++ .../com/nooblol/global/utils/ResponseEnum.java | 5 ----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/nooblol/global/utils/CommonUtils.java b/src/main/java/com/nooblol/global/utils/CommonUtils.java index 0e27b29d..d5757069 100644 --- a/src/main/java/com/nooblol/global/utils/CommonUtils.java +++ b/src/main/java/com/nooblol/global/utils/CommonUtils.java @@ -1,8 +1,10 @@ package com.nooblol.global.utils; import com.nooblol.global.dto.ResponseDto; +import com.nooblol.global.exception.ExceptionMessage; import java.util.List; import org.springframework.http.HttpStatus; +import org.springframework.util.ObjectUtils; public class CommonUtils { @@ -27,4 +29,19 @@ public static ResponseDto makeListToResponseDto(List list) { return new ResponseDto(HttpStatus.OK.value(), list); } + + /** + * Object를 받아 ResponseDto를 반환한다. + * + * @param obj + * @return + */ + public static ResponseDto makeToResponseOkDto(Object obj) { + if (ObjectUtils.isEmpty(obj)) { + throw new IllegalArgumentException(ExceptionMessage.NO_DATA); + } + ResponseDto result = ResponseEnum.OK.getResponse(); + result.setResult(obj); + return result; + } } diff --git a/src/main/java/com/nooblol/global/utils/ResponseEnum.java b/src/main/java/com/nooblol/global/utils/ResponseEnum.java index 9989ef75..53a3dee3 100644 --- a/src/main/java/com/nooblol/global/utils/ResponseEnum.java +++ b/src/main/java/com/nooblol/global/utils/ResponseEnum.java @@ -25,9 +25,4 @@ ResponseEnum(int resultCode, T result) { this.response = new ResponseDto(resultCode, result); } - void setResponseResult(T result) { - this.response.setResult(result); - } - - } \ No newline at end of file From 6a79fa9c1fb4201b5651966375a076c0f5b7f566 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 04:25:19 +0900 Subject: [PATCH 18/28] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 29 +++++++++++----- .../nooblol/board/mapper/ArticleMapper.java | 2 ++ .../nooblol/board/service/ArticleService.java | 9 +++++ .../service/impl/ArticleServiceImpl.java | 33 +++++++++++++++++++ src/main/resources/data.sql | 6 ++++ .../mybatis/mapper/board/ArticleMapper.xml | 6 ++++ 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index 0763e46e..b30114ef 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -6,6 +6,7 @@ import com.nooblol.board.service.ArticleService; import com.nooblol.global.annotation.UserLoginCheck; import com.nooblol.global.dto.ResponseDto; +import com.nooblol.global.utils.CommonUtils; import com.nooblol.global.utils.ResponseEnum; import com.nooblol.global.utils.SessionUtils; import javax.servlet.http.HttpSession; @@ -42,9 +43,7 @@ public ResponseDto getArticle( articleId, SessionUtils.getSessionUserId(session) ); - ResponseDto result = ResponseEnum.OK.getResponse(); - result.setResult(article); - return result; + return CommonUtils.makeToResponseOkDto(article); } /** @@ -72,9 +71,7 @@ public ResponseDto insertArticle( boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); - ResponseDto result = ResponseEnum.OK.getResponse(); - result.setResult(upsertResult); - return result; + return CommonUtils.makeToResponseOkDto(upsertResult); } /** @@ -99,8 +96,22 @@ public ResponseDto updateArticle( boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); - ResponseDto result = ResponseEnum.OK.getResponse(); - result.setResult(upsertResult); - return result; + return CommonUtils.makeToResponseOkDto(upsertResult); } + + /** + * @param articleId + * @param session + * @return + */ + @UserLoginCheck + @GetMapping("/delete/{articleId}") + public ResponseDto deleteArticle( + @PathVariable int articleId, HttpSession session + ) { + boolean deleteResult = articleService.deleteArticle(articleId, session); + + return CommonUtils.makeToResponseOkDto(deleteResult); + } + } diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index 0149d9fa..b0d72900 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -17,4 +17,6 @@ public interface ArticleMapper { int selectMaxArticleId(); String selectCreatedUserId(int articleId); + + int deleteArticleByArticleId(int articleId); } diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index 5b963419..799f4327 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -45,4 +45,13 @@ public interface ArticleService { * @return */ int getNewArticleId(); + + /** + * 게시물 삭제, 요청자가 관리자인 경우 또는 글 작성자인 경우에만 삭제를 진행한다. + * + * @param articleId + * @param session + * @return + */ + boolean deleteArticle(int articleId, HttpSession session); } diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 2cd6bd0c..b02161b5 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -78,6 +78,29 @@ public int getNewArticleId() { return articleMapper.selectMaxArticleId(); } + @Override + public boolean deleteArticle(int articleId, HttpSession session) { + ArticleDto haveArticleData = articleMapper.selectArticleByArticleId(articleId); + + if (ObjectUtils.isEmpty(haveArticleData)) { + throw new IllegalArgumentException(ExceptionMessage.NO_DATA); + } + + if (isUserAdmin(SessionUtils.getSessionUserRole(session))) { + return isArticleDeleteSuccess(articleId); + } + + boolean isNotCreatedUser = isNotArticleCreatedUser( + haveArticleData.getCreatedUserId(), SessionUtils.getSessionUserId(session) + ); + + if (isNotCreatedUser) { + throw new IllegalArgumentException(ExceptionMessage.FORBIDDEN); + } + + return isArticleDeleteSuccess(articleId); + } + /** * Upsert가 정상적으로 진행된 경우 True를 Return한다. * @@ -103,4 +126,14 @@ private boolean isNotArticleCreatedUser(String dbCreatedUserId, String sessionUs private boolean isUserAdmin(int userRole) { return userRole == UserRoleStatus.ADMIN.getRoleValue(); } + + /** + * 게시글을 삭제하며, 정상적으로 삭제가 되면 True, 삭제된 건수가 없으면 False를 Return한다 + * + * @param articleId + * @return + */ + private boolean isArticleDeleteSuccess(int articleId) { + return articleMapper.deleteArticleByArticleId(articleId) == 0 ? false : true; + } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 4995bddf..3e11a7ae 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -5,6 +5,12 @@ VALUES ('test', 'test@test.com', 'test', '3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==', 1, 1, 0, now(), now()); +INSERT INTO users(user_id, user_email, user_name, user_password_hash, user_role, level, exp, + created_at, updated_at) +VALUES ('test-admin-user', 'admin@test.com', 'admin', + '3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==', + 9, 1, 0, now(), now()); + INSERT INTO bbs_category(category_name, status, created_user_id, created_at, updated_user_id, updated_at) VALUES ('Test Active Category1', 1, 'test-admin-user', now(), 'test-admin-user', now()); diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml index 9a43e6c9..77a9c360 100644 --- a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -56,4 +56,10 @@ WHERE article_id = #{articleId} + + DELETE + FROM bbs_articles + WHERE article_id = #{articleId} + + \ No newline at end of file From 3a7950c4589760020a2da7b8b8d2acdd0fb36e0b Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 04:57:43 +0900 Subject: [PATCH 19/28] =?UTF-8?q?test=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ArticleServiceImplTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java index 19ac8740..71f9e229 100644 --- a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java +++ b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java @@ -229,4 +229,119 @@ void upsertArticle_WhenUserIsAuthUserAndNotEqualCreatedUserThenForbiddenExceptio //then assertEquals(e.getMessage(), ExceptionMessage.FORBIDDEN); } + + @Test + @DisplayName("게시글을 삭제할 때 DB에 데이터가 없는 경우 NoData Exception이 발생한다.") + void deleteArticle_WhenIsNotHaveArticleInDbThenNoDataException() { + //given + int testArticleId = 1; + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(null); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleService.deleteArticle(testArticleId, null); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.NO_DATA); + } + + @Test + @DisplayName("게시글을 삭제할 때 요청자가 관리자인 경우, 정상삭제 되었으면 Return으로 True값을 받는다.") + void deleteArticle_WhenUserIsAdminAndDeleteSuccessThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.ADMIN.getRoleValue()) + .level(1) + .exp(0) + .build(); + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockReturnDto = new ArticleDto().builder() + .createdUserId(sessionUserData.getUserId()) + .build(); + + //mock + when(articleMapper.deleteArticleByArticleId(testArticleId)).thenReturn(1); + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(mockReturnDto); + + //when + boolean result = articleService.deleteArticle(testArticleId, session); + + //then + assertEquals(result, true); + } + + @Test + @DisplayName("게시글을 삭제할 때 요청자가 작성자인 경우, 정상삭제 되었으면 Return으로 True값을 받는다.") + void deleteArticle_WhenUserIsAuthUserAndDeleteSuccessThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockReturnDto = new ArticleDto().builder() + .createdUserId(sessionUserData.getUserId()) + .build(); + + //mock + when(articleMapper.deleteArticleByArticleId(testArticleId)).thenReturn(1); + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(mockReturnDto); + + //when + boolean result = articleService.deleteArticle(testArticleId, session); + + //then + assertEquals(result, true); + } + + @Test + @DisplayName("게시글을 삭제할 때 일반사용자가 요청시 작성자가 아닌 경우, ForBidden Exception이 발생한다.") + void deleteArticle_WhenUserIsNotCreatedUserThenForbiddenException() { + //given + int testArticleId = 1; + + UserDto sessionUserData = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), sessionUserData); + + ArticleDto mockReturnDto = new ArticleDto().builder() + .createdUserId("different UserId") + .build(); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(mockReturnDto); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleService.deleteArticle(testArticleId, session); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.FORBIDDEN); + } } \ No newline at end of file From d89d1bdef2436c8c089dc1992f787c4f1cb93fd3 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 16:35:13 +0900 Subject: [PATCH 20/28] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EC=B2=9C,=20=EB=B9=84=EC=B6=94=EC=B2=9C?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EA=B0=80,=EC=82=AD=EC=A0=9C,=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 37 +++++++++ .../nooblol/board/dto/ArticleStatusDto.java | 30 +++++++ .../board/dto/LikeAndNotLikeResponseDto.java | 19 +++++ .../nooblol/board/mapper/ArticleMapper.java | 32 ++++++++ .../nooblol/board/service/ArticleService.java | 28 +++++++ .../service/impl/ArticleServiceImpl.java | 82 ++++++++++++++++++- src/main/resources/data.sql | 13 ++- .../mybatis/mapper/board/ArticleMapper.xml | 37 +++++++++ src/main/resources/schema.sql | 6 +- 9 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/nooblol/board/dto/ArticleStatusDto.java create mode 100644 src/main/java/com/nooblol/board/dto/LikeAndNotLikeResponseDto.java diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index b30114ef..af21d07f 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -114,4 +114,41 @@ public ResponseDto deleteArticle( return CommonUtils.makeToResponseOkDto(deleteResult); } + + /** + * 파라미터로 제공한 게시물의 추천, 비추천 갯수를 Return한다 + * + * @param articleId + * @return + */ + @GetMapping("/status/{articleId}") + public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { + return CommonUtils.makeToResponseOkDto(articleService.likeAndNotListStatus(articleId)); + } + + /** + * 게시물 추천 + * + * @param articleId + * @param session + * @return + */ + @UserLoginCheck + @GetMapping("/like/{articleId}") + public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) { + return CommonUtils.makeToResponseOkDto(articleService.likeArticle(articleId, session)); + } + + /** + * 게시물 비추천 + * + * @param articleId + * @param session + * @return + */ + @UserLoginCheck + @GetMapping("/notLike/{articleId}") + public ResponseDto notLikeArticle(@PathVariable int articleId, HttpSession session) { + return CommonUtils.makeToResponseOkDto(articleService.notLikeArticle(articleId, session)); + } } diff --git a/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java b/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java new file mode 100644 index 00000000..0c5c3386 --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java @@ -0,0 +1,30 @@ +package com.nooblol.board.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ArticleStatusDto { + + private int articleId; + + private String userId; + + //True : 추천 , False : 비추천 + private boolean type; + + private LocalDateTime createdAt; + + + public void setCreatedAtNow() { + setCreatedAt(LocalDateTime.now()); + } +} diff --git a/src/main/java/com/nooblol/board/dto/LikeAndNotLikeResponseDto.java b/src/main/java/com/nooblol/board/dto/LikeAndNotLikeResponseDto.java new file mode 100644 index 00000000..af589573 --- /dev/null +++ b/src/main/java/com/nooblol/board/dto/LikeAndNotLikeResponseDto.java @@ -0,0 +1,19 @@ +package com.nooblol.board.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LikeAndNotLikeResponseDto { + + private int likeCnt; + private int notLikeCnt; + +} diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index b0d72900..a7efbf1e 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -1,6 +1,8 @@ package com.nooblol.board.mapper; import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.ArticleStatusDto; +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -19,4 +21,34 @@ public interface ArticleMapper { String selectCreatedUserId(int articleId); int deleteArticleByArticleId(int articleId); + + /** + * 특정 사용자가 해당글에 추천, 비추천 를 하였는지 확인하기 위함. + * + * @param articleStatusDto + * @return + */ + ArticleStatusDto selectArticleStatusByArticleIdAndUserId(ArticleStatusDto articleStatusDto); + + /** + * 파라미터로 제공한 게시물의 총 추천, 비추천 수를 Return한다 + * + * @param articleId + * @return + */ + LikeAndNotLikeResponseDto selectArticleAllStatusByArticleId(int articleId); + + int insertArticleStatus(ArticleStatusDto articleStatusDto); + + /** + * 게시물의 추천, 비추천 에 대한 데이터를 삭제한다. + *

+ * int의 Default값은 0이 들어가나 articleId의 경우에는 1부터 사용하기 떄문에 ArticleId를 설정안하면 아무 데이터도 삭제가 되지 않기 떄문에 문제가 + * 없다. + * + * @param articleStatusDto + * @return + */ + int deleteArticleStatue(ArticleStatusDto articleStatusDto); + } diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index 799f4327..dad9afdd 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -1,6 +1,7 @@ package com.nooblol.board.service; import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; import javax.servlet.http.HttpSession; public interface ArticleService { @@ -48,10 +49,37 @@ public interface ArticleService { /** * 게시물 삭제, 요청자가 관리자인 경우 또는 글 작성자인 경우에만 삭제를 진행한다. + * Delete를 진행하는 경우 관련 테이블에 대해서도 삭제가 이뤄지다 보니, Transaction으로 묶어 처리를 진행한다 * * @param articleId * @param session * @return */ boolean deleteArticle(int articleId, HttpSession session); + + /** + * 게시물 추천 + * + * @param articleId + * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. + * @return + */ + boolean likeArticle(int articleId, HttpSession session); + + /** + * 게시물 비추천 + * + * @param articleId + * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. + * @return + */ + boolean notLikeArticle(int articleId, HttpSession session); + + /** + * 해당 ArticleId의 좋아요 갯수와 싫어요 갯수를 Return한다. + * + * @param articleId + * @return + */ + LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId); } diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index b02161b5..7912f84d 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -1,6 +1,8 @@ package com.nooblol.board.service.impl; import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.ArticleStatusDto; +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; import com.nooblol.board.service.ArticleService; import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; @@ -12,6 +14,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @Slf4j @@ -23,7 +27,7 @@ public class ArticleServiceImpl implements ArticleService { @Override public ArticleDto getArticleInfo(int articleId, String userId) { - articleMapper.addReadCount(articleId); + addReadCount(articleId); ArticleDto result = articleMapper.selectArticleByArticleId(articleId); if (ObjectUtils.isEmpty(result)) { @@ -101,6 +105,35 @@ public boolean deleteArticle(int articleId, HttpSession session) { return isArticleDeleteSuccess(articleId); } + @Override + public boolean likeArticle(int articleId, HttpSession session) { + validatedNotHaveArticle(articleId); + + ArticleStatusDto requestArticleStatusDto = + createArticleStatusDto( + articleId, SessionUtils.getSessionUserId(session), true + ); + + return statusProcess(requestArticleStatusDto); + } + + @Override + public boolean notLikeArticle(int articleId, HttpSession session) { + validatedNotHaveArticle(articleId); + + ArticleStatusDto requestArticleStatusDto = + createArticleStatusDto( + articleId, SessionUtils.getSessionUserId(session), false + ); + + return statusProcess(requestArticleStatusDto); + } + + @Override + public LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId) { + return articleMapper.selectArticleAllStatusByArticleId(articleId); + } + /** * Upsert가 정상적으로 진행된 경우 True를 Return한다. * @@ -136,4 +169,51 @@ private boolean isUserAdmin(int userRole) { private boolean isArticleDeleteSuccess(int articleId) { return articleMapper.deleteArticleByArticleId(articleId) == 0 ? false : true; } + + + private boolean isNotArticleInDb(int articleId) { + return ObjectUtils.isEmpty(articleMapper.selectArticleByArticleId(articleId)); + } + + private void validatedNotHaveArticle(int articleId) { + if (isNotArticleInDb(articleId)) { + throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); + } + } + + private ArticleStatusDto createArticleStatusDto(int articleId, String userId, boolean type) { + ArticleStatusDto articleStatusDto = new ArticleStatusDto().builder() + .articleId(articleId) + .userId(userId) + .type(type) + .build(); + + articleStatusDto.setCreatedAtNow(); + + return articleStatusDto; + } + + /** + * 추천, 비추천에 대한 프로세스, 해당 게시물에 대해 사용자가 좋아요가 없는 경우 Insert 이미 같은 타입(추천, 비추천)을 한경우는 삭제, 다른 타입인 경우는 + * Exception이 발생한다 + * + * @param requestArticleStatusDto + * @return + */ + private boolean statusProcess(ArticleStatusDto requestArticleStatusDto) { + ArticleStatusDto IsHaveStatusData = articleMapper.selectArticleStatusByArticleIdAndUserId( + requestArticleStatusDto); + + if (ObjectUtils.isEmpty(IsHaveStatusData)) { + return articleMapper.insertArticleStatus(requestArticleStatusDto) > 0 ? true : false; + } + + if (IsHaveStatusData.isType() != requestArticleStatusDto.isType()) { + throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); + } + + return articleMapper.deleteArticleStatue(requestArticleStatusDto) > 0 ? true : false; + } + + } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 3e11a7ae..8344e15e 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -27,4 +27,15 @@ VALUES (1, 1, 'Test Article Title - 1', 0, '내용이웨요', 1, 'test-admin-use INSERT INTO bbs_articles(article_id, bbs_id, article_title, article_read_count, article_content, status, created_user_id, created_at, updated_at) -VALUES (2, 1, 'Test Article Title - 22', 0, '내용이웨요22', 1, 'test', now(), now()); \ No newline at end of file +VALUES (2, 1, 'Test Article Title - 22', 0, '내용이웨요22', 1, 'test', now(), now()); + + + +INSERT INTO bbs_articles_status (article_id, user_id, type, created_at) +VALUES (1, 'test', true, now()); + +INSERT INTO bbs_articles_status (article_id, user_id, type, created_at) +VALUES (1, 'test2', true, now()); + +INSERT INTO bbs_articles_status (article_id, user_id, type, created_at) +VALUES (1, 'test3', false, now()); \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml index 77a9c360..f95bdc10 100644 --- a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -62,4 +62,41 @@ WHERE article_id = #{articleId} + + + + + + INSERT INTO bbs_articles_status(article_id, user_id, type, created_at) + VALUES (#{articleId}, #{userId}, #{type}, #{createdAt}) + + + + + DELETE + FROM bbs_articles_status + WHERE article_id = #{articleId} + + AND user_id = #{userId} + + + \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index af943ab0..557605a4 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -151,12 +151,14 @@ CREATE TABLE `bbs_articles` `updated_at` datetime ); +/* + 22. 09. 09 BBSID컬럼 삭제 : articleId로 추적이 가능하기 떄문에 해당 테이블에서는 꼭 필요하지 않다 판단. + */ CREATE TABLE `bbs_articles_status` ( `article_id` int, - `bbs_id` varchar(255), `user_id` varchar(255), - `type` tinyint, + `type` tinyint(1), `created_at` datetime DEFAULT (now()) ); From 153e021fe42e43a723dd13ac6d86cbfc3a329a94 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 16:36:20 +0900 Subject: [PATCH 21/28] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C,=20=EB=B9=84=EC=B6=94=EC=B2=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=A0=9C=EC=9E=91=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게시물의 삭제를 진행할 경우 이전에 해당 게시물의 모든 추천과 비추천의 내역을 삭제한다. --- .../nooblol/board/service/impl/ArticleServiceImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 7912f84d..06927be0 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -83,6 +83,7 @@ public int getNewArticleId() { } @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean deleteArticle(int articleId, HttpSession session) { ArticleDto haveArticleData = articleMapper.selectArticleByArticleId(articleId); @@ -161,12 +162,18 @@ private boolean isUserAdmin(int userRole) { } /** - * 게시글을 삭제하며, 정상적으로 삭제가 되면 True, 삭제된 건수가 없으면 False를 Return한다 + * 먼저 추천, 비추천에 대한 기록도 모두 삭제하며, + *

+ * 이후 게시글을 삭제하며, 정상적으로 삭제가 되면 True, 삭제된 건수가 없으면 False를 Return한다 * * @param articleId * @return */ private boolean isArticleDeleteSuccess(int articleId) { + articleMapper.deleteArticleStatue( + new ArticleStatusDto().builder().articleId(articleId).build() + ); + return articleMapper.deleteArticleByArticleId(articleId) == 0 ? false : true; } From 439bacbae5b85f97a1a75c9b4b4af36d559c9480 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 9 Sep 2022 17:28:03 +0900 Subject: [PATCH 22/28] =?UTF-8?q?test=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EC=B2=9C,=20=EB=B9=84=EC=B6=94=EC=B2=9C?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 비추천테스트케이스의 경우에는 같은 추천 메소드와 같은 메소드를 사용중인 상황이라 따로 구현하지 않음. --- .../service/impl/ArticleServiceImplTest.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java index 71f9e229..9cb2a9ce 100644 --- a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java +++ b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java @@ -4,6 +4,7 @@ import static org.mockito.BDDMockito.*; import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.ArticleStatusDto; import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; import com.nooblol.global.exception.ExceptionMessage; @@ -344,4 +345,128 @@ void deleteArticle_WhenUserIsNotCreatedUserThenForbiddenException() { //then assertEquals(e.getMessage(), ExceptionMessage.FORBIDDEN); } + + @Test + @DisplayName("게시글을 추천 또는 비추천 할 때, DB에 게시물이 존재하지 않으면 BadRequest Exception이 발생한다") + void likeArticle_WhenHaveNotInDbThenBadRequestException() { + //given + int testArticleId = 3; + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(null); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleService.likeArticle(testArticleId, null); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.BAD_REQUEST); + } + + @Test + @DisplayName("게시글을 추천 또는 비추천 하려고 할 때, 추천했던 적이 없으면 결과값으로 true를 획득한다") + void likeArticle_WhenArticleFirstLikeThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn(null); + when(articleMapper.insertArticleStatus(any())).thenReturn(1); + + //then + boolean result = articleService.likeArticle(testArticleId, session); + + assertEquals(result, true); + } + + @Test + @DisplayName("게시글을 추천할떄, 이미 비추천을 한상황이면 BadRequest Exception이 발생한다.") + void likeArticle_WhenArticleLikeAndHistoryIsNotLikeThenBadRequestException() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() + .articleId(testArticleId) + .userId(mockUserDto.getUserId()) + .type(false) + .build(); + mockArticleStatusDto.setCreatedAtNow(); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( + mockArticleStatusDto); + + //when + Exception result = assertThrows(IllegalArgumentException.class, () -> { + articleService.likeArticle(testArticleId, session); + }); + + //then + assertEquals(result.getMessage(), ExceptionMessage.BAD_REQUEST); + } + + @Test + @DisplayName("게시글을 추천할떄, 이미 추천을 한상황이면 추천했던 이력을 삭제후 Return값으로 True를 획득한다") + void likeArticle_WhenArticleLikeAndHistoryIsLikeThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() + .articleId(testArticleId) + .userId(mockUserDto.getUserId()) + .type(true) + .build(); + mockArticleStatusDto.setCreatedAtNow(); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( + mockArticleStatusDto); + when(articleMapper.deleteArticleStatue(any())).thenReturn(1); + + //when + boolean result = articleService.likeArticle(testArticleId, session); + + //then + assertEquals(result, true); + } } \ No newline at end of file From 346e1a1b68b8bbbc4e56428145dfb5d1320de6f2 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Tue, 27 Sep 2022 14:20:44 +0900 Subject: [PATCH 23/28] =?UTF-8?q?refactor=20:=20Merge=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=95=88=EB=90=9C=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/ArticleController.java | 17 ++++++++--------- .../nooblol/global/config/AuthCheckAspect.java | 3 +-- src/main/java/com/nooblol/user/dto/UserDto.java | 1 - 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index af21d07f..960fe6a9 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -6,8 +6,7 @@ import com.nooblol.board.service.ArticleService; import com.nooblol.global.annotation.UserLoginCheck; import com.nooblol.global.dto.ResponseDto; -import com.nooblol.global.utils.CommonUtils; -import com.nooblol.global.utils.ResponseEnum; +import com.nooblol.global.utils.ResponseUtils; import com.nooblol.global.utils.SessionUtils; import javax.servlet.http.HttpSession; import javax.validation.Valid; @@ -43,7 +42,7 @@ public ResponseDto getArticle( articleId, SessionUtils.getSessionUserId(session) ); - return CommonUtils.makeToResponseOkDto(article); + return ResponseUtils.makeToResponseOkDto(article); } /** @@ -71,7 +70,7 @@ public ResponseDto insertArticle( boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); - return CommonUtils.makeToResponseOkDto(upsertResult); + return ResponseUtils.makeToResponseOkDto(upsertResult); } /** @@ -96,7 +95,7 @@ public ResponseDto updateArticle( boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); - return CommonUtils.makeToResponseOkDto(upsertResult); + return ResponseUtils.makeToResponseOkDto(upsertResult); } /** @@ -111,7 +110,7 @@ public ResponseDto deleteArticle( ) { boolean deleteResult = articleService.deleteArticle(articleId, session); - return CommonUtils.makeToResponseOkDto(deleteResult); + return ResponseUtils.makeToResponseOkDto(deleteResult); } @@ -123,7 +122,7 @@ public ResponseDto deleteArticle( */ @GetMapping("/status/{articleId}") public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { - return CommonUtils.makeToResponseOkDto(articleService.likeAndNotListStatus(articleId)); + return ResponseUtils.makeToResponseOkDto(articleService.likeAndNotListStatus(articleId)); } /** @@ -136,7 +135,7 @@ public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { @UserLoginCheck @GetMapping("/like/{articleId}") public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) { - return CommonUtils.makeToResponseOkDto(articleService.likeArticle(articleId, session)); + return ResponseUtils.makeToResponseOkDto(articleService.likeArticle(articleId, session)); } /** @@ -149,6 +148,6 @@ public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) @UserLoginCheck @GetMapping("/notLike/{articleId}") public ResponseDto notLikeArticle(@PathVariable int articleId, HttpSession session) { - return CommonUtils.makeToResponseOkDto(articleService.notLikeArticle(articleId, session)); + return ResponseUtils.makeToResponseOkDto(articleService.notLikeArticle(articleId, session)); } } diff --git a/src/main/java/com/nooblol/global/config/AuthCheckAspect.java b/src/main/java/com/nooblol/global/config/AuthCheckAspect.java index 8550b541..a5480732 100644 --- a/src/main/java/com/nooblol/global/config/AuthCheckAspect.java +++ b/src/main/java/com/nooblol/global/config/AuthCheckAspect.java @@ -19,9 +19,8 @@ public class AuthCheckAspect { /** - * 사용자로그인이 필요한 기능에서 AOP를 활용하여 사전에 로그인이 안된 사용자를 거름 <<<<<<< HEAD ======= + * 사용자로그인이 필요한 기능에서 AOP를 활용하여 사전에 로그인이 안된 사용자를 거름 *

- * >>>>>>> develop * * @param jp */ diff --git a/src/main/java/com/nooblol/user/dto/UserDto.java b/src/main/java/com/nooblol/user/dto/UserDto.java index f779fddf..2d3e142e 100644 --- a/src/main/java/com/nooblol/user/dto/UserDto.java +++ b/src/main/java/com/nooblol/user/dto/UserDto.java @@ -12,7 +12,6 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Builder public class UserDto { private String userId; From d0e453708b714e0fade64de57619a33438c813b2 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Tue, 27 Sep 2022 15:50:28 +0900 Subject: [PATCH 24/28] =?UTF-8?q?refactor=20:=20=ED=95=98=EB=8B=A8=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=EB=82=B4=EC=97=AD..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Controller Mapping에 대해 기능별로 세부적으로 수정 - Mapper주석 불필요하다 판단하여 삭제 - 사용자의 관리자 여부 확인에 대한 검증 메소드 Enum으로 변경 - 조건문 삼항연산자 삭제 및 바로 결과값 Return하도록 수정 --- .../board/controller/ArticleController.java | 23 ++++++++++--------- .../nooblol/board/mapper/ArticleMapper.java | 21 ----------------- .../service/impl/ArticleServiceImpl.java | 21 +++++++++-------- .../nooblol/user/utils/UserRoleStatus.java | 7 ++++++ 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index 960fe6a9..edc2b0d3 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -12,9 +12,11 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,6 +31,7 @@ public class ArticleController { /** * articleId의 게시물 정보와 현재 요청한 사용자의 권한을 같이 Return한다 + * Session에 로그인정보가 없는 경우에는 게시물에 대한 정보 수정을 주는 권한을 Guest로 설정한다 * * @param articleId * @param session @@ -52,7 +55,7 @@ public ResponseDto getArticle( * @return */ @UserLoginCheck - @PostMapping("/insert") + @PostMapping("/") public ResponseDto insertArticle( @Valid @RequestBody ArticleInsertRequestDto articleDto, HttpSession session ) { @@ -80,7 +83,7 @@ public ResponseDto insertArticle( * @return */ @UserLoginCheck - @PostMapping("/update") + @PutMapping("/") public ResponseDto updateArticle( @Valid @RequestBody ArticleUpdateRequestDto articleDto, HttpSession session ) { @@ -99,18 +102,16 @@ public ResponseDto updateArticle( } /** + * 게시물 삭제 + * * @param articleId * @param session * @return */ @UserLoginCheck - @GetMapping("/delete/{articleId}") - public ResponseDto deleteArticle( - @PathVariable int articleId, HttpSession session - ) { - boolean deleteResult = articleService.deleteArticle(articleId, session); - - return ResponseUtils.makeToResponseOkDto(deleteResult); + @DeleteMapping("/{articleId}") + public ResponseDto deleteArticle(@PathVariable int articleId, HttpSession session) { + return ResponseUtils.makeToResponseOkDto(articleService.deleteArticle(articleId, session)); } @@ -133,7 +134,7 @@ public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { * @return */ @UserLoginCheck - @GetMapping("/like/{articleId}") + @PostMapping("/like/{articleId}") public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) { return ResponseUtils.makeToResponseOkDto(articleService.likeArticle(articleId, session)); } @@ -146,7 +147,7 @@ public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) * @return */ @UserLoginCheck - @GetMapping("/notLike/{articleId}") + @PostMapping("/notLike/{articleId}") public ResponseDto notLikeArticle(@PathVariable int articleId, HttpSession session) { return ResponseUtils.makeToResponseOkDto(articleService.notLikeArticle(articleId, session)); } diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index a7efbf1e..dabfe18a 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -22,33 +22,12 @@ public interface ArticleMapper { int deleteArticleByArticleId(int articleId); - /** - * 특정 사용자가 해당글에 추천, 비추천 를 하였는지 확인하기 위함. - * - * @param articleStatusDto - * @return - */ ArticleStatusDto selectArticleStatusByArticleIdAndUserId(ArticleStatusDto articleStatusDto); - /** - * 파라미터로 제공한 게시물의 총 추천, 비추천 수를 Return한다 - * - * @param articleId - * @return - */ LikeAndNotLikeResponseDto selectArticleAllStatusByArticleId(int articleId); int insertArticleStatus(ArticleStatusDto articleStatusDto); - /** - * 게시물의 추천, 비추천 에 대한 데이터를 삭제한다. - *

- * int의 Default값은 0이 들어가나 articleId의 경우에는 1부터 사용하기 떄문에 ArticleId를 설정안하면 아무 데이터도 삭제가 되지 않기 떄문에 문제가 - * 없다. - * - * @param articleStatusDto - * @return - */ int deleteArticleStatue(ArticleStatusDto articleStatusDto); } diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index 06927be0..b793fa85 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -60,7 +60,11 @@ public void addReadCount(int articleId) { @Override public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isInsert) { //UserLoginCheck의 Annotation을 통해 무조건 Session로그인이 확인된 상황이기에, Role이 Null이 올 수 없음 - if (isUserAdmin(SessionUtils.getSessionUserRole(session)) || isInsert) { + + boolean isUserAdminOrInsertArticle = + UserRoleStatus.isUserRoleAdmin(SessionUtils.getSessionUserRole(session)) || isInsert; + + if (isUserAdminOrInsertArticle) { return isArticleUpsertSuccess(articleDto); } @@ -91,7 +95,8 @@ public boolean deleteArticle(int articleId, HttpSession session) { throw new IllegalArgumentException(ExceptionMessage.NO_DATA); } - if (isUserAdmin(SessionUtils.getSessionUserRole(session))) { + boolean isUserAdmin = UserRoleStatus.isUserRoleAdmin(SessionUtils.getSessionUserRole(session)); + if (isUserAdmin) { return isArticleDeleteSuccess(articleId); } @@ -142,7 +147,7 @@ public LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId) { * @return */ private boolean isArticleUpsertSuccess(ArticleDto articleDto) { - return articleMapper.upsertArticle(articleDto) == 0 ? false : true; + return articleMapper.upsertArticle(articleDto) > 0; } /** @@ -157,10 +162,6 @@ private boolean isNotArticleCreatedUser(String dbCreatedUserId, String sessionUs } - private boolean isUserAdmin(int userRole) { - return userRole == UserRoleStatus.ADMIN.getRoleValue(); - } - /** * 먼저 추천, 비추천에 대한 기록도 모두 삭제하며, *

@@ -174,7 +175,7 @@ private boolean isArticleDeleteSuccess(int articleId) { new ArticleStatusDto().builder().articleId(articleId).build() ); - return articleMapper.deleteArticleByArticleId(articleId) == 0 ? false : true; + return articleMapper.deleteArticleByArticleId(articleId) > 0; } @@ -212,14 +213,14 @@ private boolean statusProcess(ArticleStatusDto requestArticleStatusDto) { requestArticleStatusDto); if (ObjectUtils.isEmpty(IsHaveStatusData)) { - return articleMapper.insertArticleStatus(requestArticleStatusDto) > 0 ? true : false; + return articleMapper.insertArticleStatus(requestArticleStatusDto) > 0; } if (IsHaveStatusData.isType() != requestArticleStatusDto.isType()) { throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); } - return articleMapper.deleteArticleStatue(requestArticleStatusDto) > 0 ? true : false; + return articleMapper.deleteArticleStatue(requestArticleStatusDto) > 0; } diff --git a/src/main/java/com/nooblol/user/utils/UserRoleStatus.java b/src/main/java/com/nooblol/user/utils/UserRoleStatus.java index cab7c41a..0877519d 100644 --- a/src/main/java/com/nooblol/user/utils/UserRoleStatus.java +++ b/src/main/java/com/nooblol/user/utils/UserRoleStatus.java @@ -1,5 +1,7 @@ package com.nooblol.user.utils; +import java.util.Arrays; + /** * Value 분류 *

@@ -21,4 +23,9 @@ public enum UserRoleStatus { public int getRoleValue() { return roleValue; } + + public static boolean isUserRoleAdmin(int roleValue) { + return Arrays.stream(UserRoleStatus.values()) + .anyMatch(userRole -> userRole.getRoleValue() == roleValue); + } } From 58148c5e07b76103f08bf6e86c140ebf06a91b6a Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Tue, 27 Sep 2022 16:01:56 +0900 Subject: [PATCH 25/28] =?UTF-8?q?refactor=20:=20Dto=20final=EC=84=A0?= =?UTF-8?q?=EC=96=B8=EC=97=90=20=EB=94=B0=EB=A5=B8=20Controller=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20Enum=20=EB=A1=9C=EC=A7=81=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nooblol/board/controller/ArticleController.java | 1 - .../java/com/nooblol/board/dto/ArticleUpdateRequestDto.java | 3 ++- src/main/java/com/nooblol/user/utils/UserRoleStatus.java | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index edc2b0d3..77e14043 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -93,7 +93,6 @@ public ResponseDto updateArticle( .articleTitle(articleDto.getArticleTitle()) .articleContent(articleDto.getArticleContent()) .status(articleDto.getStatus()) - .updatedAt(articleDto.getUpdatedAt()) .build(); boolean upsertResult = articleService.upsertArticle(upsertArticle, session, false); diff --git a/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java index f1d5dceb..fa5454d8 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java @@ -19,6 +19,7 @@ public class ArticleUpdateRequestDto extends ArticleRequestBaseDto { @NotNull(message = ArticleMessage.ARTICLE_ID_NULL) private Integer articleId; - private LocalDateTime updatedAt = LocalDateTime.now(); + + private final LocalDateTime updatedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/nooblol/user/utils/UserRoleStatus.java b/src/main/java/com/nooblol/user/utils/UserRoleStatus.java index 0877519d..203e8a37 100644 --- a/src/main/java/com/nooblol/user/utils/UserRoleStatus.java +++ b/src/main/java/com/nooblol/user/utils/UserRoleStatus.java @@ -25,7 +25,6 @@ public int getRoleValue() { } public static boolean isUserRoleAdmin(int roleValue) { - return Arrays.stream(UserRoleStatus.values()) - .anyMatch(userRole -> userRole.getRoleValue() == roleValue); + return ADMIN.getRoleValue() == roleValue; } } From 47ee2717700363b6ea1b0cdf09e1076dd3196f47 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Tue, 27 Sep 2022 16:14:34 +0900 Subject: [PATCH 26/28] refactor --- .../java/com/nooblol/board/dto/ArticleUpdateRequestDto.java | 2 +- src/main/java/com/nooblol/board/utils/BoardStatusEnum.java | 1 - .../java/com/nooblol/global/annotation/UserLoginCheck.java | 2 +- .../nooblol/global/controller/RestApiControllerAdvice.java | 5 ----- .../java/com/nooblol/global/exception/ExceptionMessage.java | 3 ++- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java index fa5454d8..08519aa7 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleUpdateRequestDto.java @@ -20,6 +20,6 @@ public class ArticleUpdateRequestDto extends ArticleRequestBaseDto { private Integer articleId; - private final LocalDateTime updatedAt = LocalDateTime.now(); + private LocalDateTime updatedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java b/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java index ad4ac56b..618a9bf2 100644 --- a/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java +++ b/src/main/java/com/nooblol/board/utils/BoardStatusEnum.java @@ -14,7 +14,6 @@ public enum BoardStatusEnum { int status; - public static boolean isExistStatus(int statusType) { return Arrays.stream(BoardStatusEnum.values()) .anyMatch(status -> status.getStatus() == statusType); diff --git a/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java b/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java index a88fcbb6..94582662 100644 --- a/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java +++ b/src/main/java/com/nooblol/global/annotation/UserLoginCheck.java @@ -2,4 +2,4 @@ public @interface UserLoginCheck { -} +} \ No newline at end of file diff --git a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java index 0ffc5f80..0dd68635 100644 --- a/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java +++ b/src/main/java/com/nooblol/global/controller/RestApiControllerAdvice.java @@ -43,11 +43,6 @@ public ResponseDto constraintViolationException( return ResponseEnum.BAD_REQUEST.getResponse(); } - /* - TODO : [22. 09. 08] - 지금 현재 전부 IllegalArgumentException으로 방향을 잡고있는데 그러면 이 Exception을 처리하는 메소드가 - 혼자서 모든 부하를 담당하는게 되는데 처리에 있어서 분산을 하는게 맞지 않을까? 하는 고민이 든다. - */ @ExceptionHandler({IllegalArgumentException.class}) public ResponseDto illegalArgumentException( IllegalArgumentException e, HttpServletRequest request diff --git a/src/main/java/com/nooblol/global/exception/ExceptionMessage.java b/src/main/java/com/nooblol/global/exception/ExceptionMessage.java index 630ce819..7d7065ab 100644 --- a/src/main/java/com/nooblol/global/exception/ExceptionMessage.java +++ b/src/main/java/com/nooblol/global/exception/ExceptionMessage.java @@ -13,9 +13,10 @@ public class ExceptionMessage { public static final String SERVER_ERROR = "SERVER_ERROR"; public static final String HAVE_DATA = "HAVE_DATA"; + public static final String FORBIDDEN = "FORBIDDEN"; public static final String UNAUTHORIZED = "UNAUTHORIZED"; public static final String NOT_FOUND = "NOT_FOUND"; -} +} \ No newline at end of file From 980673ee65720cd4389247a7913015c06d74fca1 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 30 Sep 2022 15:47:20 +0900 Subject: [PATCH 27/28] =?UTF-8?q?feat=20:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Article과 Status(추천,비추천)기능 분리 2. 추천,비추천 type에 대해서 Enum으로 분리 3. 추천,비추천 Enum분리에 따른 TypeHandler 구현 --- .../board/controller/ArticleController.java | 44 +---- .../controller/ArticleStatusController.java | 61 +++++++ .../nooblol/board/dto/ArticleStatusDto.java | 12 +- .../nooblol/board/mapper/ArticleMapper.java | 9 - .../board/mapper/ArticleStatusMapper.java | 18 ++ .../nooblol/board/service/ArticleService.java | 29 +--- .../board/service/ArticleStatusService.java | 33 ++++ .../service/impl/ArticleServiceImpl.java | 82 +-------- .../impl/ArticleStatusServiceImpl.java | 98 +++++++++++ .../board/utils/ArticleLikeStatusEnum.java | 31 ++++ .../ArticleLikeStatusEnumTypeHandler.java | 43 +++++ src/main/resources/application.yml | 1 + .../mybatis/mapper/board/ArticleMapper.xml | 38 ----- .../mapper/board/ArticleStatusMapper.xml | 45 +++++ .../service/impl/ArticleServiceImplTest.java | 131 +-------------- .../impl/ArticleStatusServiceImplTest.java | 159 ++++++++++++++++++ 16 files changed, 507 insertions(+), 327 deletions(-) create mode 100644 src/main/java/com/nooblol/board/controller/ArticleStatusController.java create mode 100644 src/main/java/com/nooblol/board/mapper/ArticleStatusMapper.java create mode 100644 src/main/java/com/nooblol/board/service/ArticleStatusService.java create mode 100644 src/main/java/com/nooblol/board/service/impl/ArticleStatusServiceImpl.java create mode 100644 src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnum.java create mode 100644 src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnumTypeHandler.java create mode 100644 src/main/resources/mybatis/mapper/board/ArticleStatusMapper.xml create mode 100644 src/test/java/com/nooblol/board/service/impl/ArticleStatusServiceImplTest.java diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index 77e14043..4f25c838 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -30,8 +30,8 @@ public class ArticleController { private final ArticleService articleService; /** - * articleId의 게시물 정보와 현재 요청한 사용자의 권한을 같이 Return한다 - * Session에 로그인정보가 없는 경우에는 게시물에 대한 정보 수정을 주는 권한을 Guest로 설정한다 + * articleId의 게시물 정보와 현재 요청한 사용자의 권한을 같이 Return한다 Session에 로그인정보가 없는 경우에는 게시물에 대한 정보 수정을 주는 권한을 + * Guest로 설정한다 * * @param articleId * @param session @@ -67,8 +67,6 @@ public ResponseDto insertArticle( .articleReadCount(articleDto.getArticleReadCount()) .status(articleDto.getStatus()) .createdUserId(SessionUtils.getSessionUserId(session)) - .createdAt(articleDto.getCreatedAt()) - .updatedAt(articleDto.getUpdatedAt()) .build(); boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); @@ -112,42 +110,4 @@ public ResponseDto updateArticle( public ResponseDto deleteArticle(@PathVariable int articleId, HttpSession session) { return ResponseUtils.makeToResponseOkDto(articleService.deleteArticle(articleId, session)); } - - - /** - * 파라미터로 제공한 게시물의 추천, 비추천 갯수를 Return한다 - * - * @param articleId - * @return - */ - @GetMapping("/status/{articleId}") - public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { - return ResponseUtils.makeToResponseOkDto(articleService.likeAndNotListStatus(articleId)); - } - - /** - * 게시물 추천 - * - * @param articleId - * @param session - * @return - */ - @UserLoginCheck - @PostMapping("/like/{articleId}") - public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) { - return ResponseUtils.makeToResponseOkDto(articleService.likeArticle(articleId, session)); - } - - /** - * 게시물 비추천 - * - * @param articleId - * @param session - * @return - */ - @UserLoginCheck - @PostMapping("/notLike/{articleId}") - public ResponseDto notLikeArticle(@PathVariable int articleId, HttpSession session) { - return ResponseUtils.makeToResponseOkDto(articleService.notLikeArticle(articleId, session)); - } } diff --git a/src/main/java/com/nooblol/board/controller/ArticleStatusController.java b/src/main/java/com/nooblol/board/controller/ArticleStatusController.java new file mode 100644 index 00000000..f1f04e61 --- /dev/null +++ b/src/main/java/com/nooblol/board/controller/ArticleStatusController.java @@ -0,0 +1,61 @@ +package com.nooblol.board.controller; + +import com.nooblol.board.service.ArticleStatusService; +import com.nooblol.global.annotation.UserLoginCheck; +import com.nooblol.global.dto.ResponseDto; +import com.nooblol.global.utils.ResponseUtils; +import javax.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/article/status") +@RequiredArgsConstructor +public class ArticleStatusController { + + private final ArticleStatusService articleStatusService; + + /** + * 파라미터로 제공한 게시물의 추천, 비추천 갯수를 Return한다 + * + * @param articleId + * @return + */ + @GetMapping("/{articleId}") + public ResponseDto likeAndNotLikeArticle(@PathVariable int articleId) { + return ResponseUtils.makeToResponseOkDto(articleStatusService.likeAndNotListStatus(articleId)); + } + + /** + * 게시물 추천 + * + * @param articleId + * @param session + * @return + */ + @UserLoginCheck + @PostMapping("/like/{articleId}") + public ResponseDto likeArticle(@PathVariable int articleId, HttpSession session) { + return ResponseUtils.makeToResponseOkDto(articleStatusService.likeArticle(articleId, session)); + } + + /** + * 게시물 비추천 + * + * @param articleId + * @param session + * @return + */ + @UserLoginCheck + @PostMapping("/notLike/{articleId}") + public ResponseDto notLikeArticle(@PathVariable int articleId, HttpSession session) { + return ResponseUtils.makeToResponseOkDto( + articleStatusService.notLikeArticle(articleId, session)); + } +} diff --git a/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java b/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java index 0c5c3386..e1866831 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleStatusDto.java @@ -1,5 +1,7 @@ package com.nooblol.board.dto; +import com.nooblol.board.utils.ArticleLikeStatusEnum; +import com.nooblol.board.utils.ArticleStatusEnum; import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,13 +20,7 @@ public class ArticleStatusDto { private String userId; - //True : 추천 , False : 비추천 - private boolean type; + private ArticleLikeStatusEnum likeType; - private LocalDateTime createdAt; - - - public void setCreatedAtNow() { - setCreatedAt(LocalDateTime.now()); - } + private LocalDateTime createdAt = LocalDateTime.now(); } diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index dabfe18a..8f1966dc 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -21,13 +21,4 @@ public interface ArticleMapper { String selectCreatedUserId(int articleId); int deleteArticleByArticleId(int articleId); - - ArticleStatusDto selectArticleStatusByArticleIdAndUserId(ArticleStatusDto articleStatusDto); - - LikeAndNotLikeResponseDto selectArticleAllStatusByArticleId(int articleId); - - int insertArticleStatus(ArticleStatusDto articleStatusDto); - - int deleteArticleStatue(ArticleStatusDto articleStatusDto); - } diff --git a/src/main/java/com/nooblol/board/mapper/ArticleStatusMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleStatusMapper.java new file mode 100644 index 00000000..2b744dd8 --- /dev/null +++ b/src/main/java/com/nooblol/board/mapper/ArticleStatusMapper.java @@ -0,0 +1,18 @@ +package com.nooblol.board.mapper; + +import com.nooblol.board.dto.ArticleStatusDto; +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ArticleStatusMapper { + + ArticleStatusDto selectArticleStatusByArticleIdAndUserId(ArticleStatusDto articleStatusDto); + + LikeAndNotLikeResponseDto selectArticleAllStatusByArticleId(int articleId); + + int insertArticleStatus(ArticleStatusDto articleStatusDto); + + int deleteArticleStatus(ArticleStatusDto articleStatusDto); + +} diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index dad9afdd..ffcca968 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -48,8 +48,8 @@ public interface ArticleService { int getNewArticleId(); /** - * 게시물 삭제, 요청자가 관리자인 경우 또는 글 작성자인 경우에만 삭제를 진행한다. - * Delete를 진행하는 경우 관련 테이블에 대해서도 삭제가 이뤄지다 보니, Transaction으로 묶어 처리를 진행한다 + * 게시물 삭제, 요청자가 관리자인 경우 또는 글 작성자인 경우에만 삭제를 진행한다. Delete를 진행하는 경우 관련 테이블에 대해서도 삭제가 이뤄지다 보니, + * Transaction으로 묶어 처리를 진행한다 * * @param articleId * @param session @@ -57,29 +57,4 @@ public interface ArticleService { */ boolean deleteArticle(int articleId, HttpSession session); - /** - * 게시물 추천 - * - * @param articleId - * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. - * @return - */ - boolean likeArticle(int articleId, HttpSession session); - - /** - * 게시물 비추천 - * - * @param articleId - * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. - * @return - */ - boolean notLikeArticle(int articleId, HttpSession session); - - /** - * 해당 ArticleId의 좋아요 갯수와 싫어요 갯수를 Return한다. - * - * @param articleId - * @return - */ - LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId); } diff --git a/src/main/java/com/nooblol/board/service/ArticleStatusService.java b/src/main/java/com/nooblol/board/service/ArticleStatusService.java new file mode 100644 index 00000000..7ca2571d --- /dev/null +++ b/src/main/java/com/nooblol/board/service/ArticleStatusService.java @@ -0,0 +1,33 @@ +package com.nooblol.board.service; + +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; +import javax.servlet.http.HttpSession; + +public interface ArticleStatusService { + + /** + * 게시물 추천 + * + * @param articleId + * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. + * @return + */ + boolean likeArticle(int articleId, HttpSession session); + + /** + * 게시물 비추천 + * + * @param articleId + * @param session 한개의 Article에 한번만 추천또는 비추천이 가능하며, 재요청이 들어온 경우 Delete처리를 하기위함. + * @return + */ + boolean notLikeArticle(int articleId, HttpSession session); + + /** + * 해당 ArticleId의 좋아요 갯수와 싫어요 갯수를 Return한다. + * + * @param articleId + * @return + */ + LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId); +} diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index b793fa85..aa732933 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -2,7 +2,7 @@ import com.nooblol.board.dto.ArticleDto; import com.nooblol.board.dto.ArticleStatusDto; -import com.nooblol.board.dto.LikeAndNotLikeResponseDto; +import com.nooblol.board.mapper.ArticleStatusMapper; import com.nooblol.board.service.ArticleService; import com.nooblol.board.mapper.ArticleMapper; import com.nooblol.board.utils.ArticleAuthMessage; @@ -25,6 +25,8 @@ public class ArticleServiceImpl implements ArticleService { private final ArticleMapper articleMapper; + private final ArticleStatusMapper articleStatusMapper; + @Override public ArticleDto getArticleInfo(int articleId, String userId) { addReadCount(articleId); @@ -111,35 +113,6 @@ public boolean deleteArticle(int articleId, HttpSession session) { return isArticleDeleteSuccess(articleId); } - @Override - public boolean likeArticle(int articleId, HttpSession session) { - validatedNotHaveArticle(articleId); - - ArticleStatusDto requestArticleStatusDto = - createArticleStatusDto( - articleId, SessionUtils.getSessionUserId(session), true - ); - - return statusProcess(requestArticleStatusDto); - } - - @Override - public boolean notLikeArticle(int articleId, HttpSession session) { - validatedNotHaveArticle(articleId); - - ArticleStatusDto requestArticleStatusDto = - createArticleStatusDto( - articleId, SessionUtils.getSessionUserId(session), false - ); - - return statusProcess(requestArticleStatusDto); - } - - @Override - public LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId) { - return articleMapper.selectArticleAllStatusByArticleId(articleId); - } - /** * Upsert가 정상적으로 진행된 경우 True를 Return한다. * @@ -171,57 +144,10 @@ private boolean isNotArticleCreatedUser(String dbCreatedUserId, String sessionUs * @return */ private boolean isArticleDeleteSuccess(int articleId) { - articleMapper.deleteArticleStatue( + articleStatusMapper.deleteArticleStatus( new ArticleStatusDto().builder().articleId(articleId).build() ); return articleMapper.deleteArticleByArticleId(articleId) > 0; } - - - private boolean isNotArticleInDb(int articleId) { - return ObjectUtils.isEmpty(articleMapper.selectArticleByArticleId(articleId)); - } - - private void validatedNotHaveArticle(int articleId) { - if (isNotArticleInDb(articleId)) { - throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); - } - } - - private ArticleStatusDto createArticleStatusDto(int articleId, String userId, boolean type) { - ArticleStatusDto articleStatusDto = new ArticleStatusDto().builder() - .articleId(articleId) - .userId(userId) - .type(type) - .build(); - - articleStatusDto.setCreatedAtNow(); - - return articleStatusDto; - } - - /** - * 추천, 비추천에 대한 프로세스, 해당 게시물에 대해 사용자가 좋아요가 없는 경우 Insert 이미 같은 타입(추천, 비추천)을 한경우는 삭제, 다른 타입인 경우는 - * Exception이 발생한다 - * - * @param requestArticleStatusDto - * @return - */ - private boolean statusProcess(ArticleStatusDto requestArticleStatusDto) { - ArticleStatusDto IsHaveStatusData = articleMapper.selectArticleStatusByArticleIdAndUserId( - requestArticleStatusDto); - - if (ObjectUtils.isEmpty(IsHaveStatusData)) { - return articleMapper.insertArticleStatus(requestArticleStatusDto) > 0; - } - - if (IsHaveStatusData.isType() != requestArticleStatusDto.isType()) { - throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); - } - - return articleMapper.deleteArticleStatue(requestArticleStatusDto) > 0; - } - - } diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleStatusServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleStatusServiceImpl.java new file mode 100644 index 00000000..62fc9990 --- /dev/null +++ b/src/main/java/com/nooblol/board/service/impl/ArticleStatusServiceImpl.java @@ -0,0 +1,98 @@ +package com.nooblol.board.service.impl; + + +import com.nooblol.board.dto.ArticleStatusDto; +import com.nooblol.board.dto.LikeAndNotLikeResponseDto; +import com.nooblol.board.mapper.ArticleMapper; +import com.nooblol.board.mapper.ArticleStatusMapper; +import com.nooblol.board.service.ArticleStatusService; +import com.nooblol.board.utils.ArticleLikeStatusEnum; +import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.global.utils.SessionUtils; +import javax.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ArticleStatusServiceImpl implements ArticleStatusService { + + private final ArticleMapper articleMapper; + + private final ArticleStatusMapper articleStatusMapper; + + @Override + public boolean likeArticle(int articleId, HttpSession session) { + validatedNotHaveArticle(articleId); + + ArticleStatusDto requestArticleStatusDto = + createArticleStatusDto( + articleId, SessionUtils.getSessionUserId(session), true + ); + + return statusProcess(requestArticleStatusDto); + } + + @Override + public boolean notLikeArticle(int articleId, HttpSession session) { + validatedNotHaveArticle(articleId); + + ArticleStatusDto requestArticleStatusDto = + createArticleStatusDto( + articleId, SessionUtils.getSessionUserId(session), false + ); + + return statusProcess(requestArticleStatusDto); + } + + @Override + public LikeAndNotLikeResponseDto likeAndNotListStatus(int articleId) { + return articleStatusMapper.selectArticleAllStatusByArticleId(articleId); + } + + private boolean isNotArticleInDb(int articleId) { + return ObjectUtils.isEmpty(articleMapper.selectArticleByArticleId(articleId)); + } + + private void validatedNotHaveArticle(int articleId) { + if (isNotArticleInDb(articleId)) { + throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); + } + } + + private ArticleStatusDto createArticleStatusDto(int articleId, String userId, boolean type) { + ArticleStatusDto articleStatusDto = new ArticleStatusDto().builder() + .articleId(articleId) + .userId(userId) + .likeType(ArticleLikeStatusEnum.findLikeStatusType(type)) + .build(); + + return articleStatusDto; + } + + /** + * 추천, 비추천에 대한 프로세스, 해당 게시물에 대해 사용자가 좋아요가 없는 경우 Insert 이미 같은 타입(추천, 비추천)을 한경우는 삭제, 다른 타입인 경우는 + * Exception이 발생한다 + * + * @param requestArticleStatusDto + * @return + */ + private boolean statusProcess(ArticleStatusDto requestArticleStatusDto) { + ArticleStatusDto IsHaveStatusData = articleStatusMapper.selectArticleStatusByArticleIdAndUserId( + requestArticleStatusDto); + + if (ObjectUtils.isEmpty(IsHaveStatusData)) { + return articleStatusMapper.insertArticleStatus(requestArticleStatusDto) > 0; + } + + if (IsHaveStatusData.getLikeType().isLikeStatus() != + requestArticleStatusDto.getLikeType().isLikeStatus()) { + throw new IllegalArgumentException(ExceptionMessage.BAD_REQUEST); + } + + return articleStatusMapper.deleteArticleStatus(requestArticleStatusDto) > 0; + } +} diff --git a/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnum.java b/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnum.java new file mode 100644 index 00000000..7a3ae323 --- /dev/null +++ b/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnum.java @@ -0,0 +1,31 @@ +package com.nooblol.board.utils; + +import lombok.Getter; +import lombok.Setter; + +@Getter +public enum ArticleLikeStatusEnum { + + LIKE(true), NOT_LIKE(false); + + ArticleLikeStatusEnum(boolean likeStatus) { + this.likeStatus = likeStatus; + } + + boolean likeStatus; + + public static ArticleLikeStatusEnum findLikeStatusType(boolean likeStatus) { + if (likeStatus) { + return LIKE; + } + return NOT_LIKE; + } + + + public static ArticleLikeStatusEnum findLikeStatusByInt(int num) { + if (num == 1) { + return LIKE; + } + return NOT_LIKE; + } +} diff --git a/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnumTypeHandler.java b/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnumTypeHandler.java new file mode 100644 index 00000000..13770203 --- /dev/null +++ b/src/main/java/com/nooblol/board/utils/ArticleLikeStatusEnumTypeHandler.java @@ -0,0 +1,43 @@ +package com.nooblol.board.utils; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; + + +/* +ReferenceList + https://umbum.dev/1122 + https://exchangetuts.com/java-mybatis-enum-string-value-1640689684810781 + */ +public class ArticleLikeStatusEnumTypeHandler implements TypeHandler { + + + @Override + public void setParameter(PreparedStatement ps, int i, ArticleLikeStatusEnum parameter, + JdbcType jdbcType) throws SQLException { + ps.setBoolean(i, parameter.likeStatus); + } + + @Override + public ArticleLikeStatusEnum getResult(ResultSet rs, String columnName) throws SQLException { +// return ArticleLikeStatusEnum.findLikeStatusType(rs.getBoolean(columnName)); + return ArticleLikeStatusEnum.findLikeStatusByInt(rs.getInt(columnName)); + } + + @Override + public ArticleLikeStatusEnum getResult(ResultSet rs, int columnIndex) throws SQLException { +// return ArticleLikeStatusEnum.findLikeStatusType(rs.getBoolean(columnIndex)); + return ArticleLikeStatusEnum.findLikeStatusByInt(rs.getInt(columnIndex)); + } + + @Override + public ArticleLikeStatusEnum getResult(CallableStatement cs, int columnIndex) + throws SQLException { +// return ArticleLikeStatusEnum.findLikeStatusType(cs.getBoolean(columnIndex)); + return ArticleLikeStatusEnum.findLikeStatusByInt(cs.getInt(columnIndex)); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 35de242a..67f9f034 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -44,6 +44,7 @@ mybatis: jdbc-type-for-null: null call-setters-on-nulls: true multiple-result-sets-enabled: false + default-enum-type-handler: com.nooblol.board.utils.ArticleLikeStatusEnumTypeHandler type-aliases-package: com.nooblol.*.* mapper-locations: classpath:mybatis/mapper/*/*.xml diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml index f95bdc10..c18d406a 100644 --- a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -61,42 +61,4 @@ FROM bbs_articles WHERE article_id = #{articleId} - - - - - - - INSERT INTO bbs_articles_status(article_id, user_id, type, created_at) - VALUES (#{articleId}, #{userId}, #{type}, #{createdAt}) - - - - - DELETE - FROM bbs_articles_status - WHERE article_id = #{articleId} - - AND user_id = #{userId} - - - \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/board/ArticleStatusMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleStatusMapper.xml new file mode 100644 index 00000000..9d35d30f --- /dev/null +++ b/src/main/resources/mybatis/mapper/board/ArticleStatusMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + INSERT INTO bbs_articles_status(article_id, user_id, type, created_at) + VALUES (#{articleId}, #{userId}, + #{likeType, typeHandler=com.nooblol.board.utils.ArticleLikeStatusEnumTypeHandler}, + #{createdAt}) + + + + + DELETE + FROM bbs_articles_status + WHERE article_id = #{articleId} + + AND user_id = #{userId} + + + + \ No newline at end of file diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java index 9cb2a9ce..3eda97d2 100644 --- a/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java +++ b/src/test/java/com/nooblol/board/service/impl/ArticleServiceImplTest.java @@ -4,8 +4,8 @@ import static org.mockito.BDDMockito.*; import com.nooblol.board.dto.ArticleDto; -import com.nooblol.board.dto.ArticleStatusDto; import com.nooblol.board.mapper.ArticleMapper; +import com.nooblol.board.mapper.ArticleStatusMapper; import com.nooblol.board.utils.ArticleAuthMessage; import com.nooblol.global.exception.ExceptionMessage; import com.nooblol.global.utils.SessionEnum; @@ -26,6 +26,9 @@ class ArticleServiceImplTest { @Mock private ArticleMapper articleMapper; + @Mock + private ArticleStatusMapper articleStatusMapper; + @InjectMocks private ArticleServiceImpl articleService; @@ -273,6 +276,7 @@ void deleteArticle_WhenUserIsAdminAndDeleteSuccessThenReturnTrue() { //mock when(articleMapper.deleteArticleByArticleId(testArticleId)).thenReturn(1); when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(mockReturnDto); + when(articleStatusMapper.deleteArticleStatus(any())).thenReturn(1); //when boolean result = articleService.deleteArticle(testArticleId, session); @@ -305,6 +309,7 @@ void deleteArticle_WhenUserIsAuthUserAndDeleteSuccessThenReturnTrue() { //mock when(articleMapper.deleteArticleByArticleId(testArticleId)).thenReturn(1); when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(mockReturnDto); + when(articleStatusMapper.deleteArticleStatus(any())).thenReturn(1); //when boolean result = articleService.deleteArticle(testArticleId, session); @@ -345,128 +350,4 @@ void deleteArticle_WhenUserIsNotCreatedUserThenForbiddenException() { //then assertEquals(e.getMessage(), ExceptionMessage.FORBIDDEN); } - - @Test - @DisplayName("게시글을 추천 또는 비추천 할 때, DB에 게시물이 존재하지 않으면 BadRequest Exception이 발생한다") - void likeArticle_WhenHaveNotInDbThenBadRequestException() { - //given - int testArticleId = 3; - - //mock - when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(null); - - //when - Exception e = assertThrows(IllegalArgumentException.class, () -> { - articleService.likeArticle(testArticleId, null); - }); - - //then - assertEquals(e.getMessage(), ExceptionMessage.BAD_REQUEST); - } - - @Test - @DisplayName("게시글을 추천 또는 비추천 하려고 할 때, 추천했던 적이 없으면 결과값으로 true를 획득한다") - void likeArticle_WhenArticleFirstLikeThenReturnTrue() { - //given - int testArticleId = 1; - - UserDto mockUserDto = new UserDto().builder() - .userId("test") - .userEmail("test@test.com") - .userName("test") - .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) - .level(1) - .exp(0) - .build(); - - HttpSession session = new MockHttpSession(); - session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); - - //mock - when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); - when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn(null); - when(articleMapper.insertArticleStatus(any())).thenReturn(1); - - //then - boolean result = articleService.likeArticle(testArticleId, session); - - assertEquals(result, true); - } - - @Test - @DisplayName("게시글을 추천할떄, 이미 비추천을 한상황이면 BadRequest Exception이 발생한다.") - void likeArticle_WhenArticleLikeAndHistoryIsNotLikeThenBadRequestException() { - //given - int testArticleId = 1; - - UserDto mockUserDto = new UserDto().builder() - .userId("test") - .userEmail("test@test.com") - .userName("test") - .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) - .level(1) - .exp(0) - .build(); - - HttpSession session = new MockHttpSession(); - session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); - - ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() - .articleId(testArticleId) - .userId(mockUserDto.getUserId()) - .type(false) - .build(); - mockArticleStatusDto.setCreatedAtNow(); - - //mock - when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); - when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( - mockArticleStatusDto); - - //when - Exception result = assertThrows(IllegalArgumentException.class, () -> { - articleService.likeArticle(testArticleId, session); - }); - - //then - assertEquals(result.getMessage(), ExceptionMessage.BAD_REQUEST); - } - - @Test - @DisplayName("게시글을 추천할떄, 이미 추천을 한상황이면 추천했던 이력을 삭제후 Return값으로 True를 획득한다") - void likeArticle_WhenArticleLikeAndHistoryIsLikeThenReturnTrue() { - //given - int testArticleId = 1; - - UserDto mockUserDto = new UserDto().builder() - .userId("test") - .userEmail("test@test.com") - .userName("test") - .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) - .level(1) - .exp(0) - .build(); - - HttpSession session = new MockHttpSession(); - session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); - - ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() - .articleId(testArticleId) - .userId(mockUserDto.getUserId()) - .type(true) - .build(); - mockArticleStatusDto.setCreatedAtNow(); - - //mock - when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); - when(articleMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( - mockArticleStatusDto); - when(articleMapper.deleteArticleStatue(any())).thenReturn(1); - - //when - boolean result = articleService.likeArticle(testArticleId, session); - - //then - assertEquals(result, true); - } } \ No newline at end of file diff --git a/src/test/java/com/nooblol/board/service/impl/ArticleStatusServiceImplTest.java b/src/test/java/com/nooblol/board/service/impl/ArticleStatusServiceImplTest.java new file mode 100644 index 00000000..ed781df1 --- /dev/null +++ b/src/test/java/com/nooblol/board/service/impl/ArticleStatusServiceImplTest.java @@ -0,0 +1,159 @@ +package com.nooblol.board.service.impl; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.nooblol.board.dto.ArticleDto; +import com.nooblol.board.dto.ArticleStatusDto; +import com.nooblol.board.mapper.ArticleMapper; +import com.nooblol.board.mapper.ArticleStatusMapper; +import com.nooblol.board.utils.ArticleLikeStatusEnum; +import com.nooblol.global.exception.ExceptionMessage; +import com.nooblol.global.utils.SessionEnum; +import com.nooblol.user.dto.UserDto; +import com.nooblol.user.utils.UserRoleStatus; +import javax.servlet.http.HttpSession; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpSession; + +@ExtendWith(MockitoExtension.class) +class ArticleStatusServiceImplTest { + + @Mock + private ArticleMapper articleMapper; + + @Mock + private ArticleStatusMapper articleStatusMapper; + + @InjectMocks + private ArticleStatusServiceImpl articleStatusService; + + + @Test + @DisplayName("게시글을 추천 또는 비추천 할 때, DB에 게시물이 존재하지 않으면 BadRequest Exception이 발생한다") + void likeArticle_WhenHaveNotInDbThenBadRequestException() { + //given + int testArticleId = 3; + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(null); + + //when + Exception e = assertThrows(IllegalArgumentException.class, () -> { + articleStatusService.likeArticle(testArticleId, null); + }); + + //then + assertEquals(e.getMessage(), ExceptionMessage.BAD_REQUEST); + } + + @Test + @DisplayName("게시글을 추천 또는 비추천 하려고 할 때, 추천했던 적이 없으면 결과값으로 true를 획득한다") + void likeArticle_WhenArticleFirstLikeThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleStatusMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn(null); + when(articleStatusMapper.insertArticleStatus(any())).thenReturn(1); + + //then + boolean result = articleStatusService.likeArticle(testArticleId, session); + + assertEquals(result, true); + } + + @Test + @DisplayName("게시글을 추천할떄, 이미 비추천을 한상황이면 BadRequest Exception이 발생한다.") + void likeArticle_WhenArticleLikeAndHistoryIsNotLikeThenBadRequestException() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() + .articleId(testArticleId) + .userId(mockUserDto.getUserId()) + .likeType(ArticleLikeStatusEnum.NOT_LIKE) + .build(); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleStatusMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( + mockArticleStatusDto); + + //when + Exception result = assertThrows(IllegalArgumentException.class, () -> { + articleStatusService.likeArticle(testArticleId, session); + }); + + //then + assertEquals(result.getMessage(), ExceptionMessage.BAD_REQUEST); + } + + @Test + @DisplayName("게시글을 추천할떄, 이미 추천을 한상황이면 추천했던 이력을 삭제후 Return값으로 True를 획득한다") + void likeArticle_WhenArticleLikeAndHistoryIsLikeThenReturnTrue() { + //given + int testArticleId = 1; + + UserDto mockUserDto = new UserDto().builder() + .userId("test") + .userEmail("test@test.com") + .userName("test") + .userRole(UserRoleStatus.AUTH_USER.getRoleValue()) + .level(1) + .exp(0) + .build(); + + HttpSession session = new MockHttpSession(); + session.setAttribute(SessionEnum.USER_LOGIN.getValue(), mockUserDto); + + ArticleStatusDto mockArticleStatusDto = new ArticleStatusDto().builder() + .articleId(testArticleId) + .userId(mockUserDto.getUserId()) + .likeType(ArticleLikeStatusEnum.LIKE) + .build(); + + //mock + when(articleMapper.selectArticleByArticleId(testArticleId)).thenReturn(new ArticleDto()); + when(articleStatusMapper.selectArticleStatusByArticleIdAndUserId(any())).thenReturn( + mockArticleStatusDto); + when(articleStatusMapper.deleteArticleStatus(any())).thenReturn(1); + + //when + boolean result = articleStatusService.likeArticle(testArticleId, session); + + //then + assertEquals(result, true); + } +} \ No newline at end of file From fedee18b0ec2fd55e036e0196ed88eea83eb53e9 Mon Sep 17 00:00:00 2001 From: donsonioc2010 Date: Fri, 30 Sep 2022 19:36:47 +0900 Subject: [PATCH 28/28] =?UTF-8?q?feat=20:=20Article=20Upsert=20=ED=95=9C?= =?UTF-8?q?=EB=B2=88=EC=9D=98=20=EC=9A=94=EC=B2=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A4=84=EC=A7=80=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nooblol/board/controller/ArticleController.java | 3 ++- src/main/java/com/nooblol/board/dto/ArticleDto.java | 2 +- .../java/com/nooblol/board/mapper/ArticleMapper.java | 2 -- .../com/nooblol/board/service/ArticleService.java | 6 ------ .../board/service/impl/ArticleServiceImpl.java | 5 ----- .../resources/mybatis/mapper/board/ArticleMapper.xml | 11 +++++------ 6 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/nooblol/board/controller/ArticleController.java b/src/main/java/com/nooblol/board/controller/ArticleController.java index 4f25c838..8b5d0403 100644 --- a/src/main/java/com/nooblol/board/controller/ArticleController.java +++ b/src/main/java/com/nooblol/board/controller/ArticleController.java @@ -60,13 +60,14 @@ public ResponseDto insertArticle( @Valid @RequestBody ArticleInsertRequestDto articleDto, HttpSession session ) { ArticleDto upsertArticle = new ArticleDto().builder() - .articleId(articleService.getNewArticleId()) .bbsId(articleDto.getBbsId()) .articleTitle(articleDto.getArticleTitle()) .articleContent(articleDto.getArticleContent()) .articleReadCount(articleDto.getArticleReadCount()) .status(articleDto.getStatus()) .createdUserId(SessionUtils.getSessionUserId(session)) + .createdAt(articleDto.getCreatedAt()) + .updatedAt(articleDto.getUpdatedAt()) .build(); boolean upsertResult = articleService.upsertArticle(upsertArticle, session, true); diff --git a/src/main/java/com/nooblol/board/dto/ArticleDto.java b/src/main/java/com/nooblol/board/dto/ArticleDto.java index 158d710a..ba072b55 100644 --- a/src/main/java/com/nooblol/board/dto/ArticleDto.java +++ b/src/main/java/com/nooblol/board/dto/ArticleDto.java @@ -19,7 +19,7 @@ public class ArticleDto { Integer로 변경하는 이유는 기본값 0이 들어감으로 인해서 Mybatis에서 NullCheck를 못하는 경우를 제외하기 위해 변경 */ - private int articleId; + private Integer articleId; private Integer bbsId; private String articleTitle; private int articleReadCount; diff --git a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java index 8f1966dc..f879a5e6 100644 --- a/src/main/java/com/nooblol/board/mapper/ArticleMapper.java +++ b/src/main/java/com/nooblol/board/mapper/ArticleMapper.java @@ -16,8 +16,6 @@ public interface ArticleMapper { int upsertArticle(ArticleDto articleDto); - int selectMaxArticleId(); - String selectCreatedUserId(int articleId); int deleteArticleByArticleId(int articleId); diff --git a/src/main/java/com/nooblol/board/service/ArticleService.java b/src/main/java/com/nooblol/board/service/ArticleService.java index ffcca968..22e8c18f 100644 --- a/src/main/java/com/nooblol/board/service/ArticleService.java +++ b/src/main/java/com/nooblol/board/service/ArticleService.java @@ -40,12 +40,6 @@ public interface ArticleService { */ boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean isInsert); - /** - * 현재 DB에서 사용주인 ArticleId의 최대값에 + 1을 하여 return한다. 만약 ArticleId가 없는 경우에는 1을 반환한다. - * - * @return - */ - int getNewArticleId(); /** * 게시물 삭제, 요청자가 관리자인 경우 또는 글 작성자인 경우에만 삭제를 진행한다. Delete를 진행하는 경우 관련 테이블에 대해서도 삭제가 이뤄지다 보니, diff --git a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java index aa732933..dc9d0f2a 100644 --- a/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/nooblol/board/service/impl/ArticleServiceImpl.java @@ -83,11 +83,6 @@ public boolean upsertArticle(ArticleDto articleDto, HttpSession session, boolean return isArticleUpsertSuccess(articleDto); } - @Override - public int getNewArticleId() { - return articleMapper.selectMaxArticleId(); - } - @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean deleteArticle(int articleId, HttpSession session) { diff --git a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml index c18d406a..1fe3d247 100644 --- a/src/main/resources/mybatis/mapper/board/ArticleMapper.xml +++ b/src/main/resources/mybatis/mapper/board/ArticleMapper.xml @@ -35,7 +35,11 @@ INSERT INTO bbs_articles(article_id, bbs_id, article_title, article_read_count, article_content, status, created_user_id, created_at, updated_at) - VALUES (#{articleId}, #{bbsId}, #{articleTitle}, #{articleReadCount}, #{articleContent}, + VALUES (IFNULL(#{articleId}, IFNULL((SELECT article_id + 1 + FROM bbs_articles + ORDER BY article_id DESC limit 1), 1)), + #{bbsId}, #{articleTitle}, + #{articleReadCount}, #{articleContent}, #{status}, #{createdUserId}, #{createdAt}, #{updatedAt}) ON DUPLICATE KEY UPDATE updated_at = IFNULL(VALUES (updated_at), updated_at) @@ -45,11 +49,6 @@ , status = IFNULL(VALUES (status), status) - -