diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..cf57a323 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI Pipeline + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Grant execute permission for Gradle wrapper + run: chmod +x ./gradlew + + - name: Build project + run: ./gradlew build + + - name: Run tests + run: ./gradlew test diff --git a/src/main/java/io/spring/api/ArticlesApi.java b/src/main/java/io/spring/api/ArticlesApi.java index 50584bd6..a83beb8e 100644 --- a/src/main/java/io/spring/api/ArticlesApi.java +++ b/src/main/java/io/spring/api/ArticlesApi.java @@ -17,7 +17,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Max; +import org.springframework.validation.annotation.Validated; +@Validated @RestController @RequestMapping(path = "/articles") @AllArgsConstructor @@ -47,8 +51,8 @@ public ResponseEntity getFeed( @GetMapping public ResponseEntity getArticles( - @RequestParam(value = "offset", defaultValue = "0") int offset, - @RequestParam(value = "limit", defaultValue = "20") int limit, + @RequestParam(value = "offset", defaultValue = "0") @Min(0) int offset, + @RequestParam(value = "limit", defaultValue = "20") @Min(1) @Max(50) int limit, @RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "favorited", required = false) String favoritedBy, @RequestParam(value = "author", required = false) String author, diff --git a/src/main/java/io/spring/api/UsersApi.java b/src/main/java/io/spring/api/UsersApi.java index d91321e8..295f8434 100644 --- a/src/main/java/io/spring/api/UsersApi.java +++ b/src/main/java/io/spring/api/UsersApi.java @@ -1,7 +1,5 @@ package io.spring.api; -import static org.springframework.web.bind.annotation.RequestMethod.POST; - import com.fasterxml.jackson.annotation.JsonRootName; import io.spring.api.exception.InvalidAuthenticationException; import io.spring.application.UserQueryService; @@ -18,13 +16,16 @@ import javax.validation.Valid; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; +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; @RestController @@ -36,37 +37,35 @@ public class UsersApi { private JwtService jwtService; private UserService userService; - @RequestMapping(path = "/users", method = POST) - public ResponseEntity createUser(@Valid @RequestBody RegisterParam registerParam) { + @PostMapping("/users") + public ResponseEntity> createUser(@Valid @RequestBody RegisterParam registerParam) { User user = userService.createUser(registerParam); UserData userData = userQueryService.findById(user.getId()).get(); - return ResponseEntity.status(201) + return ResponseEntity.status(HttpStatus.CREATED) .body(userResponse(new UserWithToken(userData, jwtService.toToken(user)))); } - @RequestMapping(path = "/users/login", method = POST) - public ResponseEntity userLogin(@Valid @RequestBody LoginParam loginParam) { + @PostMapping("/users/login") + public ResponseEntity> userLogin(@Valid @RequestBody LoginParam loginParam) { Optional optional = userRepository.findByEmail(loginParam.getEmail()); if (optional.isPresent() && passwordEncoder.matches(loginParam.getPassword(), optional.get().getPassword())) { UserData userData = userQueryService.findById(optional.get().getId()).get(); return ResponseEntity.ok( userResponse(new UserWithToken(userData, jwtService.toToken(optional.get())))); - } else { - throw new InvalidAuthenticationException(); } + throw new InvalidAuthenticationException(); } private Map userResponse(UserWithToken userWithToken) { - return new HashMap() { - { - put("user", userWithToken); - } - }; + Map map = new HashMap<>(); + map.put("user", userWithToken); + return map; } } @Getter +@Setter @JsonRootName("user") @NoArgsConstructor class LoginParam { @@ -75,5 +74,6 @@ class LoginParam { private String email; @NotBlank(message = "can't be empty") + @Size(min = 8, max = 128, message = "length must be 8-128") private String password; } diff --git a/src/main/java/io/spring/application/TagsQueryService.java b/src/main/java/io/spring/application/TagsQueryService.java index 12e0790c..d46f2b5c 100644 --- a/src/main/java/io/spring/application/TagsQueryService.java +++ b/src/main/java/io/spring/application/TagsQueryService.java @@ -1,6 +1,7 @@ package io.spring.application; import io.spring.infrastructure.mybatis.readservice.TagReadService; +import java.util.Collections; import java.util.List; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; @@ -13,4 +14,17 @@ public class TagsQueryService { public List allTags() { return tagReadService.all(); } + + public List allTagsSorted(String sortBy) { + List tags = tagReadService.all(); + + if ("name_desc".equals(sortBy)) { + Collections.sort(tags, Collections.reverseOrder()); + } else if ("name_asc".equals(sortBy)) { + Collections.sort(tags); + } else { + Collections.sort(tags); + + return tags; + } } diff --git a/src/main/java/io/spring/application/user/RegisterParam.java b/src/main/java/io/spring/application/user/RegisterParam.java index 3ba1234d..fae9119d 100644 --- a/src/main/java/io/spring/application/user/RegisterParam.java +++ b/src/main/java/io/spring/application/user/RegisterParam.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonRootName; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,9 +19,11 @@ public class RegisterParam { private String email; @NotBlank(message = "can't be empty") + @Size(min = 3, max = 30, message = "length must be 3-30") @DuplicatedUsernameConstraint private String username; @NotBlank(message = "can't be empty") + @Size(min = 8, max = 128, message = "length must be 8-128") private String password; } diff --git a/src/test/java/io/spring/api/ArticleApiTest.java b/src/test/java/io/spring/api/ArticleApiTest.java index df2ebe75..96333e89 100644 --- a/src/test/java/io/spring/api/ArticleApiTest.java +++ b/src/test/java/io/spring/api/ArticleApiTest.java @@ -34,16 +34,20 @@ import org.springframework.context.annotation.Import; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest({ArticleApi.class}) -@Import({WebSecurityConfig.class, JacksonCustomizations.class}) +@WebMvcTest({ ArticleApi.class }) +@Import({ WebSecurityConfig.class, JacksonCustomizations.class }) public class ArticleApiTest extends TestWithCurrentUser { - @Autowired private MockMvc mvc; + @Autowired + private MockMvc mvc; - @MockBean private ArticleQueryService articleQueryService; + @MockBean + private ArticleQueryService articleQueryService; - @MockBean private ArticleRepository articleRepository; + @MockBean + private ArticleRepository articleRepository; - @MockBean ArticleCommandService articleCommandService; + @MockBean + ArticleCommandService articleCommandService; @Override @BeforeEach @@ -56,14 +60,13 @@ public void setUp() throws Exception { public void should_read_article_success() throws Exception { String slug = "test-new-article"; DateTime time = new DateTime(); - Article article = - new Article( - "Test New Article", - "Desc", - "Body", - Arrays.asList("java", "spring", "jpg"), - user.getId(), - time); + Article article = new Article( + "Test New Article", + "Desc", + "Body", + Arrays.asList("java", "spring", "jpg"), + user.getId(), + time); ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user); when(articleQueryService.findBySlug(eq(slug), eq(null))).thenReturn(Optional.of(articleData)); @@ -80,25 +83,21 @@ public void should_read_article_success() throws Exception { @Test public void should_404_if_article_not_found() throws Exception { when(articleQueryService.findBySlug(anyString(), any())).thenReturn(Optional.empty()); - RestAssuredMockMvc.when().get("/articles/not-exists").then().statusCode(404); + RestAssuredMockMvc.when().get("/articles/not-exists").then().statusCode(404).body("errors.body[0]", equalTo("article not found")); } @Test public void should_update_article_content_success() throws Exception { List tagList = Arrays.asList("java", "spring", "jpg"); - Article originalArticle = - new Article("old title", "old description", "old body", tagList, user.getId()); + Article originalArticle = new Article("old title", "old description", "old body", tagList, user.getId()); - Article updatedArticle = - new Article("new title", "new description", "new body", tagList, user.getId()); + Article updatedArticle = new Article("new title", "new description", "new body", tagList, user.getId()); - Map updateParam = - prepareUpdateParam( - updatedArticle.getTitle(), updatedArticle.getBody(), updatedArticle.getDescription()); + Map updateParam = prepareUpdateParam( + updatedArticle.getTitle(), updatedArticle.getBody(), updatedArticle.getDescription()); - ArticleData updatedArticleData = - TestHelper.getArticleDataFromArticleAndUser(updatedArticle, user); + ArticleData updatedArticleData = TestHelper.getArticleDataFromArticleAndUser(updatedArticle, user); when(articleRepository.findBySlug(eq(originalArticle.getSlug()))) .thenReturn(Optional.of(originalArticle)); @@ -127,29 +126,27 @@ public void should_get_403_if_not_author_to_update_article() throws Exception { User anotherUser = new User("test@test.com", "test", "123123", "", ""); - Article article = - new Article( - title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId()); + Article article = new Article( + title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId()); DateTime time = new DateTime(); - ArticleData articleData = - new ArticleData( - article.getId(), - article.getSlug(), - article.getTitle(), - article.getDescription(), - article.getBody(), - false, - 0, - time, - time, - Arrays.asList("joda"), - new ProfileData( - anotherUser.getId(), - anotherUser.getUsername(), - anotherUser.getBio(), - anotherUser.getImage(), - false)); + ArticleData articleData = new ArticleData( + article.getId(), + article.getSlug(), + article.getTitle(), + article.getDescription(), + article.getBody(), + false, + 0, + time, + time, + Arrays.asList("joda"), + new ProfileData( + anotherUser.getId(), + anotherUser.getUsername(), + anotherUser.getBio(), + anotherUser.getImage(), + false)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))) @@ -171,8 +168,7 @@ public void should_delete_article_success() throws Exception { String body = "body"; String description = "description"; - Article article = - new Article(title, description, body, Arrays.asList("java", "spring", "jpg"), user.getId()); + Article article = new Article(title, description, body, Arrays.asList("java", "spring", "jpg"), user.getId()); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); given() @@ -193,9 +189,8 @@ public void should_403_if_not_author_delete_article() throws Exception { User anotherUser = new User("test@test.com", "test", "123123", "", ""); - Article article = - new Article( - title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId()); + Article article = new Article( + title, description, body, Arrays.asList("java", "spring", "jpg"), anotherUser.getId()); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); given()