From d74f27bfb44cf91adddba6466ad84b14e29e36d7 Mon Sep 17 00:00:00 2001 From: zerobell-lee Date: Sun, 22 Sep 2019 16:35:37 +0900 Subject: [PATCH 1/4] feat: Add classes for api, services --- .../java/h3s1/tubesday/api/ApiResponse.java | 23 +++++++++++++++++++ .../tubesday/api/ServiceErrorMessage.java | 6 +++++ .../h3s1/tubesday/api/ServiceException.java | 14 +++++++++++ .../java/h3s1/tubesday/api/StatusCode.java | 12 ++++++++++ 4 files changed, 55 insertions(+) create mode 100644 src/main/java/h3s1/tubesday/api/ApiResponse.java create mode 100644 src/main/java/h3s1/tubesday/api/ServiceErrorMessage.java create mode 100644 src/main/java/h3s1/tubesday/api/ServiceException.java create mode 100644 src/main/java/h3s1/tubesday/api/StatusCode.java diff --git a/src/main/java/h3s1/tubesday/api/ApiResponse.java b/src/main/java/h3s1/tubesday/api/ApiResponse.java new file mode 100644 index 0000000..d963f35 --- /dev/null +++ b/src/main/java/h3s1/tubesday/api/ApiResponse.java @@ -0,0 +1,23 @@ +package h3s1.tubesday.api; + +import lombok.Data; + +@Data +public class ApiResponse { + + private StatusCode statusCode; + private T data; + + private ApiResponse(StatusCode code, T data) { + this.statusCode = code; + this.data = data; + } + + public static ApiResponse ok(T data) { + return new ApiResponse<>(StatusCode.OK, data); + } + + public static ApiResponse error(String message) { + return new ApiResponse<>(StatusCode.UNKNOWN_ERROR, message); + } +} diff --git a/src/main/java/h3s1/tubesday/api/ServiceErrorMessage.java b/src/main/java/h3s1/tubesday/api/ServiceErrorMessage.java new file mode 100644 index 0000000..2daac1b --- /dev/null +++ b/src/main/java/h3s1/tubesday/api/ServiceErrorMessage.java @@ -0,0 +1,6 @@ +package h3s1.tubesday.api; + +public enum ServiceErrorMessage { + EMAIL_TYPE_INVALID, + PASSWORD_TYPE_INVALID +} diff --git a/src/main/java/h3s1/tubesday/api/ServiceException.java b/src/main/java/h3s1/tubesday/api/ServiceException.java new file mode 100644 index 0000000..6173994 --- /dev/null +++ b/src/main/java/h3s1/tubesday/api/ServiceException.java @@ -0,0 +1,14 @@ +package h3s1.tubesday.api; + +import lombok.Getter; + +public class ServiceException extends RuntimeException { + + @Getter + ServiceErrorMessage code; + + public ServiceException(ServiceErrorMessage code, String detailMessage) { + super(detailMessage); + this.code = code; + } +} diff --git a/src/main/java/h3s1/tubesday/api/StatusCode.java b/src/main/java/h3s1/tubesday/api/StatusCode.java new file mode 100644 index 0000000..020f148 --- /dev/null +++ b/src/main/java/h3s1/tubesday/api/StatusCode.java @@ -0,0 +1,12 @@ +package h3s1.tubesday.api; + +public enum StatusCode { + OK("OOK00001"), + UNKNOWN_ERROR("ERR99999"); + + private String code; + + StatusCode(String code) { + this.code = code; + } +} From 92b010156404a45c1dbe57f4b293542672b1a080 Mon Sep 17 00:00:00 2001 From: zerobell-lee Date: Sun, 22 Sep 2019 16:36:29 +0900 Subject: [PATCH 2/4] fix: Add unique constraint on email --- src/main/java/h3s1/tubesday/user/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/h3s1/tubesday/user/User.java b/src/main/java/h3s1/tubesday/user/User.java index 265b54a..ecf93d5 100644 --- a/src/main/java/h3s1/tubesday/user/User.java +++ b/src/main/java/h3s1/tubesday/user/User.java @@ -20,7 +20,7 @@ public class User { @Column(name = "user_no") private Long userNo; - @Column(name = "email") + @Column(name = "email", unique = true) private String email; @Column From 029c71c30a6c24164a15716bd9e682ba2c8a2124 Mon Sep 17 00:00:00 2001 From: zerobell-lee Date: Sun, 22 Sep 2019 16:36:48 +0900 Subject: [PATCH 3/4] fix: Raise source compatibility to 1.9 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c3d354b..290e91d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { group = 'h3s1' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '1.8' +sourceCompatibility = '1.9' configurations { compileOnly { From b929d6286ad60e3c6724bcb923acb073eb8e64c6 Mon Sep 17 00:00:00 2001 From: zerobell-lee Date: Sun, 22 Sep 2019 16:37:18 +0900 Subject: [PATCH 4/4] feat: Add UserService, ArticleService --- .../h3s1/tubesday/article/ArticleService.java | 69 ++++++++++++++ .../java/h3s1/tubesday/user/UserService.java | 95 +++++++++++++++++++ .../tubesday/article/ArticleServiceTests.java | 60 ++++++++++++ .../h3s1/tubesday/user/UserServiceTests.java | 54 +++++++++++ 4 files changed, 278 insertions(+) create mode 100644 src/main/java/h3s1/tubesday/article/ArticleService.java create mode 100644 src/main/java/h3s1/tubesday/user/UserService.java create mode 100644 src/test/java/h3s1/tubesday/article/ArticleServiceTests.java create mode 100644 src/test/java/h3s1/tubesday/user/UserServiceTests.java diff --git a/src/main/java/h3s1/tubesday/article/ArticleService.java b/src/main/java/h3s1/tubesday/article/ArticleService.java new file mode 100644 index 0000000..6a8b411 --- /dev/null +++ b/src/main/java/h3s1/tubesday/article/ArticleService.java @@ -0,0 +1,69 @@ +package h3s1.tubesday.article; + +import h3s1.tubesday.tag.ArticleTagMatch; +import h3s1.tubesday.tag.ArticleTagMatchRepository; +import h3s1.tubesday.tag.Tag; +import h3s1.tubesday.tag.TagRepository; +import h3s1.tubesday.user.User; +import h3s1.tubesday.user.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class ArticleService { + + private ArticleRepository articleRepository; + private TagRepository tagRepository; + private ArticleTagMatchRepository articleTagMatchRepository; + private UserRepository userRepository; + + @Autowired + ArticleService(ArticleRepository articleRepository, TagRepository tagRepository, ArticleTagMatchRepository articleTagMatchRepository, UserRepository userRepository) { + this.articleRepository = articleRepository; + this.tagRepository = tagRepository; + this.articleTagMatchRepository = articleTagMatchRepository; + this.userRepository = userRepository; + } + + public Article createArticle(long userNo, String title, String content, String videoId, List tags) { + final Optional optionalAuthor = userRepository.findById(userNo); + + if (!optionalAuthor.isPresent()) { + throw new RuntimeException("User not found!!"); + } + + final User author = optionalAuthor.get(); + + final List articleTagMatches = new ArrayList<>(); + final Article article = Article.builder() + .author(author) + .title(title) + .content(content) + .videoId(videoId) + .build(); + + for (String tagName : tags) { + Tag tag = tagRepository.findByTagName(tagName); + + if (tag == null) { + tag = new Tag(); + tag.setTagName(tagName); + tagRepository.save(tag); + } + ArticleTagMatch match = ArticleTagMatch.builder() + .tag(tag) + .article(article) + .build(); + + articleTagMatches.add(match); + } + article.setTagMatchList(articleTagMatches); + + return articleRepository.save(article); + } + +} diff --git a/src/main/java/h3s1/tubesday/user/UserService.java b/src/main/java/h3s1/tubesday/user/UserService.java new file mode 100644 index 0000000..b5bc43f --- /dev/null +++ b/src/main/java/h3s1/tubesday/user/UserService.java @@ -0,0 +1,95 @@ +package h3s1.tubesday.user; + +import h3s1.tubesday.api.ServiceErrorMessage; +import h3s1.tubesday.api.ServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +public class UserService { + + private UserRepository userRepository; + + @Autowired + UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User createUser(String email, String password, String nickname, String avatarUrl) { + validateEmail(email); + validatePassword(password); + LocalDateTime now = LocalDateTime.now(); + final User user = User.builder() + .email(email) + .password(password) + .nickname(nickname) + .avatarUrl(avatarUrl) + .createdAt(now) + .updatedAt(now) + .build(); + + userRepository.save(user); + return user; + } + + public User getUserByUserNo(long userNo) { + final Optional user = userRepository.findById(userNo); + + if (user.isPresent()) { + return user.get(); + } + throw new RuntimeException("That user doesn't exist."); + } + + public User getUserByEmail(String email) { + return userRepository.findByEmail(email); + } + + public User updateUserByUserNo(long userNo, String email, String password, String nickname, String avatarUrl) { + final Optional optionalUser = userRepository.findById(userNo); + final User user; + + if (optionalUser.isPresent()) { + user = optionalUser.get(); + updateUser(user, email, password, nickname, avatarUrl); + return userRepository.save(user); + } + throw new RuntimeException("User not exist"); + } + + public User updateUserByEmail(String email, String password, String nickname, String avatarUrl) { + final User user = userRepository.findByEmail(email); + + updateUser(user, email, password, nickname, avatarUrl); + return userRepository.save(user); + } + + + public void deleteUser(long userNo) { + userRepository.deleteById(userNo); + } + + private void validateEmail(String email) { + if (email == null) throw new ServiceException(ServiceErrorMessage.EMAIL_TYPE_INVALID, "email is null"); + if (!email.matches("^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$")) + throw new ServiceException(ServiceErrorMessage.EMAIL_TYPE_INVALID, "Email type doesn't invalid."); + } + + private void validatePassword(String password) { + if (password == null || "".equals(password)) + throw new ServiceException(ServiceErrorMessage.PASSWORD_TYPE_INVALID, "password is null"); + if (password.length() < 2 || password.length() > 45) + throw new ServiceException(ServiceErrorMessage.PASSWORD_TYPE_INVALID, "password length is invalid."); + } + + private void updateUser(User user, String email, String password, String nickname, String avatarUrl) { + user.setEmail("".equals(email) ? user.getEmail() : email); + user.setPassword("".equals(password) ? user.getPassword() : password); + user.setNickname("".equals(nickname) ? user.getNickname() : nickname); + user.setPassword("".equals(avatarUrl) ? user.getAvatarUrl() : avatarUrl); + user.setUpdatedAt(LocalDateTime.now()); + } +} diff --git a/src/test/java/h3s1/tubesday/article/ArticleServiceTests.java b/src/test/java/h3s1/tubesday/article/ArticleServiceTests.java new file mode 100644 index 0000000..89606fb --- /dev/null +++ b/src/test/java/h3s1/tubesday/article/ArticleServiceTests.java @@ -0,0 +1,60 @@ +package h3s1.tubesday.article; + +import h3s1.tubesday.user.User; +import h3s1.tubesday.user.UserService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +@SpringBootTest +@RunWith(SpringJUnit4ClassRunner.class) +public class ArticleServiceTests { + + @Autowired + ArticleService articleService; + + @Autowired + UserService userService; + + private static final String USER_EMAIL = "lee@zerobell.xyz"; + private static final String USER_NICKNAME = "이영종"; + private static final String USER_PASSWORD = "1q2w3e4r!"; + private static final String USER_AVATAR = "test.png"; + + private static final String TITLE = "본인 방금 튭스데이로 뗴돈 버는 상상함 ㅋ"; + private static final String CONTENT = "하지만 어림도 없지"; + private static final String VIDEO_ID = "24iwWFzaSWU"; + + private User author; + + @Before + public void init() { + author = userService.createUser(USER_EMAIL, USER_PASSWORD, USER_NICKNAME, USER_AVATAR); + } + + @Test + public void createArticle_test() { + final List tags = List.of("ASMR", "공부"); + Article article = articleService.createArticle(author.getUserNo(), TITLE, CONTENT, VIDEO_ID, tags); + assertNotNull(article.getAritcleNo()); + assertEquals(author, article.getAuthor()); + assertEquals(TITLE, article.getTitle()); + assertEquals(CONTENT, article.getContent()); + assertEquals(VIDEO_ID, article.getVideoId()); + + assertNotNull(article.getTagMatchList()); + + final List savedTags = article.getTagMatchList().stream().map(e -> e.getTag().getTagName()).collect(Collectors.toList()); + + assertTrue(savedTags.containsAll(tags)); + assertTrue(tags.containsAll(savedTags)); + } +} diff --git a/src/test/java/h3s1/tubesday/user/UserServiceTests.java b/src/test/java/h3s1/tubesday/user/UserServiceTests.java new file mode 100644 index 0000000..bfc7ccd --- /dev/null +++ b/src/test/java/h3s1/tubesday/user/UserServiceTests.java @@ -0,0 +1,54 @@ +package h3s1.tubesday.user; + +import h3s1.tubesday.api.ServiceErrorMessage; +import h3s1.tubesday.api.ServiceException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +@SpringBootTest +@RunWith(SpringJUnit4ClassRunner.class) +public class UserServiceTests { + + @Autowired + private UserService userService; + + @Test + public void createUser_test() { + User user = userService.createUser("lee@zerobell.xyz", "12341234", "이영종이담마", "test.png"); + assertNotNull(user.getUserNo()); + } + + @Test(expected = DataIntegrityViolationException.class) + public void createUser_withDuplicatedEmailTest() { + User user = userService.createUser("lee@zerobell.xyz", "12341234", "이영종이담마", "test.png"); + assertNotNull(user.getUserNo()); + + userService.createUser("lee@zerobell.xyz", "998877", "이영종이라곰마", "test.png"); + } + + @Test + public void createUser_withInvalidEmailTest() { + try { + userService.createUser("아힝홍항", "112233", "이영종", "test.png"); + fail(); + } catch (ServiceException ex) { + assertEquals(ServiceErrorMessage.EMAIL_TYPE_INVALID, ex.getCode()); + } + } + + @Test + public void createUser_withInvalidPasswordTest() { + try { + userService.createUser("lee@zerobell.xyz", "", "이영종", "test.png"); + fail(); + } catch (ServiceException ex) { + assertEquals(ServiceErrorMessage.PASSWORD_TYPE_INVALID, ex.getCode()); + } + } +}