diff --git a/.github/workflows/autoTest.yml b/.github/workflows/autoTest.yml index 284811d..8e1a4e2 100644 --- a/.github/workflows/autoTest.yml +++ b/.github/workflows/autoTest.yml @@ -33,4 +33,52 @@ jobs: if: always() with: junit_files: "build/test-results/test/**/*.xml" - + + # 전송할 파일을 담을 디렉토리 생성 + - name: Make Directory for deliver + run: mkdir deploy + + # Jar 파일 Copy + - name: Copy Jar + run: cp ./build/libs/*.jar ./deploy/ + + # appspec.yml 파일 복사 + - name: Copy appspec.yml + run: cp appspec.yml ./deploy + + # deploy.sh 파일 복사 + - name: Copy deploy.sh + run: cp deploy.sh ./deploy + + # test 파일 넘기기 + - name: Copy doc + run: cp -r doc/ ./deploy + working-directory: ${{env.working-directory}} + + # 압축파일 형태로 전달 + - name: Make zip file + run: zip -r -qq -j ./realworld.zip ./deploy + working-directory: ${{env.working-directory}} + + # S3 Bucket으로 copy + - name: Deliver to AWS S3 + env: + AWS_S3_BUCKET: ${{secrets.AWS_PRODUCTION_BUCKET_NAME}} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_IAM_MANAGER_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_IAM_MANAGER_SECRET_ACCESS_KEY }} + AWS_REGION: ap-northeast-2 + run: | + aws s3 cp --region ap-northeast-2 --acl private ./realworld.zip s3://$AWS_S3_BUCKET + + # 배포 + - name: CodeDeploy + env: + AWS_S3_BUCKET: ${{secrets.AWS_PRODUCTION_BUCKET_NAME}} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_IAM_MANAGER_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_IAM_MANAGER_SECRET_ACCESS_KEY }} + run: | + aws deploy create-deployment \ + --application-name real-world \ + --deployment-group-name real-world-group \ + --s3-location bucket=$AWS_S3_BUCKET,key=realworld.zip,bundleType=zip \ + --region ap-northeast-2 diff --git a/README.md b/README.md index 2faf772..7c907c4 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,16 @@ This codebase was created to demonstrate a fully fledged fullstack application b We've gone to great lengths to adhere to the **[Spring Boot]** community styleguides & best practices. For more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo. + +## Enter + +```text + //temp +``` + +## BackEnd - Spring + +### Test Scripts + +image + diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..afa6df6 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,18 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/linux/app + overwrite: yes + +permissions: + - object: / + pattern: "**" + owner: ec2-user + group: ec2-user + +hooks: + ApplicationStart: + - location: deploy.sh + timeout: 60 + runas: ec2-user diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..5943e50 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +REPOSITORY=/home/ubuntu/app + +echo "> 현재 구동 중인 애플리케이션 pid 확인" + +CURRENT_PID=$(pgrep -fl action | grep java | awk '{print $1}') + +echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID" + +if [ -z "$CURRENT_PID" ]; then + echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다." +else + echo "> kill -15 $CURRENT_PID" + kill -15 $CURRENT_PID + sleep 5 +fi + +echo "> 새 애플리케이션 배포" + +JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1) + +echo "> JAR NAME: $JAR_NAME" + +echo "> $JAR_NAME 에 실행권한 추가" + +chmod +x $JAR_NAME + +echo "> $JAR_NAME 실행" + +nohup java -jar $JAR_NAME > $REPOSITORY/nohup.out 2>&1 & \ No newline at end of file diff --git a/src/main/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImpl.java b/src/main/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImpl.java index 59596e1..62dc028 100644 --- a/src/main/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImpl.java +++ b/src/main/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImpl.java @@ -52,13 +52,9 @@ public class ArticleServiceImpl implements ArticleService { if (articleParam.getTag() != null) { articles = articleRepository.findByTag(articleParam.getTag(), pageable); - } - - if(articleParam.getAuthor() != null){ + }else if(articleParam.getAuthor() != null){ articles = articleRepository.findByAuthorName(articleParam.getAuthor(), pageable); - } - - if(articleParam.getFavorited() != null){ + }else if(articleParam.getFavorited() != null){ articles = articleRepository.findByFavoritedUser(articleParam.getFavorited(), pageable); } @@ -76,10 +72,14 @@ public class ArticleServiceImpl implements ArticleService { Pageable pageable = PageRequest.of(offset,limit); List follows = profileRepository.findByFollowerId(userAuth.getId()); - System.out.println(follows.size()); - follows.stream().forEach(follow -> System.out.println(follow.getFollower().getUsername())); + follows.stream().forEach(follow -> { + String followerName = follow.getFollower().getUsername(); + articles.addAll(articleRepository.findByAuthorName(followerName,pageable)); + }); - return List.of(); + return articles.stream().map(article -> { + return convertDtoWithUser(article,userAuth); + }).collect(Collectors.toList()); } // token을 받을수도 안 받을수도 있음. diff --git a/src/test/java/com/io/realworld/domain/aggregate/article/controller/ArticleControllerTest.java b/src/test/java/com/io/realworld/domain/aggregate/article/controller/ArticleControllerTest.java index 02a2e94..06b31ef 100644 --- a/src/test/java/com/io/realworld/domain/aggregate/article/controller/ArticleControllerTest.java +++ b/src/test/java/com/io/realworld/domain/aggregate/article/controller/ArticleControllerTest.java @@ -5,18 +5,12 @@ import com.io.realworld.config.WithAuthUser; import com.io.realworld.domain.aggregate.article.dto.*; import com.io.realworld.domain.aggregate.article.service.ArticleService; import com.io.realworld.domain.aggregate.article.service.CommentService; -import com.io.realworld.domain.aggregate.profile.dto.ProfileResponse; import com.io.realworld.domain.aggregate.user.dto.UserAuth; -import com.io.realworld.domain.aggregate.user.dto.UserUpdate; import com.io.realworld.domain.service.JwtService; -import lombok.With; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -28,7 +22,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.List; -import java.util.stream.Stream; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -70,6 +63,7 @@ class ArticleControllerTest { slug = title.toLowerCase().replace(' ','-'); articleResponse = ArticleResponse.builder() .author(ArticleResponse.Author.builder().bio("bio") + .username("kms") .following(false) .username("madeArticle") .image("image") @@ -89,6 +83,40 @@ class ArticleControllerTest { .build(); } + @WithAuthUser + @Test + @DisplayName("게시글들 가져오기 컨트롤러 테스트") + void getArticles() throws Exception{ + List articleResponses = List.of(articleResponse); + when(articleService.getArticles(any(UserAuth.class), any(ArticleParam.class))).thenReturn(articleResponses); + + mockMvc.perform(get("/api/articles" + "?author=kms")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.articles[0]", Matchers.notNullValue(ArticleResponse.class))) + .andExpect(jsonPath("$.articles[0].title",Matchers.equalTo(articleResponses.get(0).getTitle()))) + .andExpect(jsonPath("$.articles[0].description",Matchers.equalTo(articleResponses.get(0).getDescription()))) + .andExpect(jsonPath("$.articles[0].body",Matchers.equalTo(articleResponses.get(0).getBody()))) + .andExpect(jsonPath("$.articles[0].slug",Matchers.equalTo(articleResponses.get(0).getSlug()))) + .andExpect(jsonPath("$.articles[0].tagList",Matchers.equalTo(articleResponses.get(0).getTagList()))); + } + + @WithAuthUser + @Test + @DisplayName("팔로우한 유저 게시글 가져오기 컨트롤러 테스트") + void getFeed() throws Exception{ + List articleResponses = List.of(articleResponse); + when(articleService.getFeed(any(UserAuth.class), any(FeedParam.class))).thenReturn(articleResponses); + + mockMvc.perform(get("/api/articles/feed")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.articles[0]", Matchers.notNullValue(ArticleResponse.class))) + .andExpect(jsonPath("$.articles[0].title",Matchers.equalTo(articleResponses.get(0).getTitle()))) + .andExpect(jsonPath("$.articles[0].description",Matchers.equalTo(articleResponses.get(0).getDescription()))) + .andExpect(jsonPath("$.articles[0].body",Matchers.equalTo(articleResponses.get(0).getBody()))) + .andExpect(jsonPath("$.articles[0].slug",Matchers.equalTo(articleResponses.get(0).getSlug()))) + .andExpect(jsonPath("$.articles[0].tagList",Matchers.equalTo(articleResponses.get(0).getTagList()))); + } + @WithAuthUser(email = "test@gmail.com", username = "kms", id = 1L) @Test @DisplayName("게시글 만들기 컨트롤러 테스트") diff --git a/src/test/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImplTest.java b/src/test/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImplTest.java index 439383d..b3f53cf 100644 --- a/src/test/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImplTest.java +++ b/src/test/java/com/io/realworld/domain/aggregate/article/service/ArticleServiceImplTest.java @@ -1,32 +1,36 @@ package com.io.realworld.domain.aggregate.article.service; -import com.io.realworld.domain.aggregate.article.dto.ArticleResponse; -import com.io.realworld.domain.aggregate.article.dto.ArticleUpdate; -import com.io.realworld.domain.aggregate.article.dto.Articledto; +import com.io.realworld.domain.aggregate.article.dto.*; import com.io.realworld.domain.aggregate.article.entity.Article; import com.io.realworld.domain.aggregate.article.entity.Favorite; import com.io.realworld.domain.aggregate.article.repository.ArticleRepository; import com.io.realworld.domain.aggregate.article.repository.FavoriteRepository; import com.io.realworld.domain.aggregate.profile.dto.ProfileResponse; +import com.io.realworld.domain.aggregate.profile.entity.Follow; +import com.io.realworld.domain.aggregate.profile.repository.ProfileRepository; import com.io.realworld.domain.aggregate.profile.service.ProfileService; +import com.io.realworld.domain.aggregate.tag.entity.Tag; import com.io.realworld.domain.aggregate.tag.service.TagService; import com.io.realworld.domain.aggregate.user.dto.UserAuth; import com.io.realworld.domain.aggregate.user.entity.User; import com.io.realworld.domain.aggregate.user.repository.UserRepository; import com.io.realworld.exception.CustomException; import com.io.realworld.exception.Error; +import org.junit.jupiter.api.BeforeEach; 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.data.domain.Pageable; import org.springframework.http.HttpStatus; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -36,6 +40,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class ArticleServiceImplTest { @@ -57,6 +62,72 @@ class ArticleServiceImplTest { @Mock ProfileService profileService; + @Mock + ProfileRepository profileRepository; + + + @Test + @DisplayName("sv: 게시글들 가져오기 - tag가 일치") + void getArticlesTag(){ + UserAuth userAuth = UserAuth.builder().id(1L).username("kms").build(); + List
articles = new ArrayList
(){{ + add(articlesListGet().get(0)); + }}; + ArticleParam articleParam = new ArticleParam(); + articleParam.setTag("blogTag"); + + when(profileService.getProfile(eq(userAuth), any(String.class))).thenReturn(ProfileResponse.builder().username(articles.get(0).getAuthor().getUsername()).build()); + when(articleRepository.findByTag(eq(articleParam.getTag()),any(Pageable.class))).thenReturn(articles); + List articleResponses = articleService.getArticles(userAuth, articleParam); + + assertThat(articleResponses.get(0).getTagList().get(0)).isEqualTo(articleParam.getTag()); + } + + @Test + @DisplayName("sv: 게시글들 가져오기 - author가 일치") + void getArticlesAuthor(){ + UserAuth userAuth = UserAuth.builder().id(1L).username("kms").build(); + + ArticleParam articleParam = new ArticleParam(); + articleParam.setAuthor("jyb"); + + List
articles = articlesListGet().stream().filter(article -> { + return article.getAuthor().getUsername().equals(articleParam.getAuthor()); + }).collect(Collectors.toList()); + + + when(profileService.getProfile(eq(userAuth), any(String.class))).thenReturn(ProfileResponse.builder().username(articles.get(0).getAuthor().getUsername()).build()); + when(articleRepository.findByAuthorName(eq(articleParam.getAuthor()),any(Pageable.class))).thenReturn(articles); + List articleResponses = articleService.getArticles(userAuth, articleParam); + + assertThat(articleResponses.get(0).getAuthor().getUsername()).isEqualTo(articleParam.getAuthor()); + } + + @Test + @DisplayName("sv: 피드 게시글 가져오기 ") + void getFeed(){ + UserAuth userAuth = UserAuth.builder().id(1L).username("kms").build(); + + FeedParam feedParam = new FeedParam(); + + Article article = articlesListGet().get(2); + List
articles = new ArrayList<>(){{ + add(article); + }}; + + List follows = new ArrayList(){{ + add(Follow.builder().followee(User.builder().username("kms").build()).follower(User.builder().username("eden").build()).build()); + }}; + + when(profileService.getProfile(eq(userAuth), any(String.class))).thenReturn(ProfileResponse.builder().username(articles.get(0).getAuthor().getUsername()).build()); + when(profileRepository.findByFollowerId(any(Long.class))).thenReturn(follows); + when(articleRepository.findByAuthorName(any(String.class),any(Pageable.class))).thenReturn(articles); + List articleResponses = articleService.getFeed(userAuth, feedParam); + + assertThat(articleResponses.get(0).getAuthor().getUsername()).isEqualTo(follows.get(0).getFollower().getUsername()); + } + + @Test @DisplayName("sv: 게시글 만들기 성공") void createArticle() { @@ -397,6 +468,65 @@ class ArticleServiceImplTest { } + List
articlesListGet(){ + List blogTags = new ArrayList(){{ + add(Tag.builder().tagName("blogTag").build()); + add(Tag.builder().tagName("tutorial").build()); + }}; + List dietTags = new ArrayList(){{ + add(Tag.builder().tagName("dietTag").build()); + add(Tag.builder().tagName("tutorial").build()); + }}; + Article blogPost = Article.builder() + .id(1L) + .slug("post-my-blog") + .author(User.builder() + .username("kms") + .image("blog image") + .bio("blog bio") + .email("kms@naver.com").build()) + .body("blog Post very ez") + .tagList(blogTags) + .description("blog create") + .title("Post My Blog") + .build(); + + Article dietPost = Article.builder() + .id(2L) + .slug("post-week-diet") + .author(User.builder() + .username("jyb") + .image("diet image") + .bio("diet bio") + .email("jyb@naver.com").build()) + .body("diet very hard") + .tagList(dietTags) + .description("blog create") + .title("Post week diet") + .build(); + + Article codePost = Article.builder() + .id(3L) + .slug("post-coding-test") + .author(User.builder() + .username("eden") + .image("programmer") + .bio("s") + .email("tesla@google.com").build()) + .body("Tesla very nice") + .tagList(dietTags) + .description("realworldApp") + .title("post Coding Test") + .build(); + + List
articles = new ArrayList
(){{ + add(blogPost); + add(dietPost); + add(codePost); + }}; + return articles; + } + diff --git a/src/test/java/com/io/realworld/repository/UserRepositoryTest.java b/src/test/java/com/io/realworld/domain/aggregate/user/repository/UserRepositoryTest.java similarity index 96% rename from src/test/java/com/io/realworld/repository/UserRepositoryTest.java rename to src/test/java/com/io/realworld/domain/aggregate/user/repository/UserRepositoryTest.java index a07777e..2eae86f 100644 --- a/src/test/java/com/io/realworld/repository/UserRepositoryTest.java +++ b/src/test/java/com/io/realworld/domain/aggregate/user/repository/UserRepositoryTest.java @@ -1,4 +1,4 @@ -package com.io.realworld.repository; +package com.io.realworld.domain.aggregate.user.repository; import com.io.realworld.domain.aggregate.user.entity.User; import com.io.realworld.domain.aggregate.user.repository.UserRepository; diff --git a/src/test/java/com/io/realworld/service/UserServiceImplTest.java b/src/test/java/com/io/realworld/domain/aggregate/user/service/UserServiceImplTest.java similarity index 99% rename from src/test/java/com/io/realworld/service/UserServiceImplTest.java rename to src/test/java/com/io/realworld/domain/aggregate/user/service/UserServiceImplTest.java index 81e6fa8..5d0f02a 100644 --- a/src/test/java/com/io/realworld/service/UserServiceImplTest.java +++ b/src/test/java/com/io/realworld/domain/aggregate/user/service/UserServiceImplTest.java @@ -1,4 +1,4 @@ -package com.io.realworld.service; +package com.io.realworld.domain.aggregate.user.service; import com.io.realworld.domain.aggregate.user.dto.*; import com.io.realworld.domain.aggregate.user.entity.User;