From 5fdc5be16e742fc49b1465c439217b29a3cf39ac Mon Sep 17 00:00:00 2001 From: sujin Date: Sun, 18 Feb 2024 23:04:24 +0900 Subject: [PATCH 001/103] =?UTF-8?q?:test=5Ftube:=20test:=20CoinAdminServic?= =?UTF-8?q?e=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=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 --- .../coin/service/CoinAdminServiceTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/test/java/com/gg/server/admin/coin/service/CoinAdminServiceTest.java diff --git a/src/test/java/com/gg/server/admin/coin/service/CoinAdminServiceTest.java b/src/test/java/com/gg/server/admin/coin/service/CoinAdminServiceTest.java new file mode 100644 index 000000000..e73356a0b --- /dev/null +++ b/src/test/java/com/gg/server/admin/coin/service/CoinAdminServiceTest.java @@ -0,0 +1,64 @@ +package com.gg.server.admin.coin.service; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 com.gg.server.admin.coin.dto.CoinUpdateRequestDto; +import com.gg.server.data.store.CoinHistory; +import com.gg.server.data.user.User; +import com.gg.server.domain.coin.service.CoinHistoryService; +import com.gg.server.domain.user.data.UserRepository; +import com.gg.server.domain.user.exception.UserNotFoundException; +import com.gg.server.utils.annotation.UnitTest; + +@UnitTest +@ExtendWith(MockitoExtension.class) +@DisplayName("CoinAdminServiceTest") +class CoinAdminServiceTest { + @Mock + UserRepository userRepository; + @Mock + CoinHistoryService coinHistoryService; + @InjectMocks + CoinAdminService coinAdminService; + + @Nested + @DisplayName("updateUserCoin 메서드 unitTest") + class UpdateUserCoinTest { + @Test + @DisplayName("성공") + void success() { + //given + CoinUpdateRequestDto requestDto = new CoinUpdateRequestDto("testId", 10, "test"); + User user = mock(User.class); + given(userRepository.findByIntraId(any(String.class))).willReturn(Optional.of(user)); + //when + coinAdminService.updateUserCoin(requestDto); + //then + verify(user).addGgCoin(requestDto.getChange()); + verify(coinHistoryService).addCoinHistory(any(CoinHistory.class)); + } + + @Test + @DisplayName("UserNotFound") + void userNotFound() { + //given + CoinUpdateRequestDto requestDto = new CoinUpdateRequestDto("testId", 10, "test"); + given(userRepository.findByIntraId(requestDto.getIntraId())).willReturn(Optional.empty()); + //when, then + Assertions.assertThatThrownBy(() -> coinAdminService.updateUserCoin(requestDto)) + .isInstanceOf(UserNotFoundException.class); + } + } +} From 0e04e0eea26491d1e2ff3fcebefc3bfbab1d108a Mon Sep 17 00:00:00 2001 From: sujin Date: Sun, 18 Feb 2024 23:05:20 +0900 Subject: [PATCH 002/103] =?UTF-8?q?:green=5Fheart:=20chore:=20=EC=9E=90?= =?UTF-8?q?=EB=B0=94=EB=8F=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gg/server/admin/coin/service/CoinAdminService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/gg/server/admin/coin/service/CoinAdminService.java b/src/main/java/com/gg/server/admin/coin/service/CoinAdminService.java index 65e8227c7..6af21dae7 100644 --- a/src/main/java/com/gg/server/admin/coin/service/CoinAdminService.java +++ b/src/main/java/com/gg/server/admin/coin/service/CoinAdminService.java @@ -18,6 +18,11 @@ public class CoinAdminService { private final UserRepository userRepository; private final CoinHistoryService coinHistoryService; + /*** + * 유저의 코인 정보를 업데이트합니다. + * @param coinUpdateRequestDto 코인 업데이트에 필요한 Dto + * @throws UserNotFoundException 유저가 존재하지 않을 경우 + */ @Transactional public void updateUserCoin(CoinUpdateRequestDto coinUpdateRequestDto) { User user = userRepository.findByIntraId(coinUpdateRequestDto.getIntraId()) From 2e590469e9ed2907706790f4c91e7cd02a370ee3 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 18:41:28 +0900 Subject: [PATCH 003/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 52 ++++++++++++++++++------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 9867ee258..23217e54e 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -47,8 +47,8 @@ jobs: username: ${{ env.DOCKER_USER }} password: ${{ env.DOCKER_PASSWORD }} - - name: Test with Gradle - run: ./gradlew clean test + # - name: Test with Gradle + # run: ./gradlew clean test - name: Build with Gradle run: ./gradlew clean build -x test @@ -62,30 +62,38 @@ jobs: id: ip uses: haythem/public-ip@v1.3 - - name: AWS Credentials + - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_TEST_MIGRATE_SECURITY_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_TEST_MIGRATE_SECURITY_SECRET_KEY }} aws-region: ap-northeast-2 - - name: Add Github Actions IP to Security group + - name: Trigger Instance Refresh run: | - aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 - - - name: executing docker-compose up on test server - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.TEST_MIGRATE_SERVER_HOST }} - username: ${{ secrets.TEST_MIGRATE_SERVER_USERNAME }} - key: ${{ secrets.TEST_MIGRATE_SERVER_PEM }} - script: | - cd /home/ec2-user/docker - docker-compose down tomcat - docker rmi ${{ env.IMAGE_NAME }}:latest - docker-compose up tomcat -d - docker-compose up prometheus -d - - - name: Remove Github Actions IP From Security Group - run: | - aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + aws autoscaling start-instance-refresh \ + --auto-scaling-group-name gg-dev \ + --strategy Rolling \ + --preferences '{"MinHealthyPercentage": + 100, "InstanceWarmup": 300}' + +# - name: Add Github Actions IP to Security group +# run: | +# aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 +# +# - name: executing docker-compose up on test server +# uses: appleboy/ssh-action@master +# with: +# host: ${{ secrets.TEST_MIGRATE_SERVER_HOST }} +# username: ${{ secrets.TEST_MIGRATE_SERVER_USERNAME }} +# key: ${{ secrets.TEST_MIGRATE_SERVER_PEM }} +# script: | +# cd /home/ec2-user/docker +# docker-compose down tomcat +# docker rmi ${{ env.IMAGE_NAME }}:latest +# docker-compose up tomcat -d +# docker-compose up prometheus -d +# +# - name: Remove Github Actions IP From Security Group +# run: | +# aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 From cac0cc844e67bf36d0bd2c8779fceaae7753fcdd Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 19:47:38 +0900 Subject: [PATCH 004/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/party/api/user/room/controller/RoomController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java b/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java index 1934911c1..7091fefce 100644 --- a/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java +++ b/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java @@ -33,6 +33,11 @@ public class RoomController { private final RoomFindService roomFindService; private final RoomManagementService roomManagementService; + @GetMapping("/test") + public String newFeature() { + return "new"; + } + /** * 시작하지 않은 방과 시작한 방을 모두 조회한다 * @return 시작하지 않은 방 (최신순) + 시작한 방(끝나는 시간이 빠른 순) 전체 List - 200 From d0acfa54076227f4eed31d533d896077060ab7ff Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 20:56:34 +0900 Subject: [PATCH 005/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/party/api/user/room/controller/RoomController.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java b/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java index 7091fefce..1934911c1 100644 --- a/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java +++ b/gg-pingpong-api/src/main/java/gg/party/api/user/room/controller/RoomController.java @@ -33,11 +33,6 @@ public class RoomController { private final RoomFindService roomFindService; private final RoomManagementService roomManagementService; - @GetMapping("/test") - public String newFeature() { - return "new"; - } - /** * 시작하지 않은 방과 시작한 방을 모두 조회한다 * @return 시작하지 않은 방 (최신순) + 시작한 방(끝나는 시간이 빠른 순) 전체 List - 200 From a329fcd6a4f12fc05cb0c5bc0864ddb763e5658a Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 21:16:10 +0900 Subject: [PATCH 006/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 23217e54e..9a201b139 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -76,6 +76,7 @@ jobs: --strategy Rolling \ --preferences '{"MinHealthyPercentage": 100, "InstanceWarmup": 300}' + --launch-template '{"LaunchTemplateId":"${{ secrets.DEV_LAUNCH_TEMPLATE_ID }}","Version":"$Latest"}' # - name: Add Github Actions IP to Security group # run: | From fd132897f4de63bf40e4ff680bafd90a938b91c1 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 21:26:06 +0900 Subject: [PATCH 007/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 9a201b139..d3bdcfbcf 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -58,9 +58,9 @@ jobs: docker build -t ${{ env.IMAGE_NAME }}:latest . docker push ${{ env.IMAGE_NAME }}:latest - - name: Get Github Actions IP - id: ip - uses: haythem/public-ip@v1.3 + # - name: Get Github Actions IP + # id: ip + # uses: haythem/public-ip@v1.3 - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 @@ -75,7 +75,7 @@ jobs: --auto-scaling-group-name gg-dev \ --strategy Rolling \ --preferences '{"MinHealthyPercentage": - 100, "InstanceWarmup": 300}' + 100, "InstanceWarmup": 300}' \ --launch-template '{"LaunchTemplateId":"${{ secrets.DEV_LAUNCH_TEMPLATE_ID }}","Version":"$Latest"}' # - name: Add Github Actions IP to Security group From b8d8ce296e403be491ddfabc202bc5b2b921cc41 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 21:37:09 +0900 Subject: [PATCH 008/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index d3bdcfbcf..1ad68fe26 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -71,12 +71,14 @@ jobs: - name: Trigger Instance Refresh run: | + aws autoscaling update-auto-scaling-group --auto-scaling-group-name gg-dev \ + --launch-template LaunchTemplateId=${{ secrets.DEV_LAUNCH_TEMPLATE_ID }},Version='$Latest' + aws autoscaling start-instance-refresh \ --auto-scaling-group-name gg-dev \ --strategy Rolling \ --preferences '{"MinHealthyPercentage": - 100, "InstanceWarmup": 300}' \ - --launch-template '{"LaunchTemplateId":"${{ secrets.DEV_LAUNCH_TEMPLATE_ID }}","Version":"$Latest"}' + 100, "InstanceWarmup": 300}' # - name: Add Github Actions IP to Security group # run: | From abfaa8730765d098e595e56cfd75ded39481aa12 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 21:47:49 +0900 Subject: [PATCH 009/103] =?UTF-8?q?[FIX]=20test=20=EB=AC=B4=EC=A4=91?= =?UTF-8?q?=EB=8B=A8=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 10 ++-------- dev-autoscaling.json | 13 +++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 dev-autoscaling.json diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 1ad68fe26..2ae87d14c 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -71,14 +71,8 @@ jobs: - name: Trigger Instance Refresh run: | - aws autoscaling update-auto-scaling-group --auto-scaling-group-name gg-dev \ - --launch-template LaunchTemplateId=${{ secrets.DEV_LAUNCH_TEMPLATE_ID }},Version='$Latest' - - aws autoscaling start-instance-refresh \ - --auto-scaling-group-name gg-dev \ - --strategy Rolling \ - --preferences '{"MinHealthyPercentage": - 100, "InstanceWarmup": 300}' + aws autoscaling start-instance-refresh --cli-input-json file://dev-autoscaling.json + # - name: Add Github Actions IP to Security group # run: | diff --git a/dev-autoscaling.json b/dev-autoscaling.json new file mode 100644 index 000000000..eedefe4bd --- /dev/null +++ b/dev-autoscaling.json @@ -0,0 +1,13 @@ +{ + "AutoScalingGroupName": "gg-dev", + "DesiredConfiguration": { + "LaunchTemplate": { + "LaunchTemplateId": "lt-0a57e7db5ccffce75", + "Version": "$Latest" + } + }, + "Preferences": { + "MinHealthyPercentage": 100, + "InstanceWarmup": 300 + } +} From 5e503a1b653e76cda5d858495298683ed8f7f2ab Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 21:57:20 +0900 Subject: [PATCH 010/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-autoscaling.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-autoscaling.json b/dev-autoscaling.json index eedefe4bd..5f2e9d8ec 100644 --- a/dev-autoscaling.json +++ b/dev-autoscaling.json @@ -8,6 +8,8 @@ }, "Preferences": { "MinHealthyPercentage": 100, - "InstanceWarmup": 300 + "InstanceWarmup": 300, + "ScaleInProtectedInstances": "Ignore", + "StandbyInstances": "Ignore" } } From 9b18a9542a39b831ec3f4cf3f19086cb4c950c0e Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:10:28 +0900 Subject: [PATCH 011/103] =?UTF-8?q?[FIX]=20test=20=EB=AC=B4=EC=A4=91?= =?UTF-8?q?=EB=8B=A8=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-autoscaling.json | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-autoscaling.json b/dev-autoscaling.json index 5f2e9d8ec..9f88c272e 100644 --- a/dev-autoscaling.json +++ b/dev-autoscaling.json @@ -8,6 +8,7 @@ }, "Preferences": { "MinHealthyPercentage": 100, + "MaxHealthyPercentage": 110, "InstanceWarmup": 300, "ScaleInProtectedInstances": "Ignore", "StandbyInstances": "Ignore" From 080a5e254467298537492a183984aea3c6628260 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:24:16 +0900 Subject: [PATCH 012/103] =?UTF-8?q?[FIX]=20action=20secret=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20LaunchTemplateId=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 47 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 2ae87d14c..1b6f322f0 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -58,9 +58,26 @@ jobs: docker build -t ${{ env.IMAGE_NAME }}:latest . docker push ${{ env.IMAGE_NAME }}:latest - # - name: Get Github Actions IP - # id: ip - # uses: haythem/public-ip@v1.3 + - name: Create the configuration file + run: | + cat << EOF > config.json + { + "AutoScalingGroupName": "gg-dev", + "DesiredConfiguration": { + "LaunchTemplate": { + "LaunchTemplateId": "${{ secrets.DEV_LAUNCH_TEMPLATE_ID }}", + "Version": "$Latest" + } + }, + "Preferences": { + "MinHealthyPercentage": 100, + "MaxHealthyPercentage": 110, + "InstanceWarmup": 300, + "ScaleInProtectedInstances": "Ignore", + "StandbyInstances": "Ignore" + } + } + EOF - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 @@ -71,26 +88,4 @@ jobs: - name: Trigger Instance Refresh run: | - aws autoscaling start-instance-refresh --cli-input-json file://dev-autoscaling.json - - -# - name: Add Github Actions IP to Security group -# run: | -# aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 -# -# - name: executing docker-compose up on test server -# uses: appleboy/ssh-action@master -# with: -# host: ${{ secrets.TEST_MIGRATE_SERVER_HOST }} -# username: ${{ secrets.TEST_MIGRATE_SERVER_USERNAME }} -# key: ${{ secrets.TEST_MIGRATE_SERVER_PEM }} -# script: | -# cd /home/ec2-user/docker -# docker-compose down tomcat -# docker rmi ${{ env.IMAGE_NAME }}:latest -# docker-compose up tomcat -d -# docker-compose up prometheus -d -# -# - name: Remove Github Actions IP From Security Group -# run: | -# aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_TEST_MIGRATE_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + aws autoscaling start-instance-refresh --cli-input-json file://config.json From 8608f9aa73e32ecad23604e323b4167daf92ebff Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:30:38 +0900 Subject: [PATCH 013/103] =?UTF-8?q?[FIX]=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 1b6f322f0..c8c7c826c 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -78,6 +78,7 @@ jobs: } } EOF + cat config.json - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 From 71f9b088400bd580210f92e6d77d0bd234603f80 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:36:18 +0900 Subject: [PATCH 014/103] =?UTF-8?q?[FIX]=20test=20=EB=AC=B4=EC=A4=91?= =?UTF-8?q?=EB=8B=A8=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index c8c7c826c..e087bfa2d 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -66,7 +66,7 @@ jobs: "DesiredConfiguration": { "LaunchTemplate": { "LaunchTemplateId": "${{ secrets.DEV_LAUNCH_TEMPLATE_ID }}", - "Version": "$Latest" + "Version": "\$Latest" } }, "Preferences": { From 45d2b474031a9f432382891a7f674ff3bd259ca5 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:39:50 +0900 Subject: [PATCH 015/103] =?UTF-8?q?[FIX]=20config=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-autoscaling.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 dev-autoscaling.json diff --git a/dev-autoscaling.json b/dev-autoscaling.json deleted file mode 100644 index 9f88c272e..000000000 --- a/dev-autoscaling.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "AutoScalingGroupName": "gg-dev", - "DesiredConfiguration": { - "LaunchTemplate": { - "LaunchTemplateId": "lt-0a57e7db5ccffce75", - "Version": "$Latest" - } - }, - "Preferences": { - "MinHealthyPercentage": 100, - "MaxHealthyPercentage": 110, - "InstanceWarmup": 300, - "ScaleInProtectedInstances": "Ignore", - "StandbyInstances": "Ignore" - } -} From a8b46211e710ef506e89d8008d9a8a51c644bb13 Mon Sep 17 00:00:00 2001 From: wken5577 Date: Fri, 19 Apr 2024 22:40:51 +0900 Subject: [PATCH 016/103] =?UTF-8?q?[FIX]=20workflow=20test=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index e087bfa2d..fb05335fe 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -47,8 +47,8 @@ jobs: username: ${{ env.DOCKER_USER }} password: ${{ env.DOCKER_PASSWORD }} - # - name: Test with Gradle - # run: ./gradlew clean test + - name: Test with Gradle + run: ./gradlew clean test - name: Build with Gradle run: ./gradlew clean build -x test From 050c8fac6fe46a555ced49c2d0a811663708923e Mon Sep 17 00:00:00 2001 From: wken5577 Date: Mon, 22 Apr 2024 14:30:35 +0900 Subject: [PATCH 017/103] =?UTF-8?q?[FIX]=20main=20server=20yml=20=EB=AC=B4?= =?UTF-8?q?=EC=A4=91=EB=8B=A8=20=EB=B0=B0=ED=8F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main-deploy.yml | 45 ++++++++++++++++--------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main-deploy.yml b/.github/workflows/main-deploy.yml index ba3edda91..66354b50b 100644 --- a/.github/workflows/main-deploy.yml +++ b/.github/workflows/main-deploy.yml @@ -56,9 +56,27 @@ jobs: docker tag ${{ env.IMAGE_NAME }}:${{ env.VERSION_TAG }} ${{ env.IMAGE_NAME }}:latest docker push ${{ env.IMAGE_NAME }}:latest - - name: Get Github Actions IP - id: ip - uses: haythem/public-ip@v1.3 + - name: Create the configuration file + run: | + cat << EOF > config.json + { + "AutoScalingGroupName": "gg-main", + "DesiredConfiguration": { + "LaunchTemplate": { + "LaunchTemplateId": "${{ secrets.LAUNCH_TEMPLATE_ID }}", + "Version": "\$Latest" + } + }, + "Preferences": { + "MinHealthyPercentage": 100, + "MaxHealthyPercentage": 110, + "InstanceWarmup": 300, + "ScaleInProtectedInstances": "Ignore", + "StandbyInstances": "Ignore" + } + } + EOF + cat config.json - name: AWS Credentials uses: aws-actions/configure-aws-credentials@v2 @@ -67,23 +85,6 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECURITY_SECRET_KEY }} aws-region: ap-northeast-2 - - name: Add Github Actions IP to Security group - run: | - aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_MAIN_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 - - - name: executing docker-compose up on main server - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.MAIN_SERVER_HOST }} - username: ${{ secrets.MAIN_SERVER_USERNAME }} - key: ${{ secrets.MAIN_SERVER_PEM }} - script: | - cd ./docker - docker-compose down tomcat - docker rmi ${{ env.IMAGE_NAME }}:latest - docker-compose up tomcat -d - docker-compose up prometheus -d - - - name: Remove Github Actions IP From Security Group + - name: Trigger Instance Refresh run: | - aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_MAIN_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + aws autoscaling start-instance-refresh --cli-input-json file://config.json From bb28d9e16d1889a7b49d86d0e97044f18ed4f267 Mon Sep 17 00:00:00 2001 From: Hyunkyu Lee <88573971+wken5577@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:54:09 +0900 Subject: [PATCH 018/103] [FIX] # 831 Application status len (#832) --- .../src/main/java/gg/data/recruit/application/Application.java | 2 +- .../db/migration/V2.1__recruit_update_status_length.sql | 2 ++ .../api/admin/controller/request/InterviewRequestDto.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 gg-pingpong-api/src/main/resources/db/migration/V2.1__recruit_update_status_length.sql diff --git a/gg-data/src/main/java/gg/data/recruit/application/Application.java b/gg-data/src/main/java/gg/data/recruit/application/Application.java index 8e8f4ad23..e75505c82 100644 --- a/gg-data/src/main/java/gg/data/recruit/application/Application.java +++ b/gg-data/src/main/java/gg/data/recruit/application/Application.java @@ -49,7 +49,7 @@ public class Application extends BaseTimeEntity { private Boolean isDeleted; @Enumerated(EnumType.STRING) - @Column(length = 15, nullable = false) + @Column(length = 30, nullable = false) private ApplicationStatus status; @OneToMany(mappedBy = "application", fetch = FetchType.LAZY) diff --git a/gg-pingpong-api/src/main/resources/db/migration/V2.1__recruit_update_status_length.sql b/gg-pingpong-api/src/main/resources/db/migration/V2.1__recruit_update_status_length.sql new file mode 100644 index 000000000..0299d3b18 --- /dev/null +++ b/gg-pingpong-api/src/main/resources/db/migration/V2.1__recruit_update_status_length.sql @@ -0,0 +1,2 @@ +ALTER TABLE `application` + MODIFY `status` varchar(30) NOT NULL; diff --git a/gg-recruit-api/src/main/java/gg/recruit/api/admin/controller/request/InterviewRequestDto.java b/gg-recruit-api/src/main/java/gg/recruit/api/admin/controller/request/InterviewRequestDto.java index ab0845af1..3dc0f88f4 100644 --- a/gg-recruit-api/src/main/java/gg/recruit/api/admin/controller/request/InterviewRequestDto.java +++ b/gg-recruit-api/src/main/java/gg/recruit/api/admin/controller/request/InterviewRequestDto.java @@ -13,7 +13,7 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @Getter public class InterviewRequestDto { - public static final String MUST_DOCS_RESULT_STATUS = "PROGRESS_INTERVIEW or FAIL 중 하나를 선택해주세요."; + public static final String MUST_DOCS_RESULT_STATUS = "PROGRESS_INTERVIEW or INTERVIEW_FAIL 중 하나를 선택해주세요."; @NotNull(message = MUST_DOCS_RESULT_STATUS) private ApplicationStatus status; From bd7c51f92724c0d0d0fb123c1d54aa97eb77633f Mon Sep 17 00:00:00 2001 From: Hyunkyu Lee <88573971+wken5577@users.noreply.github.com> Date: Mon, 6 May 2024 10:31:31 +0900 Subject: [PATCH 019/103] =?UTF-8?q?[FIX]=20#833=20error=20redir=20page?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#834)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 5 +++- .../OauthAuthenticationFailureHandler.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java index aedb2ed41..56a62b45d 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java @@ -14,6 +14,7 @@ import gg.pingpong.api.global.security.config.properties.CorsProperties; import gg.pingpong.api.global.security.handler.OAuthAuthenticationSuccessHandler; +import gg.pingpong.api.global.security.handler.OauthAuthenticationFailureHandler; import gg.pingpong.api.global.security.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.security.repository.OAuthAuthorizationRequestBasedOnCookieRepository; import lombok.RequiredArgsConstructor; @@ -23,6 +24,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final OAuthAuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; + private final OauthAuthenticationFailureHandler oauthAuthenticationFailureHandler; private final CorsProperties corsProperties; private final TokenAuthenticationFilter tokenAuthenticationFilter; private final OAuthAuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository; @@ -58,7 +60,8 @@ protected void configure(HttpSecurity http) throws Exception { .baseUri("/oauth2/authorization") .authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository) .and() - .successHandler(oAuth2AuthenticationSuccessHandler); + .successHandler(oAuth2AuthenticationSuccessHandler) + .failureHandler(oauthAuthenticationFailureHandler); http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java new file mode 100644 index 000000000..dcbec7888 --- /dev/null +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java @@ -0,0 +1,28 @@ +package gg.pingpong.api.global.security.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import gg.pingpong.api.global.utils.ApplicationYmlRead; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class OauthAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final ApplicationYmlRead applicationYmlRead; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + exception.printStackTrace(); + response.sendRedirect(applicationYmlRead.getFrontUrl() + "/404"); + } +} From cb9052354b89ee1ec57f9365bdd592acee0aa53f Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:50:45 +0900 Subject: [PATCH 020/103] =?UTF-8?q?[feature]=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=9D=B8=EC=9B=90=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#828)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/TemplateAdminService.java | 16 +++++- .../template/TemplateAdminControllerTest.java | 54 ++++++++++++++++++- .../java/gg/utils/exception/ErrorCode.java | 3 +- .../utils/exception/party/RoomMinMaxTime.java | 10 ++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 gg-utils/src/main/java/gg/utils/exception/party/RoomMinMaxTime.java diff --git a/gg-pingpong-api/src/main/java/gg/party/api/admin/templates/service/TemplateAdminService.java b/gg-pingpong-api/src/main/java/gg/party/api/admin/templates/service/TemplateAdminService.java index b1c52050d..61939cdd0 100644 --- a/gg-pingpong-api/src/main/java/gg/party/api/admin/templates/service/TemplateAdminService.java +++ b/gg-pingpong-api/src/main/java/gg/party/api/admin/templates/service/TemplateAdminService.java @@ -12,6 +12,7 @@ import gg.utils.exception.ErrorCode; import gg.utils.exception.party.CategoryNotFoundException; import gg.utils.exception.party.RoomMinMaxPeople; +import gg.utils.exception.party.RoomMinMaxTime; import gg.utils.exception.party.TemplateNotFoundException; import lombok.RequiredArgsConstructor; @@ -23,9 +24,17 @@ public class TemplateAdminService { /** * 템플릿 추가 - * @exception CategoryNotFoundException 존재하지 않는 카테고리 입력 - 404 + * @throws RoomMinMaxPeople 최소인원이 최대인원보다 큰 경우 - 400 + * @throws gg.utils.exception.party.RoomMinMaxTime 최소시간이 최대시간보다 큰 경우 - 400 + * @throws CategoryNotFoundException 존재하지 않는 카테고리 입력 - 404 */ public void addTemplate(TemplateAdminCreateReqDto request) { + if (request.getMaxGamePeople() < request.getMinGamePeople()) { + throw new RoomMinMaxPeople(ErrorCode.ROOM_MIN_MAX_PEOPLE); + } + if (request.getMinGameTime() > request.getMaxGameTime()) { + throw new RoomMinMaxPeople(ErrorCode.ROOM_MIN_MAX_TIME); + } Category category = categoryRepository.findByName(request.getCategoryName()) .orElseThrow(CategoryNotFoundException::new); GameTemplate gameTemplate = TemplateAdminCreateReqDto.toEntity(request, category); @@ -36,6 +45,7 @@ public void addTemplate(TemplateAdminCreateReqDto request) { * 템플릿 수정 * @throws TemplateNotFoundException 존재하지 않는 템플릿 입력 - 404 * @throws RoomMinMaxPeople 최소인원이 최대인원보다 큰 경우 - 400 + * @throws RoomMinMaxTime 최소시간이 최대시간보다 큰 경우 - 400 * @throws CategoryNotFoundException 존재하지 않는 카테고리 입력 - 404 */ @Transactional @@ -45,6 +55,10 @@ public void modifyTemplate(Long templateId, TemplateAdminUpdateReqDto request) { if (request.getMaxGamePeople() < request.getMinGamePeople()) { throw new RoomMinMaxPeople(ErrorCode.ROOM_MIN_MAX_PEOPLE); } + if (request.getMinGameTime() > request.getMaxGameTime()) { + throw new RoomMinMaxPeople(ErrorCode.ROOM_MIN_MAX_TIME); + } + request.updateEntity(template); if (request.getCategoryName() != null) { diff --git a/gg-pingpong-api/src/test/java/gg/party/api/admin/template/TemplateAdminControllerTest.java b/gg-pingpong-api/src/test/java/gg/party/api/admin/template/TemplateAdminControllerTest.java index 65b66931b..08bb666fc 100644 --- a/gg-pingpong-api/src/test/java/gg/party/api/admin/template/TemplateAdminControllerTest.java +++ b/gg-pingpong-api/src/test/java/gg/party/api/admin/template/TemplateAdminControllerTest.java @@ -103,6 +103,40 @@ public void fail() throws Exception { .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken)) .andExpect(status().isNotFound()); } + + @Test + @DisplayName("최소인원이 최대인원보다 큰 오류 400") + public void notValidMinMaxPeopleFail() throws Exception { + //given + String url = "/party/admin/templates"; + TemplateAdminCreateReqDto templateAdminCreateReqDto = new TemplateAdminCreateReqDto( + "category", "gameName", 2, 4, + 180, 180, "genre", "hard", "summary"); + String jsonRequest = objectMapper.writeValueAsString(templateAdminCreateReqDto); + //when && then + mockMvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("최소시간이 최대시간보다 큰 오류 400") + public void notValidMinMaxTimeFail() throws Exception { + //given + String url = "/party/admin/templates"; + TemplateAdminCreateReqDto templateAdminCreateReqDto = new TemplateAdminCreateReqDto( + "category", "gameName", 4, 2, + 100, 180, "genre", "hard", "summary"); + String jsonRequest = objectMapper.writeValueAsString(templateAdminCreateReqDto); + //when && then + mockMvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken)) + .andExpect(status().isBadRequest()); + } } @Nested @@ -172,7 +206,7 @@ public void noCategoryFail() throws Exception { @Test @DisplayName("최소인원이 최대인원보다 큰 오류 400") - public void notValidMinMaxFail() throws Exception { + public void notValidMinMaxPeopleFail() throws Exception { //given String templateId = testTemplate.getId().toString(); String url = "/party/admin/templates/" + templateId; @@ -188,6 +222,24 @@ public void notValidMinMaxFail() throws Exception { .andExpect(status().isBadRequest()); } + @Test + @DisplayName("최소시간이 최대시간보다 큰 오류 400") + public void notValidMinMaxTimeFail() throws Exception { + //given + String templateId = testTemplate.getId().toString(); + String url = "/party/admin/templates/" + templateId; + TemplateAdminUpdateReqDto templateAdminUpdateReqDto = new TemplateAdminUpdateReqDto( + testCategory.getName(), "newGameName", 8, 4, + 90, 120, "newGenre", "easy", "newSummary"); + String jsonRequest = objectMapper.writeValueAsString(templateAdminUpdateReqDto); + //when && then + mockMvc.perform(patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + userAccessToken)) + .andExpect(status().isBadRequest()); + } + @Test @DisplayName("템플릿 없음으로 인한 수정 실패 404") public void noTemplateFail() throws Exception { diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 5e10f5628..90c578f85 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -180,7 +180,8 @@ public enum ErrorCode { ROOM_NOT_OPEN(400, "PT204", "모집중인 방이 아닙니다."), ROOM_NOT_PARTICIPANT(400, "PT205", "참여하지 않은 방 입니다."), ROOM_MIN_MAX_PEOPLE(400, "PT206", "최소인원이 최대인원보다 큽니다."), - SELF_REPORT(400, "PT207", "자신을 신고할 수 없습니다."), + ROOM_MIN_MAX_TIME(400, "PT207", "최소시간이 최대시간보다 큽니다."), + SELF_REPORT(400, "PT208", "자신을 신고할 수 없습니다."), USER_ALREADY_IN_ROOM(409, "PT301", "이미 참여한 방 입니다."), ALREADY_REPORTED(409, "PT302", "이미 신고한 요청입니다."), CATEGORY_DUPLICATE(409, "PT304", "중복된 카테고리 입니다."), diff --git a/gg-utils/src/main/java/gg/utils/exception/party/RoomMinMaxTime.java b/gg-utils/src/main/java/gg/utils/exception/party/RoomMinMaxTime.java new file mode 100644 index 000000000..819312b07 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/exception/party/RoomMinMaxTime.java @@ -0,0 +1,10 @@ +package gg.utils.exception.party; + +import gg.utils.exception.ErrorCode; +import gg.utils.exception.custom.InvalidParameterException; + +public class RoomMinMaxTime extends InvalidParameterException { + public RoomMinMaxTime(ErrorCode errorCode) { + super(ErrorCode.ROOM_MIN_MAX_TIME.getMessage(), ErrorCode.ROOM_MIN_MAX_TIME); + } +} From d8038b3a2915f9bfddefa414547c80be71ecea93 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:55:21 +0900 Subject: [PATCH 021/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Module=20?= =?UTF-8?q?=EC=82=AC=EC=A0=84=EC=9E=91=EC=97=85=20(#868)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 ++ gg-agenda-api/build.gradle | 29 +++++ .../gg/agenda/GgAgendaApiApplication.java | 9 ++ .../agenda/GgAgendaApiApplicationTests.java | 13 +++ .../src/main/java/gg/data/agenda/Agenda.java | 107 ++++++++++++++++++ .../gg/data/agenda/AgendaAnnouncement.java | 50 ++++++++ .../java/gg/data/agenda/AgendaProfile.java | 55 +++++++++ .../main/java/gg/data/agenda/AgendaTeam.java | 82 ++++++++++++++ .../gg/data/agenda/AgendaTeamProfile.java | 44 +++++++ .../src/main/java/gg/data/agenda/Ticket.java | 47 ++++++++ .../gg/data/agenda/type/AgendaStatus.java | 14 +++ .../gg/data/agenda/type/AgendaTeamStatus.java | 14 +++ .../java/gg/data/agenda/type/Coalition.java | 19 ++++ .../java/gg/data/agenda/type/Location.java | 14 +++ .../resources/db/migration/V3__agenda.sql | 104 +++++++++++++++++ .../agenda/AgendaAnnouncementRepository.java | 8 ++ .../repo/agenda/AgendaProfileRepository.java | 8 ++ .../java/gg/repo/agenda/AgendaRepository.java | 8 ++ .../agenda/AgendaTeamProfileRepository.java | 8 ++ .../gg/repo/agenda/AgendaTeamRepository.java | 8 ++ .../java/gg/repo/agenda/TicketRepository.java | 8 ++ settings.gradle | 2 +- 22 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 gg-agenda-api/build.gradle create mode 100644 gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java create mode 100644 gg-data/src/main/java/gg/data/agenda/Agenda.java create mode 100644 gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java create mode 100644 gg-data/src/main/java/gg/data/agenda/AgendaProfile.java create mode 100644 gg-data/src/main/java/gg/data/agenda/AgendaTeam.java create mode 100644 gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java create mode 100644 gg-data/src/main/java/gg/data/agenda/Ticket.java create mode 100644 gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java create mode 100644 gg-data/src/main/java/gg/data/agenda/type/AgendaTeamStatus.java create mode 100644 gg-data/src/main/java/gg/data/agenda/type/Coalition.java create mode 100644 gg-data/src/main/java/gg/data/agenda/type/Location.java create mode 100644 gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java diff --git a/build.gradle b/build.gradle index 7233d1bff..40f156f7d 100644 --- a/build.gradle +++ b/build.gradle @@ -267,6 +267,18 @@ project(':gg-recruit-api') { } } +project(':gg-agenda-api') { + bootJar { enabled = false } + jar { enabled = true } + dependencies { + implementation project(':gg-data') + implementation project(':gg-repo') + implementation project(':gg-admin-repo') + implementation project(':gg-utils') + implementation project(':gg-auth') + } +} + project(':gg-auth') { bootJar { enabled = false } jar { enabled = true } diff --git a/gg-agenda-api/build.gradle b/gg-agenda-api/build.gradle new file mode 100644 index 000000000..4f8e11d62 --- /dev/null +++ b/gg-agenda-api/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' +} + +group 'gg.api' +version '42gg' + +repositories { + mavenCentral() +} + +dependencies { + /* spring */ + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-mail' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + + /* StringUtils */ + implementation 'org.apache.commons:commons-lang3:3.12.0' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testImplementation testFixtures(project(':gg-utils')) +} + +test { + useJUnitPlatform() +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java b/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java new file mode 100644 index 000000000..2948f75a9 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java @@ -0,0 +1,9 @@ +package gg.agenda; + +public class GgAgendaApiApplication { + + public static void main(String[] args) { + ; + } + +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java b/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java new file mode 100644 index 000000000..e03e5c753 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java @@ -0,0 +1,13 @@ +package gg.agenda; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class GgAgendaApiApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java new file mode 100644 index 000000000..5dd062ad5 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -0,0 +1,107 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import gg.data.BaseTimeEntity; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "agenda", uniqueConstraints = {@UniqueConstraint(name = "uk_agenda_key", columnNames = "key")}) +public class Agenda extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "key", nullable = false, columnDefinition = "BINARY(16)") + private byte[] key; + + @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(50)") + private String title; + + @Column(name = "content", nullable = false, columnDefinition = "VARCHAR(500)") + private String content; + + @Column(name = "deadline", nullable = false, columnDefinition = "DATETIME") + private String deadline; + + @Column(name = "start_time", nullable = false, columnDefinition = "DATETIME") + private String startTime; + + @Column(name = "end_time", nullable = false, columnDefinition = "DATETIME") + private String endTime; + + @Column(name = "min_team", nullable = false, columnDefinition = "INT") + private int minTeam; + + @Column(name = "max_team", nullable = false, columnDefinition = "INT") + private int maxTeam; + + @Column(name = "current_team", nullable = false, columnDefinition = "INT") + private int currentTeam; + + @Column(name = "min_people", nullable = false, columnDefinition = "INT") + private int minPeople; + + @Column(name = "max_people", nullable = false, columnDefinition = "INT") + private int maxPeople; + + @Column(name = "poster_uri", columnDefinition = "VARCHAR(255)") + private String posterUri; + + @Column(name = "host_intra_id", nullable = false, columnDefinition = "VARCHAR(30)") + private String hostIntraId; + + @Column(name = "location", nullable = false, columnDefinition = "VARCHAR(30)") + @Enumerated(EnumType.STRING) + private Location location; + + @Column(name = "status", nullable = false, columnDefinition = "VARCHAR(10)") + @Enumerated(EnumType.STRING) + private AgendaStatus status; + + @Column(name = "is_official", nullable = false, columnDefinition = "BIT(1)") + private boolean isOfficial; + + @Column(name = "is_ranking", nullable = false, columnDefinition = "BIT(1)") + private boolean isRanking; + + @Builder + public Agenda(Long id, byte[] key, String title, String content, String deadline, String startTime, String endTime, + int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, String posterUri, String hostIntraId, + Location location, AgendaStatus status, boolean isOfficial, boolean isRanking) { + this.id = id; + this.key = key; + this.title = title; + this.content = content; + this.deadline = deadline; + this.startTime = startTime; + this.endTime = endTime; + this.minTeam = minTeam; + this.maxTeam = maxTeam; + this.currentTeam = currentTeam; + this.minPeople = minPeople; + this.maxPeople = maxPeople; + this.posterUri = posterUri; + this.hostIntraId = hostIntraId; + this.location = location; + this.status = status; + this.isOfficial = isOfficial; + this.isRanking = isRanking; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java new file mode 100644 index 000000000..e1bb65d72 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java @@ -0,0 +1,50 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import gg.data.BaseTimeEntity; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "agenda_announcement") +public class AgendaAnnouncement extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(50)") + private String title; + + @Column(name = "content", nullable = false, columnDefinition = "VARCHAR(1000)") + private String content; + + @Column(name = "is_show", nullable = false, columnDefinition = "BIT(1)") + private boolean isShow; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "agenda_id") + private Agenda agenda; + + @Builder + public AgendaAnnouncement(Long id, String title, String content, boolean isShow, Agenda agenda) { + this.id = id; + this.title = title; + this.content = content; + this.isShow = isShow; + this.agenda = agenda; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java new file mode 100644 index 000000000..30d61b8e0 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -0,0 +1,55 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import gg.data.BaseTimeEntity; +import gg.data.agenda.type.Coalition; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "agenda_profile") +public class AgendaProfile extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "content", length = 1000, nullable = false) + private String content; + + @Column(name = "github_url", length = 255, nullable = false) + private String githubUrl; + + @Column(name = "coalition", length = 30, nullable = false) + @Enumerated(EnumType.STRING) + private Coalition coalition; + + @Column(name = "location", length = 30, nullable = false) + @Enumerated(EnumType.STRING) + private Location location; + + @Column(name = "user_id", nullable = false, columnDefinition = "BIGINT") + private Long userId; + + @Builder + public AgendaProfile(Long id, String content, String githubUrl, String coalition, String location) { + this.id = id; + this.content = content; + this.githubUrl = githubUrl; + this.coalition = Coalition.valueOf(coalition); + this.location = Location.valueOf(location); + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java new file mode 100644 index 000000000..28099bce8 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -0,0 +1,82 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import gg.data.BaseTimeEntity; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class AgendaTeam extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @ManyToOne + @JoinColumn(name = "agenda_id", nullable = false) + private Agenda agenda; + + @Column(name = "`key`", nullable = false, unique = true, columnDefinition = "BINARY(16)") + private byte[] key; + + @Column(name = "name", nullable = false, length = 30) + private String name; + + @Column(name = "content", nullable = false, length = 500) + private String content; + + @Column(name = "leader_intra_id", nullable = false, length = 30) + private String leaderIntraId; + + @Column(name = "status", nullable = false, length = 10) + @Enumerated(EnumType.STRING) + private AgendaTeamStatus status; + + @Column(name = "location", nullable = false, length = 10) + @Enumerated(EnumType.STRING) + private Location location; + + @Column(name = "mate_count", nullable = false) + private int mateCount; + + @Column(name = "award", nullable = false, length = 30) + private String award; + + @Column(name = "award_priority", nullable = false) + private int awardPriority; + + @Column(name = "is_private", nullable = false, columnDefinition = "BIT(1)") + private boolean isPrivate; + + @Builder + public AgendaTeam(Long id, Agenda agenda, byte[] key, String name, String content, String leaderIntraId, + String status, String location, int mateCount, String award, int awardPriority, boolean isPrivate) { + this.id = id; + this.agenda = agenda; + this.key = key; + this.name = name; + this.content = content; + this.leaderIntraId = leaderIntraId; + this.status = AgendaTeamStatus.valueOf(status); + this.location = Location.valueOf(location); + this.mateCount = mateCount; + this.award = award; + this.awardPriority = awardPriority; + this.isPrivate = isPrivate; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java new file mode 100644 index 000000000..939e22cd1 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -0,0 +1,44 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import gg.data.BaseTimeEntity; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class AgendaTeamProfile extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @ManyToOne + @JoinColumn(name = "profile_id", nullable = false) + private AgendaProfile profile; + + @ManyToOne + @JoinColumn(name = "agenda_team_id", nullable = false) + private AgendaTeam agendaTeam; + + @Column(name = "is_exist", nullable = false, columnDefinition = "BIT(1)") + private boolean isExist; + + @Builder + public AgendaTeamProfile(Long id, AgendaProfile profile, AgendaTeam agendaTeam, boolean isExist) { + this.id = id; + this.profile = profile; + this.agendaTeam = agendaTeam; + this.isExist = isExist; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java new file mode 100644 index 000000000..e6be378a9 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -0,0 +1,47 @@ +package gg.data.agenda; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import gg.data.BaseTimeEntity; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "ticket") +public class Ticket extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + private AgendaProfile agendaProfile; + + @Column(name = "is_used", nullable = false, columnDefinition = "BIT(1)") + private Boolean isUsed; + + @Column(name = "is_approve", nullable = false, columnDefinition = "BIT(1)") + private Boolean isApprove; + + @Builder + public Ticket(Long id, AgendaProfile agendaProfile, Boolean isUsed, Boolean isApprove) { + this.id = id; + this.agendaProfile = agendaProfile; + this.isUsed = isUsed; + this.isApprove = isApprove; + } +} + diff --git a/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java b/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java new file mode 100644 index 000000000..f5b8de94e --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java @@ -0,0 +1,14 @@ +package gg.data.agenda.type; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AgendaStatus { + CANCEL("CANCEL"), + ON_GOING("ON_GOING"), + CONFIRM("CONFIRM"); + + private final String status; +} diff --git a/gg-data/src/main/java/gg/data/agenda/type/AgendaTeamStatus.java b/gg-data/src/main/java/gg/data/agenda/type/AgendaTeamStatus.java new file mode 100644 index 000000000..acc893cb0 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/type/AgendaTeamStatus.java @@ -0,0 +1,14 @@ +package gg.data.agenda.type; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AgendaTeamStatus { + OPEN("OPEN"), + CANCEL("CANCEL"), + CONFIRM("CONFIRM"); + + private String status; +} diff --git a/gg-data/src/main/java/gg/data/agenda/type/Coalition.java b/gg-data/src/main/java/gg/data/agenda/type/Coalition.java new file mode 100644 index 000000000..4e5dc7b15 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/type/Coalition.java @@ -0,0 +1,19 @@ +package gg.data.agenda.type; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Coalition { + GUN("GUN"), + GON("GON"), + GAM("GAM"), + LEE("LEE"), + SPRING("SPRING"), + SUMMER("SUMMER"), + AUTUMN("AUTUMN"), + WINTER("WINTER"); + + private String coalition; +} diff --git a/gg-data/src/main/java/gg/data/agenda/type/Location.java b/gg-data/src/main/java/gg/data/agenda/type/Location.java new file mode 100644 index 000000000..d36b3f65b --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/type/Location.java @@ -0,0 +1,14 @@ +package gg.data.agenda.type; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Location { + SEOUL("SEOUL"), + GYEONGSAN("GYEONGSAN"), + MIX("MIX"); + + private String location; +} diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql new file mode 100644 index 000000000..6f0825bbe --- /dev/null +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -0,0 +1,104 @@ +CREATE TABLE `agenda` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `key` BINARY(16) NOT NULL, + `title` VARCHAR(50) NOT NULL, + `content` VARCHAR(500) NOT NULL, + `deadline` DATETIME NOT NULL, + `start_time` DATETIME NOT NULL, + `end_time` DATETIME NOT NULL, + `min_team` INT NOT NULL, + `max_team` INT NOT NULL, + `current_team` INT NOT NULL, + `min_people` INT NOT NULL, + `max_people` INT NOT NULL, + `poster_uri` VARCHAR(255) NULL, + `host_intra_id` VARCHAR(30) NOT NULL, + `location` VARCHAR(30) NOT NULL, + `status` VARCHAR(10) NOT NULL, + `is_official` BIT(1) NOT NULL, + `is_ranking` BIT(1) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_agenda_key` (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `agenda_team` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `agenda_id` BIGINT NOT NULL, + `key` BINARY(16) NOT NULL, + `name` VARCHAR(30) NOT NULL, + `content` VARCHAR(500) NOT NULL, + `leader_intra_id` VARCHAR(30) NOT NULL, + `status` VARCHAR(10) NOT NULL, + `location` VARCHAR(10) NOT NULL, + `mate_count` INT NOT NULL, + `award` VARCHAR(30) NOT NULL, + `award_priority` INT NOT NULL, + `is_private` BIT(1) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_agenda_team_agenda_agenda_id` (`agenda_id`), + CONSTRAINT `fk_agenda_team_agenda_agenda_id` FOREIGN KEY (`agenda_id`) REFERENCES `agenda` (`id`), + UNIQUE KEY `uk_agenda_team_key` (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `agenda_announcement` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `agenda_id` BIGINT NOT NULL, + `title` VARCHAR(50) NOT NULL, + `content` VARCHAR(1000) NOT NULL, + `is_show` BIT(1) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_agenda_announcement_agenda_agenda_id` (`agenda_id`), + CONSTRAINT `fk_agenda_announcement_agenda_agenda_id` FOREIGN KEY (`agenda_id`) REFERENCES `agenda` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `agenda_profile` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `content` VARCHAR(1000) NOT NULL, + `github_url` VARCHAR(255) NOT NULL, + `coalition` VARCHAR(30) NOT NULL, + `location` VARCHAR(30) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_agenda_profile_user_user_id` (`user_id`), + CONSTRAINT `fk_agenda_profile_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `agenda_team_profile` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `profile_id` BIGINT NOT NULL, + `agenda_team_id` BIGINT NOT NULL, + `is_exist` BIT(1) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_agenda_team_profile_profile_profile_id` (`profile_id`), + KEY `fk_agenda_team_profile_agenda_team_agenda_team_id` (`agenda_team_id`), + CONSTRAINT `fk_agenda_team_profile_profile_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `agenda_profile` (`id`), + CONSTRAINT `fk_agenda_team_profile_agenda_team_agenda_team_id` FOREIGN KEY (`agenda_team_id`) REFERENCES `agenda_team` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `ticket` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `profile_id` BIGINT NOT NULL, + `is_used` BIT(1) NOT NULL, + `is_approve` BIT(1) NOT NULL, + `created_at` DATETIME NOT NULL, + `modified_at` DATETIME NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_ticket_profile_profile_id` (`profile_id`), + CONSTRAINT `fk_ticket_profile_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `agenda_profile` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java new file mode 100644 index 000000000..e964a9cb3 --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.AgendaAnnouncement; + +public interface AgendaAnnouncementRepository extends JpaRepository { +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java new file mode 100644 index 000000000..5cd7159f3 --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.AgendaProfile; + +public interface AgendaProfileRepository extends JpaRepository { +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java new file mode 100644 index 000000000..50dd8201c --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.Agenda; + +public interface AgendaRepository extends JpaRepository { +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java new file mode 100644 index 000000000..ad9a35b1d --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.AgendaTeamProfile; + +public interface AgendaTeamProfileRepository extends JpaRepository { +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java new file mode 100644 index 000000000..6928195e9 --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.AgendaTeam; + +public interface AgendaTeamRepository extends JpaRepository { +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java new file mode 100644 index 000000000..a1a2f7d42 --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -0,0 +1,8 @@ +package gg.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.Ticket; + +public interface TicketRepository extends JpaRepository { +} diff --git a/settings.gradle b/settings.gradle index 9e311f424..6b5cabcf4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,4 +6,4 @@ include 'gg-pingpong-api' include 'gg-utils' include 'gg-auth' include 'gg-recruit-api' - +include 'gg-agenda-api' From a776e639e3080b58ce5f25be2b3543152c02325b Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:22:30 +0900 Subject: [PATCH 022/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20#869=20r?= =?UTF-8?q?efactoring=20=EB=A9=80=ED=8B=B0=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95=20(#870)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - testcov 추후 예정 --- build.gradle | 1 + .../gg/agenda/GgAgendaApiApplication.java | 4 - .../agenda/GgAgendaApiApplicationTests.java | 5 - .../src/test/resources/application.yml | 201 ++++++++++++++++++ .../src/main/java/gg/data/agenda/Agenda.java | 18 +- .../main/java/gg/data/agenda/AgendaTeam.java | 6 +- .../main/java/gg/PingpongApiApplication.java | 2 +- 7 files changed, 218 insertions(+), 19 deletions(-) create mode 100644 gg-agenda-api/src/test/resources/application.yml diff --git a/build.gradle b/build.gradle index 40f156f7d..e7bff3c38 100644 --- a/build.gradle +++ b/build.gradle @@ -252,6 +252,7 @@ project(':gg-pingpong-api') { implementation project(':gg-utils') implementation project(':gg-auth') implementation project(':gg-recruit-api') + implementation project(':gg-agenda-api') } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java b/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java index 2948f75a9..b37cb2ae5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java +++ b/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java @@ -2,8 +2,4 @@ public class GgAgendaApiApplication { - public static void main(String[] args) { - ; - } - } diff --git a/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java b/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java index e03e5c753..d99a88e41 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java +++ b/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java @@ -3,11 +3,6 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest class GgAgendaApiApplicationTests { - @Test - void contextLoads() { - } - } diff --git a/gg-agenda-api/src/test/resources/application.yml b/gg-agenda-api/src/test/resources/application.yml new file mode 100644 index 000000000..e01be0052 --- /dev/null +++ b/gg-agenda-api/src/test/resources/application.yml @@ -0,0 +1,201 @@ +spring: + application: + name: 42gg + + profiles: + active: testCode + + security: + oauth2.client: + authenticationScheme: header + registration: + 42: + redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}" + authorization-grant-type: authorization_code + scope: public + kakao: + redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}" + authorization-grant-type: authorization_code + scope: profile_nickname, profile_image, account_email + provider: + 42: + authorization-uri: "https://api.intra.42.fr/oauth/authorize" + token-uri: "https://api.intra.42.fr/oauth/token" + user-info-uri: "https://api.intra.42.fr/v2/me" + user-name-attribute: id + kakao: + authorization-uri: "https://kauth.kakao.com/oauth/authorize" + token-uri: "https://kauth.kakao.com/oauth/token" + user-info-uri: "https://kapi.kakao.com/v2/user/me" + user-name-attribute: id + + mvc: + hiddenmethod: + filter: + enabled: true + data: + web: + pageable: + default-page-size: 20 + one-indexed-parameters: false + + mail: + host: smtp.gmail.com + port: 587 + username: dummy + password: dummy + properties: + mail: + smtp: + starttls: + enable: true + required: true + auth: true + + # Message 설정 + messages: + basename: 'messages/validation' + encoding: UTF-8 + +springdoc: + swagger-ui: + path: /api-docs + default-consumes-media-type: application/json + default-produces-media-type: application/json + +app: + auth: + tokenSecret: authdummydummydummydummydummydummydummydummydummy + refreshTokenSecret: refreshdummydummydummydummydummydummydummydummydummy + +info: + image: + defaultUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/small_default.jpeg' + itemNotFoundUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/not_found.svg' + +constant: + allowedMinimalStartDays: 2 + tournamentSchedule: "0 0 0 * * *" + +# -- actuator + +management: + server: + port: 8081 + + info: + java: + enabled: true + os: + enabled: true + env: + enabled: true + + health: + show-details: always + + endpoints: + jmx: + exposure: + exclude: "*" + + web: + exposure: + include: "prometheus" + +server: + tomcat: + mbeanregistry: + enabled: true + +--- +spring.config.activate.on-profile: testCode + +# =========================== LOCAL =========================== +spring: + flyway: + enabled: true + baselineOnMigrate: true + locations: classpath:db/migration + user: root + password: 1234 + + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: validate + properties: + hibernate: + show_sql: true + format_sql: true + use_sql_comments: false + + security: + oauth2.client: + registration: + 42: + client-id: "dummy" + client-secret: "dummy" + kakao: + client-id: "dummy" + client-secret: "dummy" + client-authentication-method: POST + + # Redis 설정 + cache: + type: redis + +# cors 설정 +cors: + allowed-origins: 'http://localhost:8080,http://127.0.0.1:8081' + allowed-methods: GET,POST,PUT,DELETE,OPTIONS,PATCH + allowed-headers: '*' + allowed-Credentials: false + max-age: 3600 + +logging-level: + org.hibernate.SQL: debug + org.hibernate.type: trace + +slack: + xoxbToken: "dummy" + +info: + web: + frontUrl: 'http://localhost:8080' + domain: "localhost" + +cloud: + aws: + credentials: + accessKey: dummy + secretKey: dummy + s3: + bucket: 42gg-public-test-image + dir: images/ + region: + static: ap-northeast-2 + stack: + auto: false + +app: + auth: + refreshTokenExpiry: 604800000 + tokenExpiry: 604800000 + + + +--- +spring.config.activate.on-profile: test-mvc + +spring: + security: + oauth2.client: + registration: + 42: + client-id: "dummy" + client-secret: "dummy" + kakao: + client-id: "dummy" + client-secret: "dummy" + client-authentication-method: POST diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 5dd062ad5..d3ad3b725 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -1,5 +1,8 @@ package gg.data.agenda; +import java.time.LocalDateTime; +import java.util.UUID; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -29,7 +32,7 @@ public class Agenda extends BaseTimeEntity { private Long id; @Column(name = "key", nullable = false, columnDefinition = "BINARY(16)") - private byte[] key; + private UUID key; @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(50)") private String title; @@ -38,13 +41,13 @@ public class Agenda extends BaseTimeEntity { private String content; @Column(name = "deadline", nullable = false, columnDefinition = "DATETIME") - private String deadline; + private LocalDateTime deadline; @Column(name = "start_time", nullable = false, columnDefinition = "DATETIME") - private String startTime; + private LocalDateTime startTime; @Column(name = "end_time", nullable = false, columnDefinition = "DATETIME") - private String endTime; + private LocalDateTime endTime; @Column(name = "min_team", nullable = false, columnDefinition = "INT") private int minTeam; @@ -82,9 +85,10 @@ public class Agenda extends BaseTimeEntity { private boolean isRanking; @Builder - public Agenda(Long id, byte[] key, String title, String content, String deadline, String startTime, String endTime, - int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, String posterUri, String hostIntraId, - Location location, AgendaStatus status, boolean isOfficial, boolean isRanking) { + public Agenda(Long id, UUID key, String title, String content, LocalDateTime deadline, LocalDateTime startTime, + LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, + String posterUri, String hostIntraId, Location location, AgendaStatus status, boolean isOfficial, + boolean isRanking) { this.id = id; this.key = key; this.title = title; diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 28099bce8..318841f36 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -1,5 +1,7 @@ package gg.data.agenda; +import java.util.UUID; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -32,7 +34,7 @@ public class AgendaTeam extends BaseTimeEntity { private Agenda agenda; @Column(name = "`key`", nullable = false, unique = true, columnDefinition = "BINARY(16)") - private byte[] key; + private UUID key; @Column(name = "name", nullable = false, length = 30) private String name; @@ -64,7 +66,7 @@ public class AgendaTeam extends BaseTimeEntity { private boolean isPrivate; @Builder - public AgendaTeam(Long id, Agenda agenda, byte[] key, String name, String content, String leaderIntraId, + public AgendaTeam(Long id, Agenda agenda, UUID key, String name, String content, String leaderIntraId, String status, String location, int mateCount, String award, int awardPriority, boolean isPrivate) { this.id = id; this.agenda = agenda; diff --git a/gg-pingpong-api/src/main/java/gg/PingpongApiApplication.java b/gg-pingpong-api/src/main/java/gg/PingpongApiApplication.java index b61ffb94e..e92fed3b4 100644 --- a/gg-pingpong-api/src/main/java/gg/PingpongApiApplication.java +++ b/gg-pingpong-api/src/main/java/gg/PingpongApiApplication.java @@ -4,7 +4,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages = {"gg.admin.repo", "gg.data", "gg.repo", - "gg.pingpong.api", "gg.utils", "gg.party.api", "gg.auth", "gg.recruit.api"}) + "gg.pingpong.api", "gg.utils", "gg.party.api", "gg.auth", "gg.recruit.api", "gg.agenda.api"}) public class PingpongApiApplication { public static void main(String[] args) { From 51585bf9b8fbf8d404da688605339d4513236eb6 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:01:39 +0900 Subject: [PATCH 023/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#857=20Agenda?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=20(#871)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agenda/GgAgendaApiApplication.java | 5 - .../agenda/controller/AgendaController.java | 34 +++++ .../controller/dto/AgendaResponseDto.java | 100 ++++++++++++++ .../user/agenda/service/AgendaService.java | 34 +++++ .../agenda/GgAgendaApiApplicationTests.java | 8 -- .../java/gg/agenda/api/AgendaMockData.java | 63 +++++++++ .../user/controller/AgendaControllerTest.java | 123 ++++++++++++++++++ .../api/user/service/AgendaServiceTest.java | 70 ++++++++++ .../src/main/java/gg/data/agenda/Agenda.java | 22 ++-- .../java/gg/data/agenda/AgendaProfile.java | 9 -- .../main/java/gg/data/agenda/AgendaTeam.java | 21 +-- .../gg/data/agenda/AgendaTeamProfile.java | 8 -- .../src/main/java/gg/data/agenda/Ticket.java | 8 -- .../resources/db/migration/V3__agenda.sql | 8 +- .../agenda/AgendaAnnouncementRepository.java | 9 ++ .../java/gg/repo/agenda/AgendaRepository.java | 7 + .../java/gg/utils/exception/ErrorCode.java | 12 +- .../exception/custom/ForbiddenException.java | 4 + .../custom/InvalidParameterException.java | 4 + .../exception/custom/NotExistException.java | 4 + 20 files changed, 483 insertions(+), 70 deletions(-) delete mode 100644 gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java delete mode 100644 gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java b/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java deleted file mode 100644 index b37cb2ae5..000000000 --- a/gg-agenda-api/src/main/java/gg/agenda/GgAgendaApiApplication.java +++ /dev/null @@ -1,5 +0,0 @@ -package gg.agenda; - -public class GgAgendaApiApplication { - -} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java new file mode 100644 index 000000000..2b9418268 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -0,0 +1,34 @@ +package gg.agenda.api.user.agenda.controller; + +import java.util.UUID; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.agenda.api.user.agenda.service.AgendaService; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda") +public class AgendaController { + + private final AgendaService agendaService; + + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Agenda 상세 조회 성공"), + @ApiResponse(responseCode = "400", description = "Agenda 조회 요청이 잘못됨"), + @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음") + }) + public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { + AgendaResponseDto agendaDto = agendaService.findAgendaWithLatestAnnouncement(agendaKey); + return ResponseEntity.ok(agendaDto); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java new file mode 100644 index 000000000..a83c1faf3 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java @@ -0,0 +1,100 @@ +package gg.agenda.api.user.agenda.controller.dto; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaResponseDto { + + private String agendaTitle; + + private String agendaContents; + + private String agendaDeadLine; + + private String agendaStartTime; + + private String agendaEndTime; + + private int agendaMinTeam; + + private int agendaMaxTeam; + + private int agendaCurrentTeam; + + private int agendaMinPeople; + + private int agendaMaxPeople; + + private String agendaPoster; + + private String agendaHost; + + private String agendaLocation; + + private String agendaStatus; + + private String createdAt; + + private boolean isOfficial; + + private String announcementTitle; + + @Builder + public AgendaResponseDto(String agendaTitle, String agendaContents, String agendaDeadLine, String agendaStartTime, + String agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaCurrentTeam, int agendaMinPeople, + int agendaMaxPeople, String agendaPoster, String agendaHost, String agendaLocation, String agendaStatus, + String createdAt, String announcementTitle, boolean isOfficial) { + this.agendaTitle = agendaTitle; + this.agendaContents = agendaContents; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaMinTeam = agendaMinTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaCurrentTeam = agendaCurrentTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + this.agendaPoster = agendaPoster; + this.agendaHost = agendaHost; + this.agendaLocation = agendaLocation; + this.agendaStatus = agendaStatus; + this.createdAt = createdAt; + this.announcementTitle = announcementTitle; + this.isOfficial = isOfficial; + } + + @Mapper + public interface MapStruct { + + AgendaResponseDto.MapStruct INSTANCE = Mappers.getMapper(AgendaResponseDto.MapStruct.class); + + @Mapping(target = "agendaTitle", source = "agenda.title") + @Mapping(target = "agendaContents", source = "agenda.content") + @Mapping(target = "agendaDeadLine", source = "agenda.deadline") + @Mapping(target = "agendaStartTime", source = "agenda.startTime") + @Mapping(target = "agendaEndTime", source = "agenda.endTime") + @Mapping(target = "agendaMinTeam", source = "agenda.minTeam") + @Mapping(target = "agendaMaxTeam", source = "agenda.maxTeam") + @Mapping(target = "agendaCurrentTeam", source = "agenda.currentTeam") + @Mapping(target = "agendaMinPeople", source = "agenda.minPeople") + @Mapping(target = "agendaMaxPeople", source = "agenda.maxPeople") + @Mapping(target = "agendaPoster", source = "agenda.posterUri") + @Mapping(target = "agendaHost", source = "agenda.hostIntraId") + @Mapping(target = "agendaLocation", source = "agenda.location") + @Mapping(target = "agendaStatus", source = "agenda.status") + @Mapping(target = "createdAt", source = "agenda.createdAt") + @Mapping(target = "isOfficial", source = "agenda.official") + @Mapping(target = "announcementTitle", source = "announcement.title") + AgendaResponseDto toDto(Agenda agenda, AgendaAnnouncement announcement); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java new file mode 100644 index 000000000..ce3591345 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -0,0 +1,34 @@ +package gg.agenda.api.user.agenda.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.repo.agenda.AgendaRepository; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaService { + + private final AgendaRepository agendaRepository; + + private final AgendaAnnouncementRepository agendaAnnouncementRepository; + + @Transactional(readOnly = true) + public AgendaResponseDto findAgendaWithLatestAnnouncement(UUID agendaKey) { + Agenda agenda = agendaRepository.findAgendaByKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + AgendaAnnouncement announcement = agendaAnnouncementRepository + .findLatestByAgenda(agenda).orElse(null); + return AgendaResponseDto.MapStruct.INSTANCE.toDto(agenda, announcement); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java b/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java deleted file mode 100644 index d99a88e41..000000000 --- a/gg-agenda-api/src/test/java/gg/agenda/GgAgendaApiApplicationTests.java +++ /dev/null @@ -1,8 +0,0 @@ -package gg.agenda; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -class GgAgendaApiApplicationTests { - -} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java new file mode 100644 index 000000000..775be7dc1 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -0,0 +1,63 @@ +package gg.agenda.api; + +import java.time.LocalDateTime; +import java.util.UUID; + +import javax.persistence.EntityManager; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.repo.agenda.AgendaRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaMockData { + + private final EntityManager em; + + private final AgendaRepository agendaRepository; + + private final AgendaAnnouncementRepository agendaAnnouncementRepository; + + public Agenda createAgenda() { + Agenda agenda = Agenda.builder() + .agendaKey(UUID.randomUUID()) + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(AgendaStatus.ON_GOING) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { + AgendaAnnouncement announcement = AgendaAnnouncement.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .isShow(true) + .agenda(agenda) + .build(); + em.persist(announcement); + em.flush(); + em.clear(); + return announcement; + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java new file mode 100644 index 000000000..face00e94 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java @@ -0,0 +1,123 @@ +package gg.agenda.api.user.controller; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.user.User; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaMockData agendaMockData; + + @Autowired + EntityManager em; + + private String accessToken; + + @BeforeEach + void setUp() { + User user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("Agenda 상세 조회") + class GetAgenda { + + @Test + @DisplayName("agenda_id에 해당하는 Agenda를 상세 조회합니다.") + void test() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(); + AgendaAnnouncement announcement = agendaMockData.createAgendaAnnouncement(agenda); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(announcement.getTitle()); + } + + @Test + @DisplayName("announce가 없는 경우 announcementTitle를 null로 반환합니다.") + void test2() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(null); + } + + @Test + @DisplayName("announce가 여러 개인 경우 가장 최근 작성된 announce를 반환합니다.") + void test3() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(); + AgendaAnnouncement announcement1 = agendaMockData.createAgendaAnnouncement(agenda); + AgendaAnnouncement announcement2 = agendaMockData.createAgendaAnnouncement(agenda); + AgendaAnnouncement announcement3 = agendaMockData.createAgendaAnnouncement(agenda); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement1.getTitle()); + assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement2.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(announcement3.getTitle()); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java new file mode 100644 index 000000000..85afbbe4a --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java @@ -0,0 +1,70 @@ +package gg.agenda.api.user.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import gg.agenda.api.user.agenda.service.AgendaService; +import gg.data.agenda.Agenda; +import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.repo.agenda.AgendaRepository; +import gg.utils.annotation.UnitTest; +import gg.utils.exception.custom.NotExistException; + +@UnitTest +class AgendaServiceTest { + + @Mock + AgendaRepository agendaRepository; + + @Mock + AgendaAnnouncementRepository agendaAnnouncementRepository; + + @InjectMocks + AgendaService agendaService; + + @Nested + @DisplayName("Agenda 단건 조회") + class GetAgenda { + + @Test + @DisplayName("Agenda 단건 조회 성공") + void getAgendaSuccess() { + // given + UUID agendaKey = UUID.randomUUID(); + Agenda agenda = mock(Agenda.class); + when(agendaRepository.findAgendaByKey(agendaKey)).thenReturn(Optional.of(agenda)); + when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.empty()); + + // when + agendaService.findAgendaWithLatestAnnouncement(agendaKey); + + // then + verify(agendaRepository, times(1)).findAgendaByKey(agendaKey); + verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); + } + + @Test + @DisplayName("Agenda 단건 조회 실패") + void getAgendaFailedWithnoAgenda() { + // given + UUID agendaKey = UUID.randomUUID(); + Agenda agenda = mock(Agenda.class); + when(agendaRepository.findAgendaByKey(agendaKey)).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaService.findAgendaWithLatestAnnouncement(agendaKey)); + verify(agendaRepository, times(1)).findAgendaByKey(agendaKey); + verify(agendaAnnouncementRepository, never()).findLatestByAgenda(agenda); + } + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index d3ad3b725..2384a8a99 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -1,5 +1,7 @@ package gg.data.agenda; +import static gg.utils.exception.ErrorCode.*; + import java.time.LocalDateTime; import java.util.UUID; @@ -16,6 +18,8 @@ import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; +import gg.utils.exception.custom.ForbiddenException; +import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -24,15 +28,17 @@ @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "agenda", uniqueConstraints = {@UniqueConstraint(name = "uk_agenda_key", columnNames = "key")}) +@Table(name = "agenda", uniqueConstraints = { + @UniqueConstraint(name = "uk_agenda_agenda_key", columnNames = "agenda_key") +}) public class Agenda extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "key", nullable = false, columnDefinition = "BINARY(16)") - private UUID key; + @Column(name = "agenda_key", nullable = false, columnDefinition = "BINARY(16)") + private UUID agendaKey; @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(50)") private String title; @@ -85,12 +91,12 @@ public class Agenda extends BaseTimeEntity { private boolean isRanking; @Builder - public Agenda(Long id, UUID key, String title, String content, LocalDateTime deadline, LocalDateTime startTime, - LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, - String posterUri, String hostIntraId, Location location, AgendaStatus status, boolean isOfficial, - boolean isRanking) { + public Agenda(Long id, UUID agendaKey, String title, String content, LocalDateTime deadline, + LocalDateTime startTime, LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, + int maxPeople, String posterUri, String hostIntraId, Location location, AgendaStatus status, + boolean isOfficial, boolean isRanking) { this.id = id; - this.key = key; + this.agendaKey = agendaKey; this.title = title; this.content = content; this.deadline = deadline; diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index 30d61b8e0..52e221db0 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -43,13 +43,4 @@ public class AgendaProfile extends BaseTimeEntity { @Column(name = "user_id", nullable = false, columnDefinition = "BIGINT") private Long userId; - - @Builder - public AgendaProfile(Long id, String content, String githubUrl, String coalition, String location) { - this.id = id; - this.content = content; - this.githubUrl = githubUrl; - this.coalition = Coalition.valueOf(coalition); - this.location = Location.valueOf(location); - } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 318841f36..e0235fd80 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -33,8 +33,8 @@ public class AgendaTeam extends BaseTimeEntity { @JoinColumn(name = "agenda_id", nullable = false) private Agenda agenda; - @Column(name = "`key`", nullable = false, unique = true, columnDefinition = "BINARY(16)") - private UUID key; + @Column(name = "`team_key`", nullable = false, unique = true, columnDefinition = "BINARY(16)") + private UUID teamKey; @Column(name = "name", nullable = false, length = 30) private String name; @@ -64,21 +64,4 @@ public class AgendaTeam extends BaseTimeEntity { @Column(name = "is_private", nullable = false, columnDefinition = "BIT(1)") private boolean isPrivate; - - @Builder - public AgendaTeam(Long id, Agenda agenda, UUID key, String name, String content, String leaderIntraId, - String status, String location, int mateCount, String award, int awardPriority, boolean isPrivate) { - this.id = id; - this.agenda = agenda; - this.key = key; - this.name = name; - this.content = content; - this.leaderIntraId = leaderIntraId; - this.status = AgendaTeamStatus.valueOf(status); - this.location = Location.valueOf(location); - this.mateCount = mateCount; - this.award = award; - this.awardPriority = awardPriority; - this.isPrivate = isPrivate; - } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index 939e22cd1..f9bd0dc25 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -33,12 +33,4 @@ public class AgendaTeamProfile extends BaseTimeEntity { @Column(name = "is_exist", nullable = false, columnDefinition = "BIT(1)") private boolean isExist; - - @Builder - public AgendaTeamProfile(Long id, AgendaProfile profile, AgendaTeam agendaTeam, boolean isExist) { - this.id = id; - this.profile = profile; - this.agendaTeam = agendaTeam; - this.isExist = isExist; - } } diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index e6be378a9..4d711649d 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -35,13 +35,5 @@ public class Ticket extends BaseTimeEntity { @Column(name = "is_approve", nullable = false, columnDefinition = "BIT(1)") private Boolean isApprove; - - @Builder - public Ticket(Long id, AgendaProfile agendaProfile, Boolean isUsed, Boolean isApprove) { - this.id = id; - this.agendaProfile = agendaProfile; - this.isUsed = isUsed; - this.isApprove = isApprove; - } } diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 6f0825bbe..7855e9fdb 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -1,7 +1,7 @@ CREATE TABLE `agenda` ( `id` BIGINT NOT NULL AUTO_INCREMENT, - `key` BINARY(16) NOT NULL, + `agenda_key` BINARY(16) NOT NULL, `title` VARCHAR(50) NOT NULL, `content` VARCHAR(500) NOT NULL, `deadline` DATETIME NOT NULL, @@ -21,14 +21,14 @@ CREATE TABLE `agenda` `created_at` DATETIME NOT NULL, `modified_at` DATETIME NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `uk_agenda_key` (`key`) + UNIQUE KEY `uk_agenda_agenda_key` (`agenda_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `agenda_team` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `agenda_id` BIGINT NOT NULL, - `key` BINARY(16) NOT NULL, + `team_key` BINARY(16) NOT NULL, `name` VARCHAR(30) NOT NULL, `content` VARCHAR(500) NOT NULL, `leader_intra_id` VARCHAR(30) NOT NULL, @@ -43,7 +43,7 @@ CREATE TABLE `agenda_team` PRIMARY KEY (`id`), KEY `fk_agenda_team_agenda_agenda_id` (`agenda_id`), CONSTRAINT `fk_agenda_team_agenda_agenda_id` FOREIGN KEY (`agenda_id`) REFERENCES `agenda` (`id`), - UNIQUE KEY `uk_agenda_team_key` (`key`) + UNIQUE KEY `uk_agenda_team_team_key` (`team_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `agenda_announcement` diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java index e964a9cb3..834349f7d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java @@ -1,8 +1,17 @@ package gg.repo.agenda; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; public interface AgendaAnnouncementRepository extends JpaRepository { + + Optional findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(Agenda agenda); + + default Optional findLatestByAgenda(Agenda agenda) { + return findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(agenda); + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index 50dd8201c..a7f57ca3e 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -1,8 +1,15 @@ package gg.repo.agenda; +import java.util.Optional; +import java.util.UUID; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import gg.data.agenda.Agenda; public interface AgendaRepository extends JpaRepository { + + @Query("SELECT a FROM Agenda a WHERE a.agendaKey = :agendaKey") + Optional findAgendaByKey(UUID agendaKey); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 90c578f85..e734b2fbb 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -189,7 +189,17 @@ public enum ErrorCode { ON_PENALTY(403, "PT501", "패널티 상태입니다."), // recruitment - INVALID_CHECKLIST(400, "RE001", "잘못된 요청 데이터입니다."); + INVALID_CHECKLIST(400, "RE001", "잘못된 요청 데이터입니다."), + + // agenda + AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), + AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), + AGENDA_NO_CAPACITY(409, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), + HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), + LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), + TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), + TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), + TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."); private final int status; private final String errCode; diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/ForbiddenException.java b/gg-utils/src/main/java/gg/utils/exception/custom/ForbiddenException.java index f31592bd4..c01922872 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/ForbiddenException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/ForbiddenException.java @@ -10,4 +10,8 @@ public ForbiddenException(String message, ErrorCode errorCode) { public ForbiddenException(String message) { super(message, ErrorCode.FORBIDDEN); } + + public ForbiddenException(ErrorCode errorCode) { + super(errorCode); + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/InvalidParameterException.java b/gg-utils/src/main/java/gg/utils/exception/custom/InvalidParameterException.java index d2f182ec5..69a6bc6ee 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/InvalidParameterException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/InvalidParameterException.java @@ -6,4 +6,8 @@ public class InvalidParameterException extends CustomRuntimeException { public InvalidParameterException(String message, ErrorCode errorCode) { super(message, errorCode); } + + public InvalidParameterException(ErrorCode errorCode) { + super(errorCode); + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/NotExistException.java b/gg-utils/src/main/java/gg/utils/exception/custom/NotExistException.java index 8255e9873..60516b34d 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/NotExistException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/NotExistException.java @@ -12,4 +12,8 @@ public NotExistException(String message, ErrorCode errorCode) { public NotExistException(String message) { super(message, ErrorCode.NOT_FOUND); } + + public NotExistException(ErrorCode errorCode) { + super(errorCode); + } } From e44cbfa991d4df678de484316b4c0ad019b66ce5 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:09:56 +0900 Subject: [PATCH 024/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#858=20Agenda?= =?UTF-8?q?=20=ED=98=84=ED=99=A9=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20(#873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 11 ++- .../controller/dto/AgendaResponseDto.java | 29 ++++--- .../dto/AgendaSimpleResponseDto.java | 76 ++++++++++++++++++ .../user/agenda/service/AgendaService.java | 14 ++++ .../java/gg/agenda/api/AgendaMockData.java | 78 ++++++++++++++++++- .../user/controller/AgendaControllerTest.java | 61 ++++++++++++++- .../dto/AgendaSimpleResponseDtoTest.java | 43 ++++++++++ .../api/user/service/AgendaServiceTest.java | 60 +++++++++++++- .../src/main/java/gg/data/agenda/Agenda.java | 8 +- .../java/gg/repo/agenda/AgendaRepository.java | 5 ++ 10 files changed, 360 insertions(+), 25 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 2b9418268..37c77f131 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agenda.controller; +import java.util.List; import java.util.UUID; import org.springframework.http.ResponseEntity; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RestController; import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; import gg.agenda.api.user.agenda.service.AgendaService; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -21,14 +23,21 @@ public class AgendaController { private final AgendaService agendaService; - @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Agenda 상세 조회 성공"), @ApiResponse(responseCode = "400", description = "Agenda 조회 요청이 잘못됨"), @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음") }) + @GetMapping public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { AgendaResponseDto agendaDto = agendaService.findAgendaWithLatestAnnouncement(agendaKey); return ResponseEntity.ok(agendaDto); } + + @ApiResponse(responseCode = "200", description = "현재 진행중인 Agenda 목록 조회 성공") + @GetMapping("/list") + public ResponseEntity> agendaListCurrent() { + List agendaList = agendaService.findCurrentAgendaList(); + return ResponseEntity.ok(agendaList); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java index a83c1faf3..cf4b63dae 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java @@ -1,11 +1,15 @@ package gg.agenda.api.user.agenda.controller.dto; +import java.time.LocalDateTime; + import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -19,11 +23,11 @@ public class AgendaResponseDto { private String agendaContents; - private String agendaDeadLine; + private LocalDateTime agendaDeadLine; - private String agendaStartTime; + private LocalDateTime agendaStartTime; - private String agendaEndTime; + private LocalDateTime agendaEndTime; private int agendaMinTeam; @@ -39,21 +43,22 @@ public class AgendaResponseDto { private String agendaHost; - private String agendaLocation; + private Location agendaLocation; - private String agendaStatus; + private AgendaStatus agendaStatus; - private String createdAt; + private LocalDateTime createdAt; - private boolean isOfficial; + private Boolean isOfficial; private String announcementTitle; @Builder - public AgendaResponseDto(String agendaTitle, String agendaContents, String agendaDeadLine, String agendaStartTime, - String agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaCurrentTeam, int agendaMinPeople, - int agendaMaxPeople, String agendaPoster, String agendaHost, String agendaLocation, String agendaStatus, - String createdAt, String announcementTitle, boolean isOfficial) { + public AgendaResponseDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, + int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, String agendaHost, + Location agendaLocation, AgendaStatus agendaStatus, LocalDateTime createdAt, String announcementTitle, + boolean isOfficial) { this.agendaTitle = agendaTitle; this.agendaContents = agendaContents; this.agendaDeadLine = agendaDeadLine; @@ -93,7 +98,7 @@ public interface MapStruct { @Mapping(target = "agendaLocation", source = "agenda.location") @Mapping(target = "agendaStatus", source = "agenda.status") @Mapping(target = "createdAt", source = "agenda.createdAt") - @Mapping(target = "isOfficial", source = "agenda.official") + @Mapping(target = "isOfficial", source = "agenda.isOfficial") @Mapping(target = "announcementTitle", source = "announcement.title") AgendaResponseDto toDto(Agenda agenda, AgendaAnnouncement announcement); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java new file mode 100644 index 000000000..2ee1af203 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java @@ -0,0 +1,76 @@ +package gg.agenda.api.user.agenda.controller.dto; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaSimpleResponseDto { + private String agendaTitle; + + private LocalDateTime agendaDeadLine; + + private LocalDateTime agendaStartTime; + + private LocalDateTime agendaEndTime; + + private int agendaCurrentTeam; + + private int agendaMaxTeam; + + private int agendaMinPeople; + + private int agendaMaxPeople; + + private Location agendaLocation; + + private UUID agendaKey; + + private Boolean isOfficial; + + @Builder + public AgendaSimpleResponseDto(String agendaTitle, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, + LocalDateTime agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, + int agendaMaxPeople, Location agendaLocation, UUID agendaKey, boolean isOfficial) { + this.agendaTitle = agendaTitle; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaCurrentTeam = agendaCurrentTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + this.agendaLocation = agendaLocation; + this.agendaKey = agendaKey; + this.isOfficial = isOfficial; + } + + @Mapper + public interface MapStruct { + AgendaSimpleResponseDto.MapStruct INSTANCE = Mappers.getMapper(AgendaSimpleResponseDto.MapStruct.class); + + @Mapping(target = "agendaTitle", source = "title") + @Mapping(target = "agendaDeadLine", source = "deadline") + @Mapping(target = "agendaStartTime", source = "startTime") + @Mapping(target = "agendaEndTime", source = "endTime") + @Mapping(target = "agendaCurrentTeam", source = "currentTeam") + @Mapping(target = "agendaMaxTeam", source = "maxTeam") + @Mapping(target = "agendaMinPeople", source = "minTeam") + @Mapping(target = "agendaMaxPeople", source = "maxTeam") + @Mapping(target = "agendaLocation", source = "location") + @Mapping(target = "agendaKey", source = "agendaKey") + @Mapping(target = "isOfficial", source = "isOfficial") + AgendaSimpleResponseDto toDto(Agenda agenda); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index ce3591345..8b30c7a6b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -2,14 +2,19 @@ import static gg.utils.exception.ErrorCode.*; +import java.util.Comparator; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; import gg.utils.exception.custom.NotExistException; @@ -31,4 +36,13 @@ public AgendaResponseDto findAgendaWithLatestAnnouncement(UUID agendaKey) { .findLatestByAgenda(agenda).orElse(null); return AgendaResponseDto.MapStruct.INSTANCE.toDto(agenda, announcement); } + + @Transactional(readOnly = true) + public List findCurrentAgendaList() { + return agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING).stream() + .sorted(Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) + .thenComparing(Agenda::getDeadline, Comparator.reverseOrder())) + .map(AgendaSimpleResponseDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 775be7dc1..7386b924f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,7 +1,10 @@ package gg.agenda.api; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.persistence.EntityManager; @@ -25,7 +28,7 @@ public class AgendaMockData { private final AgendaAnnouncementRepository agendaAnnouncementRepository; - public Agenda createAgenda() { + public Agenda createOfficialAgenda() { Agenda agenda = Agenda.builder() .agendaKey(UUID.randomUUID()) .title("title " + UUID.randomUUID()) @@ -48,6 +51,79 @@ public Agenda createAgenda() { return agendaRepository.save(agenda); } + public Agenda createNonOfficialAgenda() { + Agenda agenda = Agenda.builder() + .agendaKey(UUID.randomUUID()) + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(AgendaStatus.ON_GOING) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public List createOfficialAgendaList(int size, AgendaStatus status) { + List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() + .agendaKey(UUID.randomUUID()) + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(i + 3)) + .startTime(LocalDateTime.now().plusDays(i + 5)) + .endTime(LocalDateTime.now().plusDays(i + 6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(status) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) // true + .isRanking(true) + .build() + ) + .collect(Collectors.toList()); + return agendaRepository.saveAll(agendas); + } + + public List createNonOfficialAgendaList(int size, AgendaStatus status) { + List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() + .agendaKey(UUID.randomUUID()) + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(i + 3)) + .startTime(LocalDateTime.now().plusDays(i + 5)) + .endTime(LocalDateTime.now().plusDays(i + 6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(status) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(false) // false + .isRanking(true) + .build() + ) + .collect(Collectors.toList()); + return agendaRepository.saveAll(agendas); + } + public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { AgendaAnnouncement announcement = AgendaAnnouncement.builder() .title("title " + UUID.randomUUID()) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java index face00e94..fb9cbbb92 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java @@ -4,6 +4,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.List; + import javax.persistence.EntityManager; import javax.transaction.Transactional; @@ -19,12 +21,16 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.type.AgendaStatus; import gg.data.user.User; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import lombok.extern.slf4j.Slf4j; +@Slf4j @IntegrationTest @Transactional @AutoConfigureMockMvc @@ -61,7 +67,7 @@ class GetAgenda { @DisplayName("agenda_id에 해당하는 Agenda를 상세 조회합니다.") void test() throws Exception { // given - Agenda agenda = agendaMockData.createAgenda(); + Agenda agenda = agendaMockData.createOfficialAgenda(); AgendaAnnouncement announcement = agendaMockData.createAgendaAnnouncement(agenda); // when @@ -81,7 +87,7 @@ void test() throws Exception { @DisplayName("announce가 없는 경우 announcementTitle를 null로 반환합니다.") void test2() throws Exception { // given - Agenda agenda = agendaMockData.createAgenda(); + Agenda agenda = agendaMockData.createOfficialAgenda(); // when String response = mockMvc.perform(get("/agenda") @@ -100,7 +106,7 @@ void test2() throws Exception { @DisplayName("announce가 여러 개인 경우 가장 최근 작성된 announce를 반환합니다.") void test3() throws Exception { // given - Agenda agenda = agendaMockData.createAgenda(); + Agenda agenda = agendaMockData.createOfficialAgenda(); AgendaAnnouncement announcement1 = agendaMockData.createAgendaAnnouncement(agenda); AgendaAnnouncement announcement2 = agendaMockData.createAgendaAnnouncement(agenda); AgendaAnnouncement announcement3 = agendaMockData.createAgendaAnnouncement(agenda); @@ -120,4 +126,53 @@ void test3() throws Exception { assertThat(result.getAnnouncementTitle()).isEqualTo(announcement3.getTitle()); } } + + @Nested + @DisplayName("Agenda 현황 전체 조회") + class GetAgendaListCurrent { + + @Test + @DisplayName("Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") + void getAgendaListSuccess() throws Exception { + // given + List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.ON_GOING); + List nonOfficialAgendaList = agendaMockData + .createNonOfficialAgendaList(6, AgendaStatus.ON_GOING); + + // when + String response = mockMvc.perform(get("/agenda/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(officialAgendaList.size() + nonOfficialAgendaList.size()); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); + if (i == 0 || i == officialAgendaList.size()) { + continue; + } + assertThat(result[i].getAgendaDeadLine()).isBefore(result[i - 1].getAgendaDeadLine()); + } + } + + @Test + @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListSuccessWithNoAgenda() throws Exception { + // given + agendaMockData.createOfficialAgendaList(3, AgendaStatus.CONFIRM); + agendaMockData.createNonOfficialAgendaList(6, AgendaStatus.CANCEL); + + // when + String response = mockMvc.perform(get("/agenda/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(0); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java new file mode 100644 index 000000000..f337476be --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java @@ -0,0 +1,43 @@ +package gg.agenda.api.user.controller.dto; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import java.util.UUID; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.data.agenda.Agenda; +import gg.utils.annotation.UnitTest; + +@UnitTest +class AgendaSimpleResponseDtoTest { + + @Nested + @DisplayName("AgendaSimpleResponseDto 생성") + class CreateAgendaSimpleResponseDto { + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("AgendaSimpleResponseDto 생성 성공") + void createAgendaSimpleResponseDtoSuccess(boolean value) { + // when + Agenda agenda = Agenda.builder() + .agendaKey(UUID.randomUUID()) + .isOfficial(value) + .build(); + + // given + AgendaSimpleResponseDto dto = AgendaSimpleResponseDto.MapStruct.INSTANCE.toDto(agenda); + + // then + assertThat(dto).isNotNull(); + assertThat(dto.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); + assertThat(dto.getIsOfficial()).isEqualTo(value); + } + } + +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java index 85afbbe4a..b611ce76e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java @@ -1,10 +1,15 @@ package gg.agenda.api.user.service; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.IntStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -12,13 +17,17 @@ import org.mockito.InjectMocks; import org.mockito.Mock; +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; import gg.agenda.api.user.agenda.service.AgendaService; import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.NotExistException; +import lombok.extern.slf4j.Slf4j; +@Slf4j @UnitTest class AgendaServiceTest { @@ -61,10 +70,57 @@ void getAgendaFailedWithnoAgenda() { when(agendaRepository.findAgendaByKey(agendaKey)).thenReturn(Optional.empty()); // expected - assertThrows(NotExistException.class, - () -> agendaService.findAgendaWithLatestAnnouncement(agendaKey)); + assertThrows(NotExistException.class, () -> agendaService.findAgendaWithLatestAnnouncement(agendaKey)); verify(agendaRepository, times(1)).findAgendaByKey(agendaKey); verify(agendaAnnouncementRepository, never()).findLatestByAgenda(agenda); } } + + @Nested + @DisplayName("Agenda 현황 전체 조회") + class GetAgendaListCurrent { + + @Test + @DisplayName("Agenda 현황 전체를 반환합니다.") + void getAgendaListSuccess() { + // given + int officialSize = 3; + int nonOfficialSize = 6; + List agendas = new ArrayList<>(); + IntStream.range(0, officialSize).forEach(i -> agendas.add(Agenda.builder() + .agendaKey(UUID.randomUUID()).isOfficial(true) + .deadline(LocalDateTime.now().plusDays(i + 3)).build())); + IntStream.range(0, nonOfficialSize).forEach(i -> agendas.add(Agenda.builder() + .agendaKey(UUID.randomUUID()).isOfficial(false) + .deadline(LocalDateTime.now().plusDays(i + 3)).build())); + when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); + + // when + List result = agendaService.findCurrentAgendaList(); + + // then + verify(agendaRepository, times(1)).findAllByStatusIs(any()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getIsOfficial()).isEqualTo(i < officialSize); + if (i == 0 || i == officialSize) { + continue; + } + assertThat(result.get(i).getAgendaDeadLine()).isBefore(result.get(i - 1).getAgendaDeadLine()); + } + } + + @Test + @DisplayName("생성된 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListWithNoContent() { + // given + List agendas = new ArrayList<>(); + when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); + + // when + agendaService.findCurrentAgendaList(); + + // then + verify(agendaRepository, times(1)).findAllByStatusIs(any()); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 2384a8a99..57cfb7afd 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -1,7 +1,5 @@ package gg.data.agenda; -import static gg.utils.exception.ErrorCode.*; - import java.time.LocalDateTime; import java.util.UUID; @@ -18,8 +16,6 @@ import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; -import gg.utils.exception.custom.ForbiddenException; -import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -85,10 +81,10 @@ public class Agenda extends BaseTimeEntity { private AgendaStatus status; @Column(name = "is_official", nullable = false, columnDefinition = "BIT(1)") - private boolean isOfficial; + private Boolean isOfficial; @Column(name = "is_ranking", nullable = false, columnDefinition = "BIT(1)") - private boolean isRanking; + private Boolean isRanking; @Builder public Agenda(Long id, UUID agendaKey, String title, String content, LocalDateTime deadline, diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index a7f57ca3e..a8ba2ff14 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -1,5 +1,6 @@ package gg.repo.agenda; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -7,9 +8,13 @@ import org.springframework.data.jpa.repository.Query; import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; public interface AgendaRepository extends JpaRepository { @Query("SELECT a FROM Agenda a WHERE a.agendaKey = :agendaKey") Optional findAgendaByKey(UUID agendaKey); + + @Query("SELECT a FROM Agenda a WHERE a.status = :status") + List findAllByStatusIs(AgendaStatus status); } From 4fe1a5ec8f5c32ad2304f6ff336b812cc47dbce8 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:14:10 +0900 Subject: [PATCH 025/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#843=20Team=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EA=B8=B0=20API=20(#874)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/agenda/service/AgendaService.java | 2 +- .../controller/AgendaTeamController.java | 38 ++ .../controller/request/TeamCreateReqDto.java | 36 ++ .../controller/response/TeamCreateResDto.java | 12 + .../agendateam/service/AgendaTeamService.java | 88 ++++ .../java/gg/agenda/api/AgendaMockData.java | 160 +++++++- .../agendateam/AgendaTeamControllerTest.java | 377 ++++++++++++++++++ .../api/user/service/AgendaServiceTest.java | 8 +- .../src/main/java/gg/data/agenda/Agenda.java | 45 ++- .../gg/data/agenda/AgendaAnnouncement.java | 4 +- .../java/gg/data/agenda/AgendaProfile.java | 9 + .../main/java/gg/data/agenda/AgendaTeam.java | 19 +- .../gg/data/agenda/AgendaTeamProfile.java | 2 +- .../src/main/java/gg/data/agenda/Ticket.java | 7 + .../resources/db/migration/V3__agenda.sql | 4 +- .../repo/agenda/AgendaProfileRepository.java | 3 + .../java/gg/repo/agenda/AgendaRepository.java | 3 +- .../agenda/AgendaTeamProfileRepository.java | 9 + .../gg/repo/agenda/AgendaTeamRepository.java | 12 + .../java/gg/repo/agenda/TicketRepository.java | 4 + .../java/gg/utils/exception/ErrorCode.java | 4 +- .../exception/custom/BusinessException.java | 4 + .../custom/DuplicationException.java | 4 + 23 files changed, 831 insertions(+), 23 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 8b30c7a6b..7aaaef4da 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -30,7 +30,7 @@ public class AgendaService { @Transactional(readOnly = true) public AgendaResponseDto findAgendaWithLatestAnnouncement(UUID agendaKey) { - Agenda agenda = agendaRepository.findAgendaByKey(agendaKey) + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); AgendaAnnouncement announcement = agendaAnnouncementRepository .findLatestByAgenda(agenda).orElse(null); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java new file mode 100644 index 000000000..320dd832f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -0,0 +1,38 @@ +package gg.agenda.api.user.agendateam.controller; + +import java.util.UUID; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.agenda.api.user.agendateam.service.AgendaTeamService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/team") +public class AgendaTeamController { + private final AgendaTeamService agendaTeamService; + + /** + * 아젠다 팀 생성하기 + * @param user 사용자 정보, teamCreateReqDto 팀 생성 요청 정보, agendaId 아젠다 아이디 + * @return 만들어진 팀 KEY + */ + @PostMapping + public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Login UserDto user, + @RequestBody TeamCreateReqDto teamCreateReqDto, @RequestParam("agenda_key") UUID agendaKey) { + TeamCreateResDto teamCreateResDto = agendaTeamService.addAgendaTeam(user, teamCreateReqDto, agendaKey); + return ResponseEntity.status(HttpStatus.CREATED).body(teamCreateResDto); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java new file mode 100644 index 000000000..c7e61a348 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java @@ -0,0 +1,36 @@ +package gg.agenda.api.user.agendateam.controller.request; + +import static gg.data.agenda.type.AgendaTeamStatus.*; + +import java.util.UUID; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Location; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TeamCreateReqDto { + private String teamName; + private boolean teamIsPrivate; + private String teamLocation; + private String teamContent; + + public static AgendaTeam toEntity(TeamCreateReqDto teamCreateReqDto, Agenda agenda, String intraId) { + return AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name(teamCreateReqDto.getTeamName()) + .content(teamCreateReqDto.getTeamContent()) + .leaderIntraId(intraId) + .status(OPEN) + .location(Location.valueOf(teamCreateReqDto.getTeamLocation())) + .mateCount(1) + .award("award") + .awardPriority(1) + .isPrivate(teamCreateReqDto.isTeamIsPrivate()) + .build(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java new file mode 100644 index 000000000..7d7ddf678 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java @@ -0,0 +1,12 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class TeamCreateResDto { + String teamKey; +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java new file mode 100644 index 000000000..f82bc969b --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -0,0 +1,88 @@ +package gg.agenda.api.user.agendateam.service; + +import static gg.data.agenda.type.AgendaTeamStatus.*; +import static gg.utils.exception.ErrorCode.*; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.auth.UserDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Location; +import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.repo.agenda.AgendaTeamRepository; +import gg.repo.agenda.TicketRepository; +import gg.utils.exception.custom.BusinessException; +import gg.utils.exception.custom.DuplicationException; +import gg.utils.exception.custom.ForbiddenException; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaTeamService { + private final AgendaRepository agendaRepository; + private final TicketRepository ticketRepository; + private final AgendaTeamRepository agendaTeamRepository; + private final AgendaProfileRepository agendaProfileRepository; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; + + /** + * 아젠다 팀 생성하기 + * @param user 사용자 정보, teamCreateReqDto 팀 생성 요청 정보, agendaId 아젠다 아이디 + * @return 만들어진 팀 KEY + */ + @Transactional + public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqDto, UUID agendaKey) { + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException("해당 유저의 프로필이 존재하지 않습니다.")); + + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + agenda.addTeam(Location.valueOf(teamCreateReqDto.getTeamLocation()), LocalDateTime.now()); + + if (agenda.getHostIntraId().equals(user.getIntraId())) { + throw new ForbiddenException(HOST_FORBIDDEN); + } + + if (agenda.getLocation() != Location.MIX && agenda.getLocation() != agendaProfile.getLocation()) { + throw new BusinessException(LOCATION_NOT_VALID); + } + + agendaTeamProfileRepository.findByAgendaAndIsExistTrue(agenda, agendaProfile).ifPresent(teamProfile -> { + throw new DuplicationException(TEAM_FORBIDDEN); + }); + + if (agenda.getIsOfficial()) { + ticketRepository.findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(agendaProfile) + .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); + } + + try { + Location.valueOf(teamCreateReqDto.getTeamLocation().toUpperCase()); + } catch (IllegalArgumentException e) { + throw new BusinessException(LOCATION_NOT_VALID); + } + + agendaTeamRepository.findByAgendaAndTeamNameAndStatus(agenda, teamCreateReqDto.getTeamName(), OPEN, CONFIRM) + .ifPresent(team -> { + throw new DuplicationException(TEAM_NAME_EXIST); + }); + + AgendaTeam agendaTeam = TeamCreateReqDto.toEntity(teamCreateReqDto, agenda, user.getIntraId()); + agendaTeamRepository.save(agendaTeam); + agendaRepository.save(agenda); + + return new TeamCreateResDto(agendaTeam.getTeamKey().toString()); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 7386b924f..052ca78fd 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,5 +1,10 @@ package gg.agenda.api; +import static gg.data.agenda.type.AgendaStatus.*; +import static gg.data.agenda.type.Coalition.*; +import static gg.data.agenda.type.Location.*; +import static java.util.UUID.*; + import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @@ -12,10 +17,16 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; +import gg.data.user.User; import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.repo.agenda.TicketRepository; import lombok.RequiredArgsConstructor; @Component @@ -23,9 +34,10 @@ public class AgendaMockData { private final EntityManager em; - + private final TicketRepository ticketRepository; private final AgendaRepository agendaRepository; - + private final AgendaProfileRepository agendaProfileRepository; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaAnnouncementRepository agendaAnnouncementRepository; public Agenda createOfficialAgenda() { @@ -91,7 +103,7 @@ public List createOfficialAgendaList(int size, AgendaStatus status) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) - .isOfficial(true) // true + .isOfficial(true) // true .isRanking(true) .build() ) @@ -116,7 +128,7 @@ public List createNonOfficialAgendaList(int size, AgendaStatus status) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) - .isOfficial(false) // false + .isOfficial(false) // false .isRanking(true) .build() ) @@ -136,4 +148,144 @@ public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { em.clear(); return announcement; } + + public Agenda createAgenda() { + UUID uuid = randomUUID(); + Agenda agenda = Agenda.builder() + .agendaKey(uuid) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(String intraId) { + UUID uuid = randomUUID(); + Agenda agenda = Agenda.builder() + .agendaKey(uuid) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId(intraId) + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(Location location) { + UUID uuid = randomUUID(); + Agenda agenda = Agenda.builder() + .agendaKey(uuid) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(location) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(int curruentTeam) { + UUID uuid = randomUUID(); + Agenda agenda = Agenda.builder() + .agendaKey(uuid) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(curruentTeam) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(AgendaStatus status) { + UUID uuid = randomUUID(); + Agenda agenda = Agenda.builder() + .agendaKey(uuid) + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(status) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public AgendaProfile createAgendaProfile(User user, Location location) { + AgendaProfile agendaProfile = AgendaProfile.builder() + .content("content") + .githubUrl("githubUrl") + .coalition(LEE) + .location(location) + .userId(user.getId()) + .build(); + return agendaProfileRepository.save(agendaProfile); + } + + public Ticket createTicket(AgendaProfile agendaProfile) { + Ticket ticket = Ticket.builder() + .agendaProfile(agendaProfile) + .isApprove(true) + .isUsed(false) + .build(); + return ticketRepository.save(ticket); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java new file mode 100644 index 000000000..f85fc6078 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -0,0 +1,377 @@ +package gg.agenda.api.user.agendateam; + +import static gg.data.agenda.type.Location.*; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.Ticket; +import gg.data.agenda.type.AgendaStatus; +import gg.data.user.User; +import gg.repo.agenda.AgendaTeamRepository; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; + +@IntegrationTest +@AutoConfigureMockMvc +@Transactional +public class AgendaTeamControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private TestDataUtils testDataUtils; + @Autowired + private AgendaMockData agendaMockData; + @Autowired + AgendaTeamRepository agendaTeamRepository; + User user; + User anotherUser; + String accessToken; + String anotherAccessToken; + AgendaProfile agendaProfile; + AgendaProfile anotherAgendaProfile; + + @Nested + @DisplayName("팀 생성 테스트") + class AddTeamTest { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + anotherUser = testDataUtils.createNewUser(); + anotherAccessToken = testDataUtils.getLoginAccessTokenFromUser(anotherUser); + anotherAgendaProfile = agendaMockData.createAgendaProfile(anotherUser, GYEONGSAN); + } + + @Test + @DisplayName("201 서울 agenda에 서울 user 팀 생성 성공") + public void addNewTeamStatusSeoul() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(SEOUL); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("201 경산 agenda에 경산 user 팀 생성 성공") + public void addNewTeamStatusGyeongsan() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(GYEONGSAN); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("201 mix agenda에 서울 user 팀 생성 성공") + public void addNewTeamStatusMixFromSeoul() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(SEOUL); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("201 MIX agenda에 경산 user 팀 생성 성공") + public void addNewTeamStatusMixFromGyeongsan() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(GYEONGSAN); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("201 mix agenda에 서울 user가 mix 팀 생성 성공") + public void addNewTeamStatusMixFromMixToSeoul() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(MIX); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("201 MIX agenda에 경산 user가 mix 팀 생성 성공") + public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + // then + AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) + .orElse(null); + assertThat(createdTeam).isNotNull(); + assertThat(createdTeam.getName()).isEqualTo("teamName"); + assertThat(createdTeam.getLocation()).isEqualTo(MIX); + assertThat(createdTeam.getContent()).isEqualTo("teamContent"); + } + + @Test + @DisplayName("404 아젠다 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("400 참여 불가능한 Agenda Location 으로 인한 실패") + public void notValidAgendaLocation() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("400 참여 불가능한 Agenda Status 으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 참여 불가능한 Agenda Team 한도로 인한 실패") + public void notValidAgendaTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(5); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("400 참여 불가능한 Agenda 시간으로 인한 실패") + public void notValidAgendaDeadline() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 아젠다 호스트의 팀 생성으로 인한 실패") + public void agendaHostFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("400 참여 불가능한 유저의 Location 으로 인한 실패") + public void notValidUserLocation() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + anotherAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java index b611ce76e..f17075b21 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java @@ -50,14 +50,14 @@ void getAgendaSuccess() { // given UUID agendaKey = UUID.randomUUID(); Agenda agenda = mock(Agenda.class); - when(agendaRepository.findAgendaByKey(agendaKey)).thenReturn(Optional.of(agenda)); + when(agendaRepository.findByAgendaKey(agendaKey)).thenReturn(Optional.of(agenda)); when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.empty()); // when agendaService.findAgendaWithLatestAnnouncement(agendaKey); // then - verify(agendaRepository, times(1)).findAgendaByKey(agendaKey); + verify(agendaRepository, times(1)).findByAgendaKey(agendaKey); verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); } @@ -67,11 +67,11 @@ void getAgendaFailedWithnoAgenda() { // given UUID agendaKey = UUID.randomUUID(); Agenda agenda = mock(Agenda.class); - when(agendaRepository.findAgendaByKey(agendaKey)).thenReturn(Optional.empty()); + when(agendaRepository.findByAgendaKey(agendaKey)).thenReturn(Optional.empty()); // expected assertThrows(NotExistException.class, () -> agendaService.findAgendaWithLatestAnnouncement(agendaKey)); - verify(agendaRepository, times(1)).findAgendaByKey(agendaKey); + verify(agendaRepository, times(1)).findByAgendaKey(agendaKey); verify(agendaAnnouncementRepository, never()).findLatestByAgenda(agenda); } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 57cfb7afd..32a8beea7 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -1,5 +1,7 @@ package gg.data.agenda; +import static gg.utils.exception.ErrorCode.*; + import java.time.LocalDateTime; import java.util.UUID; @@ -16,6 +18,8 @@ import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; +import gg.utils.exception.custom.ForbiddenException; +import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -33,7 +37,7 @@ public class Agenda extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "agenda_key", nullable = false, columnDefinition = "BINARY(16)") + @Column(name = "agenda_key", nullable = false, unique = true, columnDefinition = "BINARY(16)") private UUID agendaKey; @Column(name = "title", nullable = false, columnDefinition = "VARCHAR(50)") @@ -88,9 +92,10 @@ public class Agenda extends BaseTimeEntity { @Builder public Agenda(Long id, UUID agendaKey, String title, String content, LocalDateTime deadline, - LocalDateTime startTime, LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, - int maxPeople, String posterUri, String hostIntraId, Location location, AgendaStatus status, - boolean isOfficial, boolean isRanking) { + LocalDateTime startTime, + LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, + String posterUri, String hostIntraId, Location location, AgendaStatus status, Boolean isOfficial, + Boolean isRanking) { this.id = id; this.agendaKey = agendaKey; this.title = title; @@ -110,4 +115,36 @@ public Agenda(Long id, UUID agendaKey, String title, String content, LocalDateTi this.isOfficial = isOfficial; this.isRanking = isRanking; } + + public void addTeam(Location location, LocalDateTime now) { + mustBeWithinLocation(location); + mustStatusOnGoing(); + mustBeforeDeadline(now); + mustHaveCapacity(); + this.currentTeam++; + } + + private void mustBeWithinLocation(Location location) { + if (this.location != Location.MIX && this.location != location) { + throw new InvalidParameterException(LOCATION_NOT_VALID); + } + } + + private void mustStatusOnGoing() { + if (this.status != AgendaStatus.ON_GOING) { + throw new InvalidParameterException(AGENDA_NOT_OPEN); + } + } + + private void mustBeforeDeadline(LocalDateTime now) { + if (this.deadline.isBefore(now)) { + throw new InvalidParameterException(AGENDA_NOT_OPEN); + } + } + + private void mustHaveCapacity() { + if (this.currentTeam == this.maxTeam) { + throw new ForbiddenException(AGENDA_NO_CAPACITY); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java index e1bb65d72..97a7d28dc 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java @@ -33,14 +33,14 @@ public class AgendaAnnouncement extends BaseTimeEntity { private String content; @Column(name = "is_show", nullable = false, columnDefinition = "BIT(1)") - private boolean isShow; + private Boolean isShow; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "agenda_id") private Agenda agenda; @Builder - public AgendaAnnouncement(Long id, String title, String content, boolean isShow, Agenda agenda) { + public AgendaAnnouncement(Long id, String title, String content, Boolean isShow, Agenda agenda) { this.id = id; this.title = title; this.content = content; diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index 52e221db0..f14444628 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -43,4 +43,13 @@ public class AgendaProfile extends BaseTimeEntity { @Column(name = "user_id", nullable = false, columnDefinition = "BIGINT") private Long userId; + + @Builder + public AgendaProfile(String content, String githubUrl, Coalition coalition, Location location, Long userId) { + this.content = content; + this.githubUrl = githubUrl; + this.coalition = coalition; + this.location = location; + this.userId = userId; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index e0235fd80..bfeb632f5 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -63,5 +63,22 @@ public class AgendaTeam extends BaseTimeEntity { private int awardPriority; @Column(name = "is_private", nullable = false, columnDefinition = "BIT(1)") - private boolean isPrivate; + private Boolean isPrivate; + + @Builder + public AgendaTeam(Agenda agenda, UUID teamKey, String name, String content, String leaderIntraId, + AgendaTeamStatus status, Location location, int mateCount, String award, int awardPriority, Boolean isPrivate) { + this.agenda = agenda; + this.teamKey = teamKey; + this.name = name; + this.content = content; + this.leaderIntraId = leaderIntraId; + this.status = status; + this.location = location; + this.mateCount = mateCount; + this.award = award; + this.awardPriority = awardPriority; + this.isPrivate = isPrivate; + } + } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index f9bd0dc25..bf856ad42 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -32,5 +32,5 @@ public class AgendaTeamProfile extends BaseTimeEntity { private AgendaTeam agendaTeam; @Column(name = "is_exist", nullable = false, columnDefinition = "BIT(1)") - private boolean isExist; + private Boolean isExist; } diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index 4d711649d..41e7b00cb 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -35,5 +35,12 @@ public class Ticket extends BaseTimeEntity { @Column(name = "is_approve", nullable = false, columnDefinition = "BIT(1)") private Boolean isApprove; + + @Builder + public Ticket(AgendaProfile agendaProfile, Boolean isUsed, Boolean isApprove) { + this.agendaProfile = agendaProfile; + this.isUsed = isUsed; + this.isApprove = isApprove; + } } diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 7855e9fdb..a7bebe7c8 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -1,7 +1,7 @@ CREATE TABLE `agenda` ( `id` BIGINT NOT NULL AUTO_INCREMENT, - `agenda_key` BINARY(16) NOT NULL, + `agenda_key` BINARY(16) NOT NULL, `title` VARCHAR(50) NOT NULL, `content` VARCHAR(500) NOT NULL, `deadline` DATETIME NOT NULL, @@ -28,7 +28,7 @@ CREATE TABLE `agenda_team` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `agenda_id` BIGINT NOT NULL, - `team_key` BINARY(16) NOT NULL, + `team_key` BINARY(16) NOT NULL, `name` VARCHAR(30) NOT NULL, `content` VARCHAR(500) NOT NULL, `leader_intra_id` VARCHAR(30) NOT NULL, diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java index 5cd7159f3..ae365a233 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java @@ -1,8 +1,11 @@ package gg.repo.agenda; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import gg.data.agenda.AgendaProfile; public interface AgendaProfileRepository extends JpaRepository { + Optional findByUserId(Long userId); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index a8ba2ff14..d5c10e208 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -11,9 +11,8 @@ import gg.data.agenda.type.AgendaStatus; public interface AgendaRepository extends JpaRepository { - @Query("SELECT a FROM Agenda a WHERE a.agendaKey = :agendaKey") - Optional findAgendaByKey(UUID agendaKey); + Optional findByAgendaKey(UUID agendaKey); @Query("SELECT a FROM Agenda a WHERE a.status = :status") List findAllByStatusIs(AgendaStatus status); diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index ad9a35b1d..4d49bb69c 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -1,8 +1,17 @@ package gg.repo.agenda; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; public interface AgendaTeamProfileRepository extends JpaRepository { + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam.agenda = :agenda " + + "AND atp.profile = :agendaProfile AND atp.isExist = true") + Optional findByAgendaAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 6928195e9..4d61c8d31 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -1,8 +1,20 @@ package gg.repo.agenda; +import java.util.Optional; +import java.util.UUID; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaTeamStatus; public interface AgendaTeamRepository extends JpaRepository { + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :teamName AND" + + " (a.status = :status1 OR a.status = :status2)") + Optional findByAgendaAndTeamNameAndStatus(Agenda agenda, String teamName, AgendaTeamStatus status1, + AgendaTeamStatus status2); + + Optional findByTeamKey(UUID teamKey); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index a1a2f7d42..6e9e972bd 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -1,8 +1,12 @@ package gg.repo.agenda; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; public interface TicketRepository extends JpaRepository { + Optional findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(AgendaProfile agendaProfile); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index e734b2fbb..95f2870ce 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -194,12 +194,12 @@ public enum ErrorCode { // agenda AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), - AGENDA_NO_CAPACITY(409, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), - TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."); + TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), + AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."); private final int status; private final String errCode; diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/BusinessException.java b/gg-utils/src/main/java/gg/utils/exception/custom/BusinessException.java index eafb5fa95..8909e97d9 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/BusinessException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/BusinessException.java @@ -10,4 +10,8 @@ public BusinessException(String message, ErrorCode errorCode) { public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage(), errorCode); } + + public BusinessException(String message) { + super(message, ErrorCode.BAD_REQUEST); + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/DuplicationException.java b/gg-utils/src/main/java/gg/utils/exception/custom/DuplicationException.java index 0fc4c5861..dc82f298a 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/DuplicationException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/DuplicationException.java @@ -10,4 +10,8 @@ public DuplicationException(String message, ErrorCode errorCode) { public DuplicationException(String message) { super(message, ErrorCode.CONFLICT); } + + public DuplicationException(ErrorCode errorCode) { + super(errorCode.getMessage(), errorCode); + } } From 1ef7cb1a24b2cd3a8ae39ad5fb2e2b17a30de09b Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:04:41 +0900 Subject: [PATCH 026/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#859=20Agenda?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EA=B8=B0=20API=20(#875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 22 +- .../controller/dto/AgendaCreateDto.java | 144 +++++++ .../controller/dto/AgendaKeyResponseDto.java | 20 + .../user/agenda/service/AgendaService.java | 10 + .../java/gg/agenda/api/AgendaMockData.java | 14 - .../controller/AgendaControllerTest.java | 401 ++++++++++++++++++ .../controller/dto/AgendaCreateDtoTest.java | 125 ++++++ .../dto/AgendaSimpleResponseDtoTest.java | 3 +- .../service/AgendaServiceTest.java | 41 +- .../user/controller/AgendaControllerTest.java | 178 -------- .../src/main/java/gg/data/agenda/Agenda.java | 11 +- .../java/gg/utils/exception/ErrorCode.java | 6 +- 12 files changed, 766 insertions(+), 209 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java rename gg-agenda-api/src/test/java/gg/agenda/api/user/{ => agenda}/controller/dto/AgendaSimpleResponseDtoTest.java (93%) rename gg-agenda-api/src/test/java/gg/agenda/api/user/{ => agenda}/service/AgendaServiceTest.java (75%) delete mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 37c77f131..84d858f6a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -3,15 +3,24 @@ import java.util.List; import java.util.UUID; +import javax.validation.Valid; + +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; import gg.agenda.api.user.agenda.service.AgendaService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; @@ -23,12 +32,12 @@ public class AgendaController { private final AgendaService agendaService; + @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Agenda 상세 조회 성공"), @ApiResponse(responseCode = "400", description = "Agenda 조회 요청이 잘못됨"), @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음") }) - @GetMapping public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { AgendaResponseDto agendaDto = agendaService.findAgendaWithLatestAnnouncement(agendaKey); return ResponseEntity.ok(agendaDto); @@ -40,4 +49,15 @@ public ResponseEntity> agendaListCurrent() { List agendaList = agendaService.findCurrentAgendaList(); return ResponseEntity.ok(agendaList); } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Agenda 생성 성공"), + @ApiResponse(responseCode = "400", description = "Agenda 생성 요청 파라미터가 잘못됨") + }) + @PostMapping("/create") + public ResponseEntity agendaAdd(@Login UserDto user, + @RequestBody @Valid AgendaCreateDto agendaCreateDto) { + AgendaKeyResponseDto agendaKey = agendaService.addAgenda(agendaCreateDto, user); + return ResponseEntity.status(HttpStatus.CREATED).body(agendaKey); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java new file mode 100644 index 000000000..b73361a6f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java @@ -0,0 +1,144 @@ +package gg.agenda.api.user.agenda.controller.dto; + +import static gg.utils.exception.ErrorCode.*; + +import java.time.LocalDateTime; + +import javax.validation.constraints.Future; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.auth.UserDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.type.Location; +import gg.utils.exception.custom.InvalidParameterException; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaCreateDto { + + @NotNull + @NotEmpty + private String agendaTitle; + + @NotNull + @NotEmpty + private String agendaContent; + + @NotNull + @Future(message = "마감일은 현재 시간 이후여야 합니다.") + private LocalDateTime agendaDeadLine; + + @NotNull + @Future(message = "시작 시간은 현재 시간 이후여야 합니다.") + private LocalDateTime agendaStartTime; + + @NotNull + @Future(message = "종료 시간은 현재 시간 이후여야 합니다.") + private LocalDateTime agendaEndTime; + + @Min(2) + @Max(1000) + private int agendaMinTeam; + + @Min(2) + @Max(1000) + private int agendaMaxTeam; + + @Min(1) + @Max(100) + private int agendaMinPeople; + + @Min(1) + @Max(100) + private int agendaMaxPeople; + + private String agendaPoster; + + @NotNull + private Location agendaLocation; + + @NotNull + private Boolean agendaIsRanking; + + @NotNull + private Boolean agendaIsOfficial; + + @Builder + public AgendaCreateDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, + int agendaMinPeople, int agendaMaxPeople, String agendaPoster, Location agendaLocation, + Boolean agendaIsRanking, Boolean agendaIsOfficial) { + this.agendaTitle = agendaTitle; + this.agendaContent = agendaContents; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaMinTeam = agendaMinTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + this.agendaPoster = agendaPoster; + this.agendaLocation = agendaLocation; + this.agendaIsRanking = agendaIsRanking; + this.agendaIsOfficial = agendaIsOfficial; + } + + @Mapper + public interface MapStruct { + AgendaCreateDto.MapStruct INSTANCE = Mappers.getMapper(AgendaCreateDto.MapStruct.class); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "title", source = "dto.agendaTitle") + @Mapping(target = "content", source = "dto.agendaContent") + @Mapping(target = "deadline", source = "dto.agendaDeadLine") + @Mapping(target = "startTime", source = "dto.agendaStartTime") + @Mapping(target = "endTime", source = "dto.agendaEndTime") + @Mapping(target = "minTeam", source = "dto.agendaMinTeam") + @Mapping(target = "maxTeam", source = "dto.agendaMaxTeam") + @Mapping(target = "currentTeam", constant = "0") + @Mapping(target = "minPeople", source = "dto.agendaMinPeople") + @Mapping(target = "maxPeople", source = "dto.agendaMaxPeople") + @Mapping(target = "posterUri", source = "dto.agendaPoster") + @Mapping(target = "hostIntraId", source = "user.intraId") + @Mapping(target = "location", source = "dto.agendaLocation") + @Mapping(target = "status", constant = "ON_GOING") + @Mapping(target = "isOfficial", source = "dto.agendaIsOfficial") + @Mapping(target = "isRanking", source = "dto.agendaIsRanking") + Agenda toEntity(AgendaCreateDto dto, UserDto user); + + @BeforeMapping + default void mustHaveValidAgendaSchedule(AgendaCreateDto dto, UserDto user) { + if (!dto.getAgendaDeadLine().isBefore(dto.getAgendaStartTime())) { + throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); + } + if (!dto.getAgendaDeadLine().isBefore(dto.getAgendaEndTime())) { + throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); + } + if (!dto.getAgendaStartTime().isBefore(dto.getAgendaEndTime())) { + throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); + } + } + + @BeforeMapping + default void mustHaveValidParam(AgendaCreateDto dto, UserDto user) { + if (dto.getAgendaMinTeam() > dto.getAgendaMaxTeam()) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + if (dto.getAgendaMinPeople() > dto.getAgendaMaxPeople()) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + } + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java new file mode 100644 index 000000000..a5dd1b48d --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java @@ -0,0 +1,20 @@ +package gg.agenda.api.user.agenda.controller.dto; + +import java.util.UUID; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaKeyResponseDto { + + private UUID agendaKey; + + @Builder + public AgendaKeyResponseDto(UUID agendaKey) { + this.agendaKey = agendaKey; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 7aaaef4da..fb5ca11a7 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -10,8 +10,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.data.agenda.type.AgendaStatus; @@ -45,4 +48,11 @@ public List findCurrentAgendaList() { .map(AgendaSimpleResponseDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); } + + @Transactional + public AgendaKeyResponseDto addAgenda(AgendaCreateDto agendaCreateDto, UserDto user) { + Agenda newAgenda = AgendaCreateDto.MapStruct.INSTANCE.toEntity(agendaCreateDto, user); + Agenda savedAgenda = agendaRepository.save(newAgenda); + return AgendaKeyResponseDto.builder().agendaKey(savedAgenda.getAgendaKey()).build(); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 052ca78fd..c06a925e6 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -42,7 +42,6 @@ public class AgendaMockData { public Agenda createOfficialAgenda() { Agenda agenda = Agenda.builder() - .agendaKey(UUID.randomUUID()) .title("title " + UUID.randomUUID()) .content("content " + UUID.randomUUID()) .deadline(LocalDateTime.now().plusDays(3)) @@ -65,7 +64,6 @@ public Agenda createOfficialAgenda() { public Agenda createNonOfficialAgenda() { Agenda agenda = Agenda.builder() - .agendaKey(UUID.randomUUID()) .title("title " + UUID.randomUUID()) .content("content " + UUID.randomUUID()) .deadline(LocalDateTime.now().plusDays(3)) @@ -88,7 +86,6 @@ public Agenda createNonOfficialAgenda() { public List createOfficialAgendaList(int size, AgendaStatus status) { List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() - .agendaKey(UUID.randomUUID()) .title("title " + UUID.randomUUID()) .content("content " + UUID.randomUUID()) .deadline(LocalDateTime.now().plusDays(i + 3)) @@ -113,7 +110,6 @@ public List createOfficialAgendaList(int size, AgendaStatus status) { public List createNonOfficialAgendaList(int size, AgendaStatus status) { List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() - .agendaKey(UUID.randomUUID()) .title("title " + UUID.randomUUID()) .content("content " + UUID.randomUUID()) .deadline(LocalDateTime.now().plusDays(i + 3)) @@ -150,9 +146,7 @@ public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { } public Agenda createAgenda() { - UUID uuid = randomUUID(); Agenda agenda = Agenda.builder() - .agendaKey(uuid) .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) @@ -174,9 +168,7 @@ public Agenda createAgenda() { } public Agenda createAgenda(String intraId) { - UUID uuid = randomUUID(); Agenda agenda = Agenda.builder() - .agendaKey(uuid) .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) @@ -198,9 +190,7 @@ public Agenda createAgenda(String intraId) { } public Agenda createAgenda(Location location) { - UUID uuid = randomUUID(); Agenda agenda = Agenda.builder() - .agendaKey(uuid) .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) @@ -222,9 +212,7 @@ public Agenda createAgenda(Location location) { } public Agenda createAgenda(int curruentTeam) { - UUID uuid = randomUUID(); Agenda agenda = Agenda.builder() - .agendaKey(uuid) .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) @@ -246,9 +234,7 @@ public Agenda createAgenda(int curruentTeam) { } public Agenda createAgenda(AgendaStatus status) { - UUID uuid = randomUUID(); Agenda agenda = Agenda.builder() - .agendaKey(uuid) .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java new file mode 100644 index 000000000..51d5bea78 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -0,0 +1,401 @@ +package gg.agenda.api.user.agenda.controller; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import gg.repo.agenda.AgendaRepository; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaMockData agendaMockData; + + @Autowired + EntityManager em; + + @Autowired + AgendaRepository agendaRepository; + + private String accessToken; + + @BeforeEach + void setUp() { + User user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("Agenda 상세 조회") + class GetAgenda { + + @Test + @DisplayName("agenda_id에 해당하는 Agenda를 상세 조회합니다.") + void getAgendaSuccess() throws Exception { + // given + Agenda agenda = agendaMockData.createOfficialAgenda(); + AgendaAnnouncement announcement = agendaMockData.createAgendaAnnouncement(agenda); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(announcement.getTitle()); + } + + @Test + @DisplayName("announce가 없는 경우 announcementTitle를 null로 반환합니다.") + void getAgendaWithNoAnnounce() throws Exception { + // given + Agenda agenda = agendaMockData.createOfficialAgenda(); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(null); + } + + @Test + @DisplayName("announce가 여러 개인 경우 가장 최근 작성된 announce를 반환합니다.") + void getAgendaWithLatestAnnounce() throws Exception { + // given + Agenda agenda = agendaMockData.createOfficialAgenda(); + AgendaAnnouncement announcement1 = agendaMockData.createAgendaAnnouncement(agenda); + AgendaAnnouncement announcement2 = agendaMockData.createAgendaAnnouncement(agenda); + AgendaAnnouncement announcement3 = agendaMockData.createAgendaAnnouncement(agenda); + + // when + String response = mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + + // then + assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement1.getTitle()); + assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement2.getTitle()); + assertThat(result.getAnnouncementTitle()).isEqualTo(announcement3.getTitle()); + } + + @Test + @DisplayName("agenda_key가 잘못된 경우 400를 반환합니다.") + void getAgendaFailedWhenInvalidKey() throws Exception { + // given + Agenda agenda = agendaMockData.createOfficialAgenda(); + AgendaAnnouncement announcement = agendaMockData.createAgendaAnnouncement(agenda); + + // expected + mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", "invalid_key")) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("agenda_key에 해당하는 agenda가 없는 경우 404를 반환합니다.") + void getAgendaFailedWhenNoContent() throws Exception { + // given + UUID invalidKey = UUID.randomUUID(); + + // expected + mockMvc.perform(get("/agenda") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", invalidKey.toString())) + .andExpect(status().isNotFound()); + } + } + + @Nested + @DisplayName("Agenda 현황 전체 조회") + class GetAgendaListCurrent { + + @Test + @DisplayName("Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") + void getAgendaListSuccess() throws Exception { + // given + List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.ON_GOING); + List nonOfficialAgendaList = agendaMockData + .createNonOfficialAgendaList(6, AgendaStatus.ON_GOING); + + // when + String response = mockMvc.perform(get("/agenda/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(officialAgendaList.size() + nonOfficialAgendaList.size()); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); + if (i == 0 || i == officialAgendaList.size()) { + continue; + } + assertThat(result[i].getAgendaDeadLine()).isBefore(result[i - 1].getAgendaDeadLine()); + } + } + + @Test + @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListSuccessWithNoAgenda() throws Exception { + // given + agendaMockData.createOfficialAgendaList(3, AgendaStatus.CONFIRM); + agendaMockData.createNonOfficialAgendaList(6, AgendaStatus.CANCEL); + + // when + String response = mockMvc.perform(get("/agenda/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(0); + } + } + + @Nested + @DisplayName("Agenda 생성하기") + class CreateAgenda { + + @Test + @DisplayName("Agenda를 생성합니다.") + void createAgendaSuccess() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // when + String response = mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + AgendaKeyResponseDto result = objectMapper.readValue(response, AgendaKeyResponseDto.class); + Optional agenda = agendaRepository.findByAgendaKey(result.getAgendaKey()); + + // then + assertThat(agenda.isPresent()).isTrue(); + assertThat(agenda.get().getTitle()).isEqualTo(dto.getAgendaTitle()); + assertThat(agenda.get().getContent()).isEqualTo(dto.getAgendaContent()); + } + + @Test + @DisplayName("deadline이 startTime보다 미래인 경우 400을 반환합니다.") + void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(6)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("deadline이 endTime보다 미래인 경우 400을 반환합니다.") + void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(4)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("startTime이 endTime보다 미래인 경우 400을 반환합니다.") + void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(7)) + .agendaEndTime(LocalDateTime.now().plusDays(5)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("min team이 max team보다 큰 경우 400을 반환합니다.") + void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(7).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("min people이 max people보다 큰 경우 400을 반환합니다.") + void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(6).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 0, -1}) + @DisplayName("min team이 1 이하인 경우 400을 반환합니다.") + void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(value).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1}) + @DisplayName("min people이 0 이하인 경우 400을 반환합니다.") + void createAgendaFailedWhenNegativeMinPeople(int value) throws Exception { + // given + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaTitle("title").agendaContents("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(value).agendaMaxPeople(5) + .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/create") + .header("Authorization", "Bearer " + accessToken) + .contentType("application/json") + .content(request)) + .andExpect(status().isBadRequest()); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java new file mode 100644 index 000000000..780eb69f0 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java @@ -0,0 +1,125 @@ +package gg.agenda.api.user.agenda.controller.dto; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import gg.auth.UserDto; +import gg.data.agenda.Agenda; +import gg.utils.annotation.UnitTest; +import gg.utils.exception.custom.InvalidParameterException; + +@UnitTest +class AgendaCreateDtoTest { + + @Test + @DisplayName("Agenda 생성 성공") + void createAgendaSuccess() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(5)) + .agendaStartTime(LocalDateTime.now().plusDays(8)) + .agendaEndTime(LocalDateTime.now().plusDays(10)) + .build(); + + // when + Agenda agenda = AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user); + + // then + assertNotNull(agenda); + assertThat(agenda.getHostIntraId()).isEqualTo(user.getIntraId()); + } + + @Nested + @DisplayName("Agenda 생성 실패") + class CreateAgendaFailed { + + @Test + @DisplayName("deadline이 start time보다 늦을 경우") + void createAgendaFailedWhenDeadlineIsBeforeStartTime() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(5)) + .agendaStartTime(LocalDateTime.now().plusDays(2)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .build(); + + // expected + assertThrows(InvalidParameterException.class, + () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + } + + @Test + @DisplayName("deadline이 end time보다 늦을 경우") + void createAgendaFailedWhenDeadlineIsBeforeEndTime() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(5)) + .agendaStartTime(LocalDateTime.now().plusDays(7)) + .agendaEndTime(LocalDateTime.now().plusDays(6)) + .build(); + + // expected + assertThrows(InvalidParameterException.class, + () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + } + + @Test + @DisplayName("start time이 end time보다 늦을 경우") + void createAgendaFailedWhenStartTimeIsBeforeEndTime() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(6)) + .agendaEndTime(LocalDateTime.now().plusDays(5)) + .build(); + + // expected + assertThrows(InvalidParameterException.class, + () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + } + + @Test + @DisplayName("min team이 max team보다 큰 경우") + void createAgendaFailedWhenMinTeamIsGreaterThanMaxTeam() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(6)) + .agendaEndTime(LocalDateTime.now().plusDays(8)) + .agendaMinTeam(5).agendaMaxTeam(2) + .build(); + + // expected + assertThrows(InvalidParameterException.class, + () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + } + + @Test + @DisplayName("min people이 max people보다 큰 경우") + void createAgendaFailedWhenMinPeopleIsGreaterThanMaxPeople() { + //given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto dto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(6)) + .agendaEndTime(LocalDateTime.now().plusDays(8)) + .agendaMinPeople(5).agendaMaxPeople(2) + .build(); + + // expected + assertThrows(InvalidParameterException.class, + () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java similarity index 93% rename from gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java rename to gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java index f337476be..7ee1cbc0a 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/dto/AgendaSimpleResponseDtoTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.controller.dto; +package gg.agenda.api.user.agenda.controller.dto; import static org.assertj.core.api.AssertionsForClassTypes.*; @@ -26,7 +26,6 @@ class CreateAgendaSimpleResponseDto { void createAgendaSimpleResponseDtoSuccess(boolean value) { // when Agenda agenda = Agenda.builder() - .agendaKey(UUID.randomUUID()) .isOfficial(value) .build(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java similarity index 75% rename from gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java rename to gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index f17075b21..dfda9ba05 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.service; +package gg.agenda.api.user.agenda.service; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -17,8 +17,10 @@ import org.mockito.InjectMocks; import org.mockito.Mock; +import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; +import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; -import gg.agenda.api.user.agenda.service.AgendaService; +import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; @@ -87,11 +89,9 @@ void getAgendaListSuccess() { int officialSize = 3; int nonOfficialSize = 6; List agendas = new ArrayList<>(); - IntStream.range(0, officialSize).forEach(i -> agendas.add(Agenda.builder() - .agendaKey(UUID.randomUUID()).isOfficial(true) + IntStream.range(0, officialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(true) .deadline(LocalDateTime.now().plusDays(i + 3)).build())); - IntStream.range(0, nonOfficialSize).forEach(i -> agendas.add(Agenda.builder() - .agendaKey(UUID.randomUUID()).isOfficial(false) + IntStream.range(0, nonOfficialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(false) .deadline(LocalDateTime.now().plusDays(i + 3)).build())); when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); @@ -123,4 +123,33 @@ void getAgendaListWithNoContent() { verify(agendaRepository, times(1)).findAllByStatusIs(any()); } } + + @Nested + @DisplayName("Agenda 생성") + class CreateAgenda { + + @Test + @DisplayName("Agenda 생성 성공") + void createAgendaSuccess() { + // given + UserDto user = UserDto.builder().intraId("intraId").build(); + AgendaCreateDto agendaCreateDto = AgendaCreateDto.builder() + .agendaDeadLine(LocalDateTime.now().plusDays(5)) + .agendaStartTime(LocalDateTime.now().plusDays(8)) + .agendaEndTime(LocalDateTime.now().plusDays(10)) + .agendaMinTeam(2).agendaMaxTeam(5) + .agendaMinPeople(1).agendaMaxPeople(5) + .build(); + Agenda agenda = Agenda.builder().build(); + when(agendaRepository.save(any(Agenda.class))).thenReturn(agenda); + + // when + AgendaKeyResponseDto agendaKeyResponseDto = agendaService.addAgenda(agendaCreateDto, user); + + // then + verify(agendaRepository, times(1)).save(any(Agenda.class)); + assertThat(agendaKeyResponseDto).isNotNull(); + assertThat(agendaKeyResponseDto.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java deleted file mode 100644 index fb9cbbb92..000000000 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/controller/AgendaControllerTest.java +++ /dev/null @@ -1,178 +0,0 @@ -package gg.agenda.api.user.controller; - -import static org.assertj.core.api.Assertions.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import java.util.List; - -import javax.persistence.EntityManager; -import javax.transaction.Transactional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.test.web.servlet.MockMvc; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import gg.agenda.api.AgendaMockData; -import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; -import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; -import gg.data.agenda.type.AgendaStatus; -import gg.data.user.User; -import gg.utils.TestDataUtils; -import gg.utils.annotation.IntegrationTest; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@IntegrationTest -@Transactional -@AutoConfigureMockMvc -public class AgendaControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private TestDataUtils testDataUtils; - - @Autowired - private AgendaMockData agendaMockData; - - @Autowired - EntityManager em; - - private String accessToken; - - @BeforeEach - void setUp() { - User user = testDataUtils.createNewUser(); - accessToken = testDataUtils.getLoginAccessTokenFromUser(user); - } - - @Nested - @DisplayName("Agenda 상세 조회") - class GetAgenda { - - @Test - @DisplayName("agenda_id에 해당하는 Agenda를 상세 조회합니다.") - void test() throws Exception { - // given - Agenda agenda = agendaMockData.createOfficialAgenda(); - AgendaAnnouncement announcement = agendaMockData.createAgendaAnnouncement(agenda); - - // when - String response = mockMvc.perform(get("/agenda") - .header("Authorization", "Bearer " + accessToken) - .param("agenda_key", agenda.getAgendaKey().toString())) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); - - // then - assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); - assertThat(result.getAnnouncementTitle()).isEqualTo(announcement.getTitle()); - } - - @Test - @DisplayName("announce가 없는 경우 announcementTitle를 null로 반환합니다.") - void test2() throws Exception { - // given - Agenda agenda = agendaMockData.createOfficialAgenda(); - - // when - String response = mockMvc.perform(get("/agenda") - .header("Authorization", "Bearer " + accessToken) - .param("agenda_key", agenda.getAgendaKey().toString())) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); - - // then - assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); - assertThat(result.getAnnouncementTitle()).isEqualTo(null); - } - - @Test - @DisplayName("announce가 여러 개인 경우 가장 최근 작성된 announce를 반환합니다.") - void test3() throws Exception { - // given - Agenda agenda = agendaMockData.createOfficialAgenda(); - AgendaAnnouncement announcement1 = agendaMockData.createAgendaAnnouncement(agenda); - AgendaAnnouncement announcement2 = agendaMockData.createAgendaAnnouncement(agenda); - AgendaAnnouncement announcement3 = agendaMockData.createAgendaAnnouncement(agenda); - - // when - String response = mockMvc.perform(get("/agenda") - .header("Authorization", "Bearer " + accessToken) - .param("agenda_key", agenda.getAgendaKey().toString())) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); - - // then - assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); - assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement1.getTitle()); - assertThat(result.getAnnouncementTitle()).isNotEqualTo(announcement2.getTitle()); - assertThat(result.getAnnouncementTitle()).isEqualTo(announcement3.getTitle()); - } - } - - @Nested - @DisplayName("Agenda 현황 전체 조회") - class GetAgendaListCurrent { - - @Test - @DisplayName("Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") - void getAgendaListSuccess() throws Exception { - // given - List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.ON_GOING); - List nonOfficialAgendaList = agendaMockData - .createNonOfficialAgendaList(6, AgendaStatus.ON_GOING); - - // when - String response = mockMvc.perform(get("/agenda/list") - .header("Authorization", "Bearer " + accessToken)) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); - - // then - assertThat(result.length).isEqualTo(officialAgendaList.size() + nonOfficialAgendaList.size()); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); - if (i == 0 || i == officialAgendaList.size()) { - continue; - } - assertThat(result[i].getAgendaDeadLine()).isBefore(result[i - 1].getAgendaDeadLine()); - } - } - - @Test - @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") - void getAgendaListSuccessWithNoAgenda() throws Exception { - // given - agendaMockData.createOfficialAgendaList(3, AgendaStatus.CONFIRM); - agendaMockData.createNonOfficialAgendaList(6, AgendaStatus.CANCEL); - - // when - String response = mockMvc.perform(get("/agenda/list") - .header("Authorization", "Bearer " + accessToken)) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); - - // then - assertThat(result.length).isEqualTo(0); - } - } -} diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 32a8beea7..5a36f20c4 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -91,13 +91,12 @@ public class Agenda extends BaseTimeEntity { private Boolean isRanking; @Builder - public Agenda(Long id, UUID agendaKey, String title, String content, LocalDateTime deadline, - LocalDateTime startTime, - LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, int minPeople, int maxPeople, - String posterUri, String hostIntraId, Location location, AgendaStatus status, Boolean isOfficial, - Boolean isRanking) { + public Agenda(Long id, String title, String content, LocalDateTime deadline, + LocalDateTime startTime, LocalDateTime endTime, int minTeam, int maxTeam, int currentTeam, + int minPeople, int maxPeople, String posterUri, String hostIntraId, Location location, + AgendaStatus status, Boolean isOfficial, Boolean isRanking) { this.id = id; - this.agendaKey = agendaKey; + this.agendaKey = UUID.randomUUID(); this.title = title; this.content = content; this.deadline = deadline; diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 95f2870ce..cb256fe1a 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -194,12 +194,14 @@ public enum ErrorCode { // agenda AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), + AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), + AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), + AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), - TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), - AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."); + TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."); private final int status; private final String errCode; From a6698afa0d809bf4161e60ae5f5802bb0e2799cb Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:45:02 +0900 Subject: [PATCH 027/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#852=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20(#877)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 33 ++++++ .../response/AgendaProfileDetailsResDto.java | 29 +++++ .../service/AgendaProfileFindService.java | 45 ++++++++ .../AgendaProfileControllerTest.java | 100 ++++++++++++++++++ .../api/global/config/SwaggerConfig.java | 9 ++ .../java/gg/repo/agenda/TicketRepository.java | 3 + .../java/gg/utils/exception/ErrorCode.java | 3 +- 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java new file mode 100644 index 000000000..01d57068a --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -0,0 +1,33 @@ +package gg.agenda.api.user.agendaprofile.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/profile") +public class AgendaProfileController { + private final AgendaProfileFindService agendaProfileFindService; + + /** + * AgendaProfile 상세 조회 API + * + * @param user 로그인한 사용자 정보 + * @return AgendaProfileDetailsResDto 객체와 HTTP 상태 코드를 포함한 ResponseEntity + */ + @GetMapping + public ResponseEntity getMyAgendaProfile(@Login UserDto user) { + AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.getAgendaProfileDetails(user); + return ResponseEntity.status(HttpStatus.OK).body(agendaProfileDetails); + } +} + diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java new file mode 100644 index 000000000..18f8ce03a --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Coalition; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AgendaProfileDetailsResDto { + + private String userIntraId; + private String userContent; + private String userGithub; + private Coalition userCoalition; + private Location userLocation; + private int ticketCount; + + public AgendaProfileDetailsResDto(User user, AgendaProfile entity, int ticketCount) { + this.userIntraId = user.getIntraId(); + this.userContent = entity.getContent(); + this.userGithub = entity.getGithubUrl(); + this.userCoalition = entity.getCoalition(); + this.userLocation = entity.getLocation(); + this.ticketCount = ticketCount; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java new file mode 100644 index 000000000..0876f9f2e --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -0,0 +1,45 @@ +package gg.agenda.api.user.agendaprofile.service; + +import static gg.utils.exception.ErrorCode.*; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaProfile; +import gg.data.user.User; +import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.agenda.TicketRepository; +import gg.repo.user.UserRepository; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaProfileFindService { + + private final UserRepository userRepository; + private final AgendaProfileRepository agendaProfileRepository; + private final TicketRepository ticketRepository; + + /** + * AgendaProfile 상세 정보를 조회하는 메서드 + * + * @param user 로그인한 유저의 UserDto + * @return AgendaProfileDetailsResDto 객체 + */ + @Transactional(readOnly = true) + public AgendaProfileDetailsResDto getAgendaProfileDetails(@Login UserDto user) { + User loginUser = userRepository.getById(user.getId()); + + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApproveTrue(agendaProfile.getId()) + .size(); + + return new AgendaProfileDetailsResDto(loginUser, agendaProfile, ticketCount); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java new file mode 100644 index 000000000..9fb8af369 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -0,0 +1,100 @@ +package gg.agenda.api.user.agendaprofile; + +import static gg.data.agenda.type.Location.*; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.data.user.User; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaProfileControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private TestDataUtils testDataUtils; + @Autowired + private AgendaMockData agendaMockData; + User user; + String accessToken; + + @Nested + @DisplayName("agenda profile 상세 조회") + class GetAgendaProfile { + + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("로그인된 유저에 해당하는 Agenda profile를 상세 조회합니다.") + void test() throws Exception { + //given + AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + Ticket ticket = agendaMockData.createTicket(agendaProfile); + + // when + String response = mockMvc.perform(get("/agenda/profile") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + AgendaProfileDetailsResDto result = objectMapper.readValue(response, AgendaProfileDetailsResDto.class); + + // then + assertThat(result.getUserIntraId()).isEqualTo(user.getIntraId()); + assertThat(result.getUserContent()).isEqualTo(agendaProfile.getContent()); + assertThat(result.getUserGithub()).isEqualTo(agendaProfile.getGithubUrl()); + assertThat(result.getUserCoalition()).isEqualTo(agendaProfile.getCoalition()); + assertThat(result.getUserLocation()).isEqualTo(agendaProfile.getLocation()); + assertThat(result.getTicketCount()).isEqualTo(1); + } + + @Test + @DisplayName("로그인된 유저가 유효하지 않을 때") + void testInvalidUser() throws Exception { + // given: 유효하지 않은 유저의 액세스 토큰 + String invalidAccessToken = "invalid-access-token"; + + // when & then: 예외가 발생해야 함 + mockMvc.perform(get("/agenda/profile") + .header("Authorization", "Bearer " + invalidAccessToken)) + .andExpect(status().isUnauthorized()); + } + + @Test + @DisplayName("해당 로그인 유저의 아젠다 프로필이 없을 때") + void testAgendaProfileNotFound() throws Exception { + // given: 특정 유저와 관련된 AgendaProfile이 없음 + + // when & then: 예외가 발생해야 함 + mockMvc.perform(get("/agenda/profile") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + } +} diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java index e120dcb56..eff142567 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java @@ -14,6 +14,15 @@ @Configuration public class SwaggerConfig { + @Bean + public GroupedOpenApi agendaGroup() { + return GroupedOpenApi.builder() + .group("agenda") + .pathsToMatch("/agenda/**") + .packagesToScan("gg.agenda.api.user") + .build(); + } + @Bean public GroupedOpenApi partyGroup() { return GroupedOpenApi.builder() diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 6e9e972bd..4a70c2943 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -1,5 +1,6 @@ package gg.repo.agenda; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,4 +10,6 @@ public interface TicketRepository extends JpaRepository { Optional findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(AgendaProfile agendaProfile); + + List findByAgendaProfileIdAndIsUsedFalseAndIsApproveTrue(Long agendaProfileId); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index cb256fe1a..e12b64f43 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -201,7 +201,8 @@ public enum ErrorCode { LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), - TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."); + TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), + AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."); private final int status; private final String errCode; From 4ba07964e914c4385f6ba3659065141c86749a47 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:53:06 +0900 Subject: [PATCH 028/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#872=20Auth=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=AC=20=EB=95=8C=20AgendaProfile=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#876)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../controller/AgendaTeamController.java | 4 +- .../controller/request/TeamCreateReqDto.java | 18 ++- .../agendateam/service/AgendaTeamService.java | 16 +-- .../java/gg/agenda/api/AgendaMockData.java | 48 +++++++ .../agendateam/AgendaTeamControllerTest.java | 122 ++++++++++++------ .../java/gg/data/agenda/AgendaProfile.java | 2 +- .../gg/data/agenda/AgendaTeamProfile.java | 13 ++ .../src/main/java/gg/data/agenda/Ticket.java | 4 + .../java/gg/data/agenda/type/Coalition.java | 13 +- .../java/gg/data/agenda/type/Location.java | 10 ++ .../global/security/info/OAuthUserInfo.java | 5 + .../info/impl/FortyTwoOAuthUserInfo.java | 21 +++ .../info/impl/KakaoOAuthUserInfo.java | 13 ++ .../service/CustomOAuth2UserService.java | 51 ++++++++ .../api/global/utils/external/ApiUtil.java | 9 ++ .../resources/db/migration/V3__agenda.sql | 2 +- 17 files changed, 298 insertions(+), 54 deletions(-) diff --git a/build.gradle b/build.gradle index e7bff3c38..64fa269c5 100644 --- a/build.gradle +++ b/build.gradle @@ -107,6 +107,7 @@ subprojects { '*Application*', "**/config/*", "**/security/*", + "**/external/*", "**/dto/*", "**/aws/*", "*NotiMailSender*", diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 320dd832f..9bb6555d1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -2,6 +2,8 @@ import java.util.UUID; +import javax.validation.Valid; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -31,7 +33,7 @@ public class AgendaTeamController { */ @PostMapping public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Login UserDto user, - @RequestBody TeamCreateReqDto teamCreateReqDto, @RequestParam("agenda_key") UUID agendaKey) { + @RequestBody @Valid TeamCreateReqDto teamCreateReqDto, @RequestParam("agenda_key") UUID agendaKey) { TeamCreateResDto teamCreateResDto = agendaTeamService.addAgendaTeam(user, teamCreateReqDto, agendaKey); return ResponseEntity.status(HttpStatus.CREATED).body(teamCreateResDto); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java index c7e61a348..6a083e8c7 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java @@ -4,6 +4,10 @@ import java.util.UUID; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.Location; @@ -13,9 +17,19 @@ @Getter @AllArgsConstructor public class TeamCreateReqDto { + @NotBlank + @Size(max = 30) private String teamName; - private boolean teamIsPrivate; + + @NotNull + private Boolean teamIsPrivate; + + @NotBlank + @Size(max = 10) private String teamLocation; + + @NotBlank + @Size(max = 500) private String teamContent; public static AgendaTeam toEntity(TeamCreateReqDto teamCreateReqDto, Agenda agenda, String intraId) { @@ -30,7 +44,7 @@ public static AgendaTeam toEntity(TeamCreateReqDto teamCreateReqDto, Agenda agen .mateCount(1) .award("award") .awardPriority(1) - .isPrivate(teamCreateReqDto.isTeamIsPrivate()) + .isPrivate(teamCreateReqDto.getTeamIsPrivate()) .build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index f82bc969b..64e227b4a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -15,6 +15,8 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; +import gg.data.agenda.Ticket; import gg.data.agenda.type.Location; import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; @@ -64,14 +66,9 @@ public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateR }); if (agenda.getIsOfficial()) { - ticketRepository.findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(agendaProfile) + Ticket ticket = ticketRepository.findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(agendaProfile) .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); - } - - try { - Location.valueOf(teamCreateReqDto.getTeamLocation().toUpperCase()); - } catch (IllegalArgumentException e) { - throw new BusinessException(LOCATION_NOT_VALID); + ticket.useTicket(); } agendaTeamRepository.findByAgendaAndTeamNameAndStatus(agenda, teamCreateReqDto.getTeamName(), OPEN, CONFIRM) @@ -80,9 +77,10 @@ public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateR }); AgendaTeam agendaTeam = TeamCreateReqDto.toEntity(teamCreateReqDto, agenda, user.getIntraId()); - agendaTeamRepository.save(agendaTeam); + AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(agendaTeam, agendaProfile); agendaRepository.save(agenda); - + agendaTeamRepository.save(agendaTeam); + agendaTeamProfileRepository.save(agendaTeamProfile); return new TeamCreateResDto(agendaTeam.getTeamKey().toString()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index c06a925e6..75ee2d80e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,6 +1,7 @@ package gg.agenda.api; import static gg.data.agenda.type.AgendaStatus.*; +import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Coalition.*; import static gg.data.agenda.type.Location.*; import static java.util.UUID.*; @@ -18,6 +19,8 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; @@ -26,6 +29,7 @@ import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.repo.agenda.AgendaTeamRepository; import gg.repo.agenda.TicketRepository; import lombok.RequiredArgsConstructor; @@ -36,6 +40,7 @@ public class AgendaMockData { private final EntityManager em; private final TicketRepository ticketRepository; private final AgendaRepository agendaRepository; + private final AgendaTeamRepository agendaTeamRepository; private final AgendaProfileRepository agendaProfileRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaAnnouncementRepository agendaAnnouncementRepository; @@ -274,4 +279,47 @@ public Ticket createTicket(AgendaProfile agendaProfile) { .build(); return ticketRepository.save(ticket); } + + public AgendaTeam createAgendaTeam(Agenda agenda) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId("leaderIntraId") + .status(OPEN) + .location(SEOUL) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(SEOUL) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile agendaProfile) { + AgendaTeamProfile agendaTeamProfile = AgendaTeamProfile.builder() + .agendaTeam(agendaTeam) + .profile(agendaProfile) + .isExist(true) + .build(); + return agendaTeamProfileRepository.save(agendaTeamProfile); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index f85fc6078..eaea6417d 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -25,6 +25,7 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.data.agenda.type.AgendaStatus; import gg.data.user.User; @@ -46,24 +47,24 @@ public class AgendaTeamControllerTest { private AgendaMockData agendaMockData; @Autowired AgendaTeamRepository agendaTeamRepository; - User user; - User anotherUser; - String accessToken; - String anotherAccessToken; - AgendaProfile agendaProfile; - AgendaProfile anotherAgendaProfile; + User seoulUser; + User gyeongsanUser; + String seoulUserAccessToken; + String gyeongsanUserAccessToken; + AgendaProfile seoulUserAgendaProfile; + AgendaProfile gyeongsanUserAgendaProfile; @Nested @DisplayName("팀 생성 테스트") class AddTeamTest { @BeforeEach void beforeEach() { - user = testDataUtils.createNewUser(); - accessToken = testDataUtils.getLoginAccessTokenFromUser(user); - agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); - anotherUser = testDataUtils.createNewUser(); - anotherAccessToken = testDataUtils.getLoginAccessTokenFromUser(anotherUser); - anotherAgendaProfile = agendaMockData.createAgendaProfile(anotherUser, GYEONGSAN); + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); } @Test @@ -71,14 +72,14 @@ void beforeEach() { public void addNewTeamStatusSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -99,14 +100,14 @@ public void addNewTeamStatusSeoul() throws Exception { public void addNewTeamStatusGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -127,14 +128,14 @@ public void addNewTeamStatusGyeongsan() throws Exception { public void addNewTeamStatusMixFromSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -155,14 +156,14 @@ public void addNewTeamStatusMixFromSeoul() throws Exception { public void addNewTeamStatusMixFromGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -183,14 +184,14 @@ public void addNewTeamStatusMixFromGyeongsan() throws Exception { public void addNewTeamStatusMixFromMixToSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -211,14 +212,14 @@ public void addNewTeamStatusMixFromMixToSeoul() throws Exception { public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", "teamContent"); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -239,14 +240,14 @@ public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -259,14 +260,14 @@ public void noAgendaFail() throws Exception { public void notValidAgendaLocation() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -279,14 +280,14 @@ public void notValidAgendaLocation() throws Exception { public void notValidAgendaStatus() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -299,14 +300,14 @@ public void notValidAgendaStatus() throws Exception { public void notValidAgendaTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(5); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -319,14 +320,14 @@ public void notValidAgendaTeam() throws Exception { public void notValidAgendaDeadline() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -338,15 +339,15 @@ public void notValidAgendaDeadline() throws Exception { @DisplayName("403 아젠다 호스트의 팀 생성으로 인한 실패") public void agendaHostFail() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); - Ticket ticket = agendaMockData.createTicket(agendaProfile); + Agenda agenda = agendaMockData.createAgenda(seoulUser.getIntraId()); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -359,19 +360,62 @@ public void agendaHostFail() throws Exception { public void notValidUserLocation() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(anotherAgendaProfile); + Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( post("/agenda/team") - .header("Authorization", "Bearer " + anotherAccessToken) + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andReturn().getResponse().getContentAsString(); } + + @Test + @DisplayName("409 이미 있는 팀 이름으로 인한 실패") + public void alreadyTeamNameExist() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda); + TeamCreateReqDto req = new TeamCreateReqDto(team.getName(), true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("409 한 agenda에 여러 팀 참가 및 생성 불가로 인한 실패") + public void alreadyTeamExistForAgenda() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser); + AgendaTeamProfile agendaTeamProfile = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamCreateReqDto req = new TeamCreateReqDto("newName", true, "SEOUL", + "teamContent"); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + post("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()) + .andReturn().getResponse().getContentAsString(); + } } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index f14444628..f4f8fdf2f 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -30,7 +30,7 @@ public class AgendaProfile extends BaseTimeEntity { @Column(name = "content", length = 1000, nullable = false) private String content; - @Column(name = "github_url", length = 255, nullable = false) + @Column(name = "github_url", length = 255, nullable = true) private String githubUrl; @Column(name = "coalition", length = 30, nullable = false) diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index bf856ad42..219c1f127 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -33,4 +33,17 @@ public class AgendaTeamProfile extends BaseTimeEntity { @Column(name = "is_exist", nullable = false, columnDefinition = "BIT(1)") private Boolean isExist; + + @Builder + public AgendaTeamProfile(AgendaProfile profile, AgendaTeam agendaTeam, Boolean isExist) { + this.profile = profile; + this.agendaTeam = agendaTeam; + this.isExist = isExist; + } + + public AgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile profile) { + this.agendaTeam = agendaTeam; + this.profile = profile; + this.isExist = true; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index 41e7b00cb..777ed2d0c 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -42,5 +42,9 @@ public Ticket(AgendaProfile agendaProfile, Boolean isUsed, Boolean isApprove) { this.isUsed = isUsed; this.isApprove = isApprove; } + + public void useTicket() { + this.isUsed = true; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/type/Coalition.java b/gg-data/src/main/java/gg/data/agenda/type/Coalition.java index 4e5dc7b15..17898e3c8 100644 --- a/gg-data/src/main/java/gg/data/agenda/type/Coalition.java +++ b/gg-data/src/main/java/gg/data/agenda/type/Coalition.java @@ -13,7 +13,18 @@ public enum Coalition { SPRING("SPRING"), SUMMER("SUMMER"), AUTUMN("AUTUMN"), - WINTER("WINTER"); + WINTER("WINTER"), + OTHER("OTHER"); private String coalition; + + public static Coalition valueOfCoalition(String coalition) { + String coalitionToUpper = coalition.toUpperCase(); + for (Coalition c : values()) { + if (c.coalition.equals(coalitionToUpper)) { + return c; + } + } + return OTHER; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/type/Location.java b/gg-data/src/main/java/gg/data/agenda/type/Location.java index d36b3f65b..eaeba1843 100644 --- a/gg-data/src/main/java/gg/data/agenda/type/Location.java +++ b/gg-data/src/main/java/gg/data/agenda/type/Location.java @@ -11,4 +11,14 @@ public enum Location { MIX("MIX"); private String location; + + public static Location valueOfLocation(String location) { + String locationToUpper = location.toUpperCase(); + for (Location l : values()) { + if (l.location.equals(locationToUpper)) { + return l; + } + } + return MIX; + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/OAuthUserInfo.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/OAuthUserInfo.java index b1719d5f8..2f6922f71 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/OAuthUserInfo.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/OAuthUserInfo.java @@ -2,6 +2,7 @@ import java.util.Map; +import gg.data.agenda.type.Location; import gg.data.user.type.RoleType; public abstract class OAuthUserInfo { @@ -24,4 +25,8 @@ public Map getAttributes() { public abstract RoleType getRoleType(); public abstract Long getKakaoId(); + + public abstract String getUserId(); + + public abstract Location getLocation(); } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/FortyTwoOAuthUserInfo.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/FortyTwoOAuthUserInfo.java index 1e59b3de1..76c9fe8d8 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/FortyTwoOAuthUserInfo.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/FortyTwoOAuthUserInfo.java @@ -1,9 +1,13 @@ package gg.pingpong.api.global.security.info.impl; +import static gg.data.agenda.type.Location.*; + +import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Value; +import gg.data.agenda.type.Location; import gg.data.user.type.RoleType; import gg.pingpong.api.global.security.info.OAuthUserInfo; @@ -45,4 +49,21 @@ public RoleType getRoleType() { public Long getKakaoId() { return null; } + + @Override + public String getUserId() { + return attributes.get("id").toString(); + } + + @Override + public Location getLocation() { + List> campuses = (List>)attributes.get("campus"); + if (campuses != null && !campuses.isEmpty()) { + Map campus = campuses.get(0); + String campusName = (String)campus.get("city"); + return Location.valueOfLocation(campusName); + } else { + return MIX; + } + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/KakaoOAuthUserInfo.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/KakaoOAuthUserInfo.java index b28bea0cf..a36ee6af4 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/KakaoOAuthUserInfo.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/info/impl/KakaoOAuthUserInfo.java @@ -1,9 +1,12 @@ package gg.pingpong.api.global.security.info.impl; +import static gg.data.agenda.type.Location.*; + import java.util.Map; import org.springframework.beans.factory.annotation.Value; +import gg.data.agenda.type.Location; import gg.data.user.type.RoleType; import gg.pingpong.api.global.security.info.OAuthUserInfo; @@ -45,4 +48,14 @@ public RoleType getRoleType() { public Long getKakaoId() { return (Long)attributes.get("id"); } + + @Override + public String getUserId() { + return "OTHER"; + } + + @Override + public Location getLocation() { + return MIX; + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java index 288d049d4..5fcc50a3c 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java @@ -1,8 +1,15 @@ package gg.pingpong.api.global.security.service; +import static gg.data.agenda.type.Coalition.*; + import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -11,7 +18,10 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Coalition; import gg.data.pingpong.rank.Rank; import gg.data.pingpong.rank.Tier; import gg.data.pingpong.rank.redis.RankRedis; @@ -23,6 +33,8 @@ import gg.pingpong.api.global.security.info.OAuthUserInfoFactory; import gg.pingpong.api.global.security.info.ProviderType; import gg.pingpong.api.global.utils.aws.AsyncNewUserImageUploader; +import gg.pingpong.api.global.utils.external.ApiUtil; +import gg.repo.agenda.AgendaProfileRepository; import gg.repo.rank.RankRepository; import gg.repo.rank.TierRepository; import gg.repo.rank.redis.RankRedisRepository; @@ -36,15 +48,21 @@ @RequiredArgsConstructor @Transactional public class CustomOAuth2UserService extends DefaultOAuth2UserService { + private final ApiUtil apiUtil; private final UserRepository userRepository; private final AsyncNewUserImageUploader asyncNewUserImageUploader; private final RankRepository rankRepository; private final SeasonRepository seasonRepository; private final RankRedisRepository rankRedisRepository; private final TierRepository tierRepository; + private final AgendaProfileRepository agendaProfileRepository; @Value("${info.image.defaultUrl}") private String defaultImageUrl; + @Value("https://api.intra.42.fr/v2/users/{id}/coalitions") + private String coalitionUrl; + + private RestTemplate restTemplate; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { @@ -81,6 +99,10 @@ private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) { asyncNewUserImageUploader.upload(userInfo.getIntraId(), userInfo.getImageUrl()); } } + if (agendaProfileRepository.findByUserId(savedUser.getId()).isEmpty()) { + String token = userRequest.getAccessToken().getTokenValue(); + createProfile(userInfo, savedUser, token); + } return UserPrincipal.create(savedUser, user.getAttributes()); } @@ -110,4 +132,33 @@ private User createUser(OAuthUserInfo userInfo) { .build(); return userRepository.saveAndFlush(user); } + + private void createProfile(OAuthUserInfo userInfo, User user, String accessToken) { + AgendaProfile agendaProfile = AgendaProfile.builder() + .userId(user.getId()) + .content("안녕하세요! " + userInfo.getIntraId() + "입니다.") + .githubUrl(null) + .coalition(findCoalition(userInfo.getUserId(), accessToken)) + .location(userInfo.getLocation()) + .build(); + agendaProfileRepository.save(agendaProfile); + } + + private Coalition findCoalition(String id, String accessToken) { + String url = coalitionUrl.replace("{id}", id); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + accessToken); + headers.setContentType(MediaType.APPLICATION_JSON); + + // HttpEntity 객체를 생성하여 헤더를 포함한 요청을 보냄 + List> response = apiUtil.apiCall(url, List.class, headers, HttpMethod.GET); + + if (response != null && !response.isEmpty()) { + Map coalition = response.get(0); + String coalitionName = (String)coalition.get("name"); + return Coalition.valueOfCoalition(coalitionName); + } else { + return OTHER; + } + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java index ef30fb58d..2947aaff3 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java @@ -49,4 +49,13 @@ public T apiCall(String url, Class responseType, HttpHeaders headers, } return res.getBody(); } + + public T apiCall(String url, Class responseType, HttpHeaders headers, HttpMethod method) { + HttpEntity request = new HttpEntity<>(headers); + ResponseEntity res = restTemplate.exchange(url, method, request, responseType); + if (!res.getStatusCode().is2xxSuccessful()) { + throw new RuntimeException("api call error"); + } + return res.getBody(); + } } diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index a7bebe7c8..72e0db598 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -65,7 +65,7 @@ CREATE TABLE `agenda_profile` `id` BIGINT NOT NULL AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `content` VARCHAR(1000) NOT NULL, - `github_url` VARCHAR(255) NOT NULL, + `github_url` VARCHAR(255) NULL, `coalition` VARCHAR(30) NOT NULL, `location` VARCHAR(30) NOT NULL, `created_at` DATETIME NOT NULL, From 4fe7b4ef4e09bcc52f0852c46aa8612179358809 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:23:14 +0900 Subject: [PATCH 029/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#860=20?= =?UTF-8?q?=EC=A7=80=EB=82=9C=20Agenda=20=EC=A1=B0=ED=9A=8C=20API=20(#878)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 15 ++ .../user/agenda/service/AgendaService.java | 11 ++ .../java/gg/agenda/api/AgendaMockData.java | 25 +++ .../controller/AgendaControllerTest.java | 159 ++++++++++++++++++ .../agenda/service/AgendaServiceTest.java | 39 +++++ .../java/gg/repo/agenda/AgendaRepository.java | 5 + 6 files changed, 254 insertions(+) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 84d858f6a..191e2afa3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -5,6 +5,9 @@ import javax.validation.Valid; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -21,6 +24,7 @@ import gg.agenda.api.user.agenda.service.AgendaService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.utils.dto.PageRequestDto; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; @@ -60,4 +64,15 @@ public ResponseEntity agendaAdd(@Login UserDto user, AgendaKeyResponseDto agendaKey = agendaService.addAgenda(agendaCreateDto, user); return ResponseEntity.status(HttpStatus.CREATED).body(agendaKey); } + + @ApiResponse(responseCode = "200", description = "지난 Agenda 목록 조회 성공") + @GetMapping("/history") + public ResponseEntity> agendaListHistory( + @RequestBody @Valid PageRequestDto pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); + List agendaList = agendaService.findHistoryAgendaList(pageable); + return ResponseEntity.ok(agendaList); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index fb5ca11a7..4e62bdb60 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -7,6 +7,9 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +23,7 @@ import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; +import gg.utils.dto.PageRequestDto; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @@ -55,4 +59,11 @@ public AgendaKeyResponseDto addAgenda(AgendaCreateDto agendaCreateDto, UserDto u Agenda savedAgenda = agendaRepository.save(newAgenda); return AgendaKeyResponseDto.builder().agendaKey(savedAgenda.getAgendaKey()).build(); } + + @Transactional(readOnly = true) + public List findHistoryAgendaList(Pageable pageable) { + return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.CONFIRM).getContent().stream() + .map(AgendaSimpleResponseDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 75ee2d80e..d2c615ece 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,6 +1,7 @@ package gg.agenda.api; import static gg.data.agenda.type.AgendaStatus.*; +import static gg.data.agenda.type.AgendaStatus.CONFIRM; import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Coalition.*; import static gg.data.agenda.type.Location.*; @@ -150,6 +151,30 @@ public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { return announcement; } + public List createAgendaHistory(int size) { + List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().minusDays(i + 6)) + .startTime(LocalDateTime.now().minusDays(i + 4)) + .endTime(LocalDateTime.now().minusDays(i + 2)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(CONFIRM) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build() + ) + .collect(Collectors.toList()); + return agendaRepository.saveAll(agendas); + } + public Agenda createAgenda() { Agenda agenda = Agenda.builder() .title("title") diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 51d5bea78..ef3b950db 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.core.JsonProcessingException; @@ -38,6 +39,7 @@ import gg.repo.agenda.AgendaRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -398,4 +400,161 @@ void createAgendaFailedWhenNegativeMinPeople(int value) throws Exception { .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("Agenda 지난 목록 조회") + class GetAgendaListHistory { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4}) + @DisplayName("지난 Agenda 목록을 조회합니다.") + void getAgendaListHistorySuccess(int page) throws Exception { + // given + int totalCount = 35; + int size = 10; + List agendaHistory = agendaMockData.createAgendaHistory(totalCount); + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(size * page < totalCount ? size : totalCount % size); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getAgendaTitle()).isEqualTo(agendaHistory.get(size * (page - 1) + i).getTitle()); + if (i == 0) { + continue; + } + assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + } + } + + @Test + @DisplayName("지난 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListHistoryWithNoContent() throws Exception { + // given + int page = 1; + int size = 10; + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(0); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1}) + @DisplayName("page가 1보다 작은 경우 400을 반환합니다.") + void getAgendaListHistoryWithInvalidPage(int page) throws Exception { + // given + int size = 10; + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // expected + mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("page가 null인 경우 400을 반환합니다.") + void getAgendaListHistoryWithoutPage() throws Exception { + // given + int size = 10; + PageRequestDto pageRequestDto = new PageRequestDto(null, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // expected + mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @ValueSource(ints = {5, 6, 7, 8}) + @DisplayName("page가 실제 페이지 수보다 큰 경우 빈 리스트를 반환합니다.") + void getAgendaListHistoryWithExcessPage(int page) throws Exception { + // given + int totalCount = 35; + int size = 10; + List agendaHistory = agendaMockData.createAgendaHistory(totalCount); + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(0); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1, 31}) + @DisplayName("size가 1 미만, 30 초과인 경우 400을 반환합니다.") + void getAgendaListHistoryWithInvalidSize(int size) throws Exception { + // given + int page = 1; + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // expected + mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("size가 null인 경우 size=20으로 조회합니다.") + void getAgendaListHistoryWithoutSize() throws Exception { + // given + int page = 1; + List agendaHistory = agendaMockData.createAgendaHistory(30); + PageRequestDto pageRequestDto = new PageRequestDto(page, null); + String req = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/agenda/history") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(req)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + + // then + assertThat(result.length).isEqualTo(20); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getAgendaTitle()).isEqualTo(agendaHistory.get(i).getTitle()); + if (i == 0) { + continue; + } + assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + } + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index dfda9ba05..8e6fbe33f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -16,6 +16,11 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; @@ -152,4 +157,38 @@ void createAgendaSuccess() { assertThat(agendaKeyResponseDto.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); } } + + @Nested + @DisplayName("지난 Agenda 조회") + class GetAgendaListHistory { + + @Test + @DisplayName("지난 Agenda 조회 성공") + void getAgendaListHistorySuccess() { + // given + int page = 1; + int size = 10; + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); + List agendas = new ArrayList<>(); + IntStream.range(0, size * 2).forEach(i -> agendas.add(Agenda.builder() + .startTime(LocalDateTime.now().minusDays(i)) + .build() + )); + Page agendaPage = new PageImpl<>(agendas.subList(0, 10), pageable, size); + when(agendaRepository.findAllByStatusIs(any(Pageable.class), eq(AgendaStatus.CONFIRM))) + .thenReturn(agendaPage); + + // when + List result = agendaService.findHistoryAgendaList(pageable); + + // then + verify(agendaRepository, times(1)) + .findAllByStatusIs(pageable, AgendaStatus.CONFIRM); + assertThat(result.size()).isEqualTo(size); + for (int i = 1; i < result.size(); i++) { + assertThat(result.get(i).getAgendaStartTime()) + .isBefore(result.get(i - 1).getAgendaStartTime()); + } + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index d5c10e208..7057fdb0d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -4,6 +4,9 @@ import java.util.Optional; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -16,4 +19,6 @@ public interface AgendaRepository extends JpaRepository { @Query("SELECT a FROM Agenda a WHERE a.status = :status") List findAllByStatusIs(AgendaStatus status); + + Page findAllByStatusIs(Pageable pageable, AgendaStatus status); } From 163001a39c314784d423bb38ce480749c9a6954f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B8=B0=ED=98=84=28gkwon=29?= <79272189+ghyen@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:07:30 +0900 Subject: [PATCH 030/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20#880=206?= =?UTF-8?q?th=20README=20update=20(#881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 492e93961..98b8f9924 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 42arcade.gg.server.v2 -https://42gg.kr/ +https://gg.42seoul.kr/ ## ⚡️ 프로젝트 소개 @@ -39,6 +39,8 @@ https://42gg.kr/ - 5기 : 2023.11.01 ~ 2024.01.31 +- 6기 : 2023.02.01 ~ 2024.05.10 + ## ⚡️ 프로젝트 아키텍처 ![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) @@ -229,6 +231,54 @@ https://42gg.kr/ +### 6기 +
+ 6기 진행 사항 +
+ +### ⚡️⚡ 파티 서비스 개발 +42party + + +### ⚡️⚡ 테스트 커버리지 개선 (2024-04-16 기준) +### 전체 74% -> 75.9% +![integrationTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/79731062-a8f4-4575-a683-61fa5dd60a15) + + +### 단위 테스트 30% -> 36.7% +![unitTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/b0e5055b-9008-40d8-b93a-3b05fdffc710) + + +### ⚡️⚡ DB table 구조 변경 +![image](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/c9c47670-b955-4e34-a589-c498008446f0) + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
🏓🏓🏓🏓
권기현 @ghyen정승수 @AreSain김정주 @JayJay-Kay 이예슬 @yes-ee
파티 서비스 개발,
테스트 커버리지 개선
팀장, 파티 서비스 개발,
테스트 커버리지 개선
파티 서비스 개발,
테스트 커버리지 개선
파티 서비스 개발,
테스트 커버리지 개선
+ ## ⚡️ 필요 파일
application.yml From 3acd65c01fb8c85aad96df3a55e332545e9eba66 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 12 Jul 2024 19:30:20 +0900 Subject: [PATCH 031/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#841=20Team=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20API=20(#879)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 15 +++ .../controller/request/TeamCreateReqDto.java | 11 +- .../controller/request/TeamDetailsReqDto.java | 19 +++ .../controller/response/TeamCreateResDto.java | 8 +- .../response/TeamDetailsResDto.java | 33 +++++ .../controller/response/TeamMateDto.java | 18 +++ .../agendateam/service/AgendaTeamService.java | 29 ++++ .../java/gg/agenda/api/AgendaMockData.java | 54 ++++++++ .../agendateam/AgendaTeamControllerTest.java | 124 ++++++++++++++++++ .../java/gg/data/agenda/AgendaProfile.java | 7 +- .../service/CustomOAuth2UserService.java | 1 + .../resources/db/migration/V3__agenda.sql | 1 + .../agenda/AgendaTeamProfileRepository.java | 4 + .../gg/repo/agenda/AgendaTeamRepository.java | 6 + .../java/gg/utils/exception/ErrorCode.java | 1 + 15 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamMateDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 9bb6555d1..31d4bbc60 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -6,6 +6,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,7 +14,9 @@ import org.springframework.web.bind.annotation.RestController; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; @@ -26,6 +29,18 @@ public class AgendaTeamController { private final AgendaTeamService agendaTeamService; + /** + * 아젠다 팀 상세 정보 조회 + * @param user 사용자 정보, teamDetailsReqDto 팀 상세 정보 요청 정보, agendaId 아젠다 아이디 + * @return 팀 상세 정보 + */ + @GetMapping + public ResponseEntity agendaTeamDetails(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid TeamDetailsReqDto teamDetailsReqDto, @RequestParam("agenda_key") UUID agendaKey) { + TeamDetailsResDto teamDetailsResDto = agendaTeamService.detailsAgendaTeam(user, agendaKey, teamDetailsReqDto); + return ResponseEntity.ok(teamDetailsResDto); + } + /** * 아젠다 팀 생성하기 * @param user 사용자 정보, teamCreateReqDto 팀 생성 요청 정보, agendaId 아젠다 아이디 diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java index 6a083e8c7..10c46affc 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java @@ -11,11 +11,10 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.Location; -import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; @Getter -@AllArgsConstructor public class TeamCreateReqDto { @NotBlank @Size(max = 30) @@ -32,6 +31,14 @@ public class TeamCreateReqDto { @Size(max = 500) private String teamContent; + @Builder + public TeamCreateReqDto(String teamName, Boolean teamIsPrivate, String teamLocation, String teamContent) { + this.teamName = teamName; + this.teamIsPrivate = teamIsPrivate; + this.teamLocation = teamLocation; + this.teamContent = teamContent; + } + public static AgendaTeam toEntity(TeamCreateReqDto teamCreateReqDto, Agenda agenda, String intraId) { return AgendaTeam.builder() .agenda(agenda) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java new file mode 100644 index 000000000..8c007447d --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java @@ -0,0 +1,19 @@ +package gg.agenda.api.user.agendateam.controller.request; + +import java.util.UUID; + +import javax.validation.constraints.NotNull; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TeamDetailsReqDto { + @NotNull + private UUID teamKey; + + public TeamDetailsReqDto(UUID teamKey) { + this.teamKey = teamKey; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java index 7d7ddf678..29a18ade3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java @@ -1,12 +1,14 @@ package gg.agenda.api.user.agendateam.controller.response; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor -@AllArgsConstructor +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class TeamCreateResDto { String teamKey; + + public TeamCreateResDto(String teamKey) { + this.teamKey = teamKey; + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java new file mode 100644 index 000000000..84953ea22 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java @@ -0,0 +1,33 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import java.util.List; +import java.util.stream.Collectors; + +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TeamDetailsResDto { + private String teamName; + private String teamLeaderIntraId; + private AgendaTeamStatus teamStatus; + private Location teamLocation; + private String teamContent; + private List teamMates; + + public TeamDetailsResDto(AgendaTeam agendaTeam, List agendaTeamProfileList) { + this.teamName = agendaTeam.getName(); + this.teamLeaderIntraId = agendaTeam.getLeaderIntraId(); + this.teamStatus = agendaTeam.getStatus(); + this.teamLocation = agendaTeam.getLocation(); + this.teamContent = agendaTeam.getContent(); + this.teamMates = agendaTeamProfileList.stream() + .map(TeamMateDto::new) + .collect(Collectors.toList()); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamMateDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamMateDto.java new file mode 100644 index 000000000..3a0eb667b --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamMateDto.java @@ -0,0 +1,18 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import gg.data.agenda.AgendaTeamProfile; +import gg.data.agenda.type.Coalition; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class TeamMateDto { + private String intraId; + private Coalition coalition; + + public TeamMateDto(AgendaTeamProfile agendaTeamProfile) { + this.intraId = agendaTeamProfile.getProfile().getIntraId(); + this.coalition = agendaTeamProfile.getProfile().getCoalition(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 64e227b4a..fb55cda84 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -4,13 +4,16 @@ import static gg.utils.exception.ErrorCode.*; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -38,6 +41,32 @@ public class AgendaTeamService { private final AgendaProfileRepository agendaProfileRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; + /** + * 아젠다 팀 상세 정보 조회 + * @param user 사용자 정보, teamCreateReqDto 팀 키, agendaKey 아젠다 키 + * @return 만들어진 팀 상세 정보 + */ + @Transactional(readOnly = true) + public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamDetailsReqDto teamDetailsReqDto) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndTeamKeyAndStatus(agenda, teamDetailsReqDto.getTeamKey(), OPEN, CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + + List agendaTeamProfileList = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam); + + if (agendaTeam.getStatus().equals(CONFIRM)) { // 팀이 확정 상태인 경우에 + if (agendaTeamProfileList.stream() // 팀에 속한 유저가 아닌 경우 + .noneMatch(profile -> profile.getProfile().getUserId().equals(user.getId()))) { + throw new ForbiddenException(TEAM_FORBIDDEN); // 조회 불가 + } + } + return new TeamDetailsResDto(agendaTeam, agendaTeamProfileList); + } + /** * 아젠다 팀 생성하기 * @param user 사용자 정보, teamCreateReqDto 팀 생성 요청 정보, agendaId 아젠다 아이디 diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index d2c615ece..6c6c9dcb2 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -24,6 +24,7 @@ import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.data.user.User; import gg.repo.agenda.AgendaAnnouncementRepository; @@ -291,6 +292,7 @@ public AgendaProfile createAgendaProfile(User user, Location location) { .githubUrl("githubUrl") .coalition(LEE) .location(location) + .intraId(user.getIntraId()) .userId(user.getId()) .build(); return agendaProfileRepository.save(agendaProfile); @@ -339,6 +341,58 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user) { return agendaTeamRepository.save(agendaTeam); } + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(location) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, AgendaTeamStatus status) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(status) + .location(location) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, AgendaTeamStatus status, + Boolean isPrivate) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(status) + .location(location) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(isPrivate) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile agendaProfile) { AgendaTeamProfile agendaTeamProfile = AgendaTeamProfile.builder() .agendaTeam(agendaTeam) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index eaea6417d..0d95b7d64 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -21,13 +21,16 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.data.user.User; import gg.repo.agenda.AgendaTeamRepository; import gg.utils.TestDataUtils; @@ -418,4 +421,125 @@ public void alreadyTeamExistForAgenda() throws Exception { .andReturn().getResponse().getContentAsString(); } } + + @Nested + @DisplayName("팀 상세 정보 조회 테스트") + class AgendaTeamDetails { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 팀 상세 정보 조회 성공") + public void teamDetailsGetSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + agendaMockData.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); + TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + get("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + TeamDetailsResDto result = objectMapper.readValue(res, TeamDetailsResDto.class); + // then + assertThat(result.getTeamName()).isEqualTo(team.getName()); + assertThat(result.getTeamLeaderIntraId()).isEqualTo(seoulUser.getIntraId()); + assertThat(result.getTeamStatus()).isEqualTo(team.getStatus()); + assertThat(result.getTeamLocation()).isEqualTo(team.getLocation()); + assertThat(result.getTeamContent()).isEqualTo(team.getContent()); + assertThat(result.getTeamMates().get(0).getIntraId()).isEqualTo(seoulUser.getIntraId()); + assertThat(result.getTeamMates().get(0).getCoalition()).isEqualTo(seoulUserAgendaProfile.getCoalition()); + assertThat(result.getTeamMates().get(1).getIntraId()).isEqualTo(gyeongsanUser.getIntraId()); + assertThat(result.getTeamMates().get(1).getCoalition()).isEqualTo( + gyeongsanUserAgendaProfile.getCoalition()); + } + + @Test + @DisplayName("404 agenda가 없음으로 인한 팀 상세 정보 조회 실패") + public void teamDetailsGetFailByNoAgenda() throws Exception { + //given + TeamDetailsReqDto req = new TeamDetailsReqDto(UUID.randomUUID()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + get("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", UUID.randomUUID().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("404 team이 없음으로 인한 팀 상세 정보 조회 실패") + public void teamDetailsGetFailByNoTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(MIX); + TeamDetailsReqDto req = new TeamDetailsReqDto(UUID.randomUUID()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + get("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") + public void teamDetailsGetFailByConfirmTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); + TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + get("/agenda/team") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("404 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") + public void teamDetailsGetFailByCancelTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + get("/agenda/team") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index f4f8fdf2f..68cf619d8 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -41,15 +41,20 @@ public class AgendaProfile extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Location location; + @Column(name = "intra_id", length = 30, nullable = false) + private String intraId; + @Column(name = "user_id", nullable = false, columnDefinition = "BIGINT") private Long userId; @Builder - public AgendaProfile(String content, String githubUrl, Coalition coalition, Location location, Long userId) { + public AgendaProfile(String content, String githubUrl, Coalition coalition, Location location, String intraId, + Long userId) { this.content = content; this.githubUrl = githubUrl; this.coalition = coalition; this.location = location; + this.intraId = intraId; this.userId = userId; } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java index 5fcc50a3c..70a32d952 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java @@ -136,6 +136,7 @@ private User createUser(OAuthUserInfo userInfo) { private void createProfile(OAuthUserInfo userInfo, User user, String accessToken) { AgendaProfile agendaProfile = AgendaProfile.builder() .userId(user.getId()) + .intraId(userInfo.getIntraId()) .content("안녕하세요! " + userInfo.getIntraId() + "입니다.") .githubUrl(null) .coalition(findCoalition(userInfo.getUserId(), accessToken)) diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 72e0db598..69a489076 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -64,6 +64,7 @@ CREATE TABLE `agenda_profile` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `user_id` BIGINT NOT NULL, + `intra_id` VARCHAR(30) NOT NULL, `content` VARCHAR(1000) NOT NULL, `github_url` VARCHAR(255) NULL, `coalition` VARCHAR(30) NOT NULL, diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 4d49bb69c..5e52a8427 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -1,5 +1,6 @@ package gg.repo.agenda; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,6 +8,7 @@ import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; public interface AgendaTeamProfileRepository extends JpaRepository { @@ -14,4 +16,6 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") + List findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 4d61c8d31..0b19919d3 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -16,5 +16,11 @@ public interface AgendaTeamRepository extends JpaRepository { Optional findByAgendaAndTeamNameAndStatus(Agenda agenda, String teamName, AgendaTeamStatus status1, AgendaTeamStatus status2); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.teamKey = :teamKey AND" + + " (a.status = :status1 OR a.status = :status2)") + Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey, AgendaTeamStatus status1, + AgendaTeamStatus status2); + + @Query("SELECT a FROM AgendaTeam a WHERE a.teamKey = :teamKey") Optional findByTeamKey(UUID teamKey); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index e12b64f43..e964ae8f7 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -202,6 +202,7 @@ public enum ErrorCode { TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), + AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."); private final int status; From 06c3abc1b791b316473ebd9d36fa10ef8ecaf73d Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:22:37 +0900 Subject: [PATCH 032/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#842=20?= =?UTF-8?q?=EB=82=B4=20Team=20=EA=B0=84=EB=8B=A8=EC=A1=B0=ED=9A=8C=20API?= =?UTF-8?q?=20(#882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 18 ++++ .../response/MyTeamSimpleResDto.java | 36 +++++++ .../agendateam/service/AgendaTeamService.java | 35 ++++++- .../agendateam/AgendaTeamControllerTest.java | 96 +++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/MyTeamSimpleResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 31d4bbc60..db09e4b52 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agendateam.controller; +import java.util.Optional; import java.util.UUID; import javax.validation.Valid; @@ -15,6 +16,7 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; +import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; @@ -30,6 +32,22 @@ public class AgendaTeamController { private final AgendaTeamService agendaTeamService; /** + * 내 팀 간단 정보 조회 + * @param user 사용자 정보, agendaId 아젠다 아이디 + * @return 내 팀 간단 정보 + */ + @GetMapping("/my") + public ResponseEntity> myTeamSimpleDetails( + @Parameter(hidden = true) @Login UserDto user, + @RequestParam("agenda_key") UUID agendaKey) { + Optional myTeamSimpleResDto = agendaTeamService.detailsMyTeamSimple(user, agendaKey); + if (myTeamSimpleResDto.isEmpty()) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.ok(myTeamSimpleResDto); + } + + /* * 아젠다 팀 상세 정보 조회 * @param user 사용자 정보, teamDetailsReqDto 팀 상세 정보 요청 정보, agendaId 아젠다 아이디 * @return 팀 상세 정보 diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/MyTeamSimpleResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/MyTeamSimpleResDto.java new file mode 100644 index 000000000..0a9ec04c0 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/MyTeamSimpleResDto.java @@ -0,0 +1,36 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import java.util.List; +import java.util.UUID; + +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Coalition; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MyTeamSimpleResDto { + private String teamName; + private String teamLeaderIntraId; + private int teamMateCount; + private AgendaTeamStatus teamStatus; + private UUID teamKey; + private Location teamLocation; + private String teamAward; + private List coalitions; + + public MyTeamSimpleResDto(AgendaTeam agendaTeam, List coalitions) { + this.teamName = agendaTeam.getName(); + this.teamLeaderIntraId = agendaTeam.getLeaderIntraId(); + this.teamMateCount = agendaTeam.getMateCount(); + this.teamStatus = agendaTeam.getStatus(); + this.teamKey = agendaTeam.getTeamKey(); + this.teamLocation = agendaTeam.getLocation(); + this.teamAward = agendaTeam.getAward(); + this.coalitions = coalitions; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index fb55cda84..b0cecb953 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -5,13 +5,16 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; +import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.auth.UserDto; @@ -20,6 +23,7 @@ import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; +import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; @@ -41,6 +45,35 @@ public class AgendaTeamService { private final AgendaProfileRepository agendaProfileRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; + /** + * 내 팀 간단 정보 조회 + * @param user 사용자 정보, agendaId 아젠다 아이디 + * @return 내 팀 간단 정보 + */ + public Optional detailsMyTeamSimple(UserDto user, UUID agendaKey) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + Optional agendaTeam = agendaTeamProfileRepository.findByAgendaAndIsExistTrue(agenda, agendaProfile) + .map(AgendaTeamProfile::getAgendaTeam); + if (agendaTeam.isEmpty()) { + return Optional.empty(); + } + + List agendaTeamProfileList = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam.get()); + + List coalitions = agendaTeamProfileList.stream() + .map(AgendaTeamProfile::getProfile) + .map(AgendaProfile::getCoalition) + .collect(Collectors.toList()); + + return Optional.of(new MyTeamSimpleResDto(agendaTeam.get(), coalitions)); + } + /** * 아젠다 팀 상세 정보 조회 * @param user 사용자 정보, teamCreateReqDto 팀 키, agendaKey 아젠다 키 @@ -75,7 +108,7 @@ public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamDet @Transactional public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqDto, UUID agendaKey) { AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) - .orElseThrow(() -> new NotExistException("해당 유저의 프로필이 존재하지 않습니다.")); + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 0d95b7d64..a0b0c370b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -542,4 +542,100 @@ public void teamDetailsGetFailByCancelTeam() throws Exception { .andReturn().getResponse().getContentAsString(); } } + + @Nested + @DisplayName("내 팀 조회 테스트") + class MyTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 서울 agenda에 서울 user 팀 조회 성공") + public void myTeamSimpleDetailsStatusSeoul() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + // when + String res = mockMvc.perform( + get("/agenda/team/my") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + // then + assertThat(res).isNotNull(); + } + + @Test + @DisplayName("200 경산 agenda에 경산 user 팀 조회 성공") + public void myTeamSimpleDetailsStatusGyeongsan() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, gyeongsanUser); + agendaMockData.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); + // when + String res = mockMvc.perform( + get("/agenda/team/my") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + // then + assertThat(res).isNotNull(); + } + + @Test + @DisplayName("204 my팀 없을때 조회 성공") + public void myTeamSimpleDetailsStatusNoTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + // when + String res = mockMvc.perform( + get("/agenda/team/my") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isNoContent()) + .andReturn().getResponse().getContentAsString(); + // then + assertThat(res).isNotNull(); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + // when && then + String res = mockMvc.perform( + get("/agenda/team/my") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString())) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("404 agenda에 프로필 없음으로 인한 실패") + public void noAgendaProfileFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + User noProfileUser = testDataUtils.createNewUser(); + String noProfileUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(noProfileUser); + // when && then + String res = mockMvc.perform( + get("/agenda/team/my") + .header("Authorization", "Bearer " + noProfileUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString())) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + } } From 56ee506826f2e203ead122ac7a6dd5efc55292cf Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:32:48 +0900 Subject: [PATCH 033/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#861=20Agenda?= =?UTF-8?q?=20=EC=A2=85=EB=A3=8C=20=ED=99=95=EC=A0=95=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?API=20(#883)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 76 ++- .../request/AgendaConfirmReqDto.java | 27 + .../AgendaCreateReqDto.java} | 14 +- .../request/AgendaTeamAwardDto.java | 35 ++ .../AgendaKeyResDto.java} | 6 +- .../AgendaResDto.java} | 13 +- .../AgendaSimpleResDto.java} | 10 +- .../user/agenda/service/AgendaService.java | 57 +- .../java/gg/agenda/api/AgendaMockData.java | 78 +++ .../controller/AgendaControllerTest.java | 485 +++++++++++++++++- ...oTest.java => AgendaCreateReqDtoTest.java} | 27 +- ...oTest.java => AgendaSimpleResDtoTest.java} | 10 +- .../agenda/service/AgendaServiceTest.java | 259 +++++++++- .../src/main/java/gg/data/agenda/Agenda.java | 13 + .../main/java/gg/data/agenda/AgendaTeam.java | 4 + .../gg/repo/agenda/AgendaTeamRepository.java | 3 + .../java/gg/utils/exception/ErrorCode.java | 7 +- 17 files changed, 989 insertions(+), 135 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/{dto/AgendaCreateDto.java => request/AgendaCreateReqDto.java} (89%) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/{dto/AgendaKeyResponseDto.java => response/AgendaKeyResDto.java} (66%) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/{dto/AgendaResponseDto.java => response/AgendaResDto.java} (86%) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/{dto/AgendaSimpleResponseDto.java => response/AgendaSimpleResDto.java} (85%) rename gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/{AgendaCreateDtoTest.java => AgendaCreateReqDtoTest.java} (79%) rename gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/{AgendaSimpleResponseDtoTest.java => AgendaSimpleResDtoTest.java} (77%) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 191e2afa3..ee444312b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -1,7 +1,11 @@ package gg.agenda.api.user.agenda.controller; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import javax.validation.Valid; @@ -11,20 +15,26 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; -import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; +import gg.agenda.api.user.agenda.controller.response.AgendaKeyResDto; +import gg.agenda.api.user.agenda.controller.response.AgendaResDto; +import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; import gg.agenda.api.user.agenda.service.AgendaService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.custom.ForbiddenException; +import gg.utils.exception.custom.InvalidParameterException; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; @@ -42,37 +52,67 @@ public class AgendaController { @ApiResponse(responseCode = "400", description = "Agenda 조회 요청이 잘못됨"), @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음") }) - public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { - AgendaResponseDto agendaDto = agendaService.findAgendaWithLatestAnnouncement(agendaKey); - return ResponseEntity.ok(agendaDto); + public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + Optional announcement = agendaService.findAgendaWithLatestAnnouncement(agenda); + String announcementTitle = announcement.map(AgendaAnnouncement::getTitle).orElse(""); + AgendaResDto agendaResDto = AgendaResDto.MapStruct.INSTANCE.toDto(agenda, announcementTitle); + return ResponseEntity.ok(agendaResDto); } @ApiResponse(responseCode = "200", description = "현재 진행중인 Agenda 목록 조회 성공") @GetMapping("/list") - public ResponseEntity> agendaListCurrent() { - List agendaList = agendaService.findCurrentAgendaList(); - return ResponseEntity.ok(agendaList); + public ResponseEntity> agendaListCurrent() { + List agendaList = agendaService.findCurrentAgendaList(); + List agendaSimpleResDtoList = agendaList.stream() + .map(AgendaSimpleResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(agendaSimpleResDtoList); } @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Agenda 생성 성공"), + @ApiResponse(responseCode = "201", description = "Agenda 생성 성공"), @ApiResponse(responseCode = "400", description = "Agenda 생성 요청 파라미터가 잘못됨") }) @PostMapping("/create") - public ResponseEntity agendaAdd(@Login UserDto user, - @RequestBody @Valid AgendaCreateDto agendaCreateDto) { - AgendaKeyResponseDto agendaKey = agendaService.addAgenda(agendaCreateDto, user); - return ResponseEntity.status(HttpStatus.CREATED).body(agendaKey); + public ResponseEntity agendaAdd(@Login UserDto user, + @RequestBody @Valid AgendaCreateReqDto agendaCreateReqDto) { + UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, user).getAgendaKey(); + AgendaKeyResDto responseDto = AgendaKeyResDto.builder().agendaKey(agendaKey).build(); + return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); } @ApiResponse(responseCode = "200", description = "지난 Agenda 목록 조회 성공") @GetMapping("/history") - public ResponseEntity> agendaListHistory( + public ResponseEntity> agendaListHistory( @RequestBody @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); - List agendaList = agendaService.findHistoryAgendaList(pageable); - return ResponseEntity.ok(agendaList); + List agendas = agendaService.findHistoryAgendaList(pageable); + List agendaSimpleResDtoList = agendas.stream() + .map(AgendaSimpleResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(agendaSimpleResDtoList); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Agenda 참가 신청 성공"), + @ApiResponse(responseCode = "400", description = "Agenda 참가 신청 요청이 잘못됨"), + @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음"), + @ApiResponse(responseCode = "409", description = "Agenda 참가 신청이 이미 완료됨") + }) + @PatchMapping("/confirm") + public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user, + @RequestBody(required = false) @Valid AgendaConfirmReqDto agendaConfirmReqDto) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + if (!user.getIntraId().equals(agenda.getHostIntraId())) { + throw new ForbiddenException(CONFIRM_FORBIDDEN); + } + if (agenda.getIsRanking() && agendaConfirmReqDto == null) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + agendaService.confirmAgenda(agendaConfirmReqDto, agenda); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java new file mode 100644 index 000000000..e96bf1e08 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java @@ -0,0 +1,27 @@ +package gg.agenda.api.user.agenda.controller.request; + +import java.util.List; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaConfirmReqDto { + + @Valid + @NotNull + @NotEmpty + private List awards; + + @Builder + public AgendaConfirmReqDto(List awards) { + this.awards = awards; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java similarity index 89% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index b73361a6f..60129f6d9 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.agenda.controller.dto; +package gg.agenda.api.user.agenda.controller.request; import static gg.utils.exception.ErrorCode.*; @@ -26,7 +26,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaCreateDto { +public class AgendaCreateReqDto { @NotNull @NotEmpty @@ -76,7 +76,7 @@ public class AgendaCreateDto { private Boolean agendaIsOfficial; @Builder - public AgendaCreateDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + public AgendaCreateReqDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, Location agendaLocation, Boolean agendaIsRanking, Boolean agendaIsOfficial) { @@ -97,7 +97,7 @@ public AgendaCreateDto(String agendaTitle, String agendaContents, LocalDateTime @Mapper public interface MapStruct { - AgendaCreateDto.MapStruct INSTANCE = Mappers.getMapper(AgendaCreateDto.MapStruct.class); + AgendaCreateReqDto.MapStruct INSTANCE = Mappers.getMapper(AgendaCreateReqDto.MapStruct.class); @Mapping(target = "id", ignore = true) @Mapping(target = "title", source = "dto.agendaTitle") @@ -116,10 +116,10 @@ public interface MapStruct { @Mapping(target = "status", constant = "ON_GOING") @Mapping(target = "isOfficial", source = "dto.agendaIsOfficial") @Mapping(target = "isRanking", source = "dto.agendaIsRanking") - Agenda toEntity(AgendaCreateDto dto, UserDto user); + Agenda toEntity(AgendaCreateReqDto dto, UserDto user); @BeforeMapping - default void mustHaveValidAgendaSchedule(AgendaCreateDto dto, UserDto user) { + default void mustHaveValidAgendaSchedule(AgendaCreateReqDto dto, UserDto user) { if (!dto.getAgendaDeadLine().isBefore(dto.getAgendaStartTime())) { throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); } @@ -132,7 +132,7 @@ default void mustHaveValidAgendaSchedule(AgendaCreateDto dto, UserDto user) { } @BeforeMapping - default void mustHaveValidParam(AgendaCreateDto dto, UserDto user) { + default void mustHaveValidParam(AgendaCreateReqDto dto, UserDto user) { if (dto.getAgendaMinTeam() > dto.getAgendaMaxTeam()) { throw new InvalidParameterException(AGENDA_INVALID_PARAM); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java new file mode 100644 index 000000000..ae7a28873 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java @@ -0,0 +1,35 @@ +package gg.agenda.api.user.agenda.controller.request; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamAwardDto { + + @NotNull + @NotEmpty + private String teamName; + + @NotNull + @NotEmpty + private String awardName; + + @Min(1) + @Max(1000) + private int awardPriority; + + @Builder + public AgendaTeamAwardDto(String teamName, String awardName, int awardPriority) { + this.teamName = teamName; + this.awardName = awardName; + this.awardPriority = awardPriority; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaKeyResDto.java similarity index 66% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaKeyResDto.java index a5dd1b48d..fa61675cb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaKeyResponseDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaKeyResDto.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.agenda.controller.dto; +package gg.agenda.api.user.agenda.controller.response; import java.util.UUID; @@ -9,12 +9,12 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaKeyResponseDto { +public class AgendaKeyResDto { private UUID agendaKey; @Builder - public AgendaKeyResponseDto(UUID agendaKey) { + public AgendaKeyResDto(UUID agendaKey) { this.agendaKey = agendaKey; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java similarity index 86% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java index cf4b63dae..ec62320c3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaResponseDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.agenda.controller.dto; +package gg.agenda.api.user.agenda.controller.response; import java.time.LocalDateTime; @@ -7,7 +7,6 @@ import org.mapstruct.factory.Mappers; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; import lombok.AccessLevel; @@ -17,7 +16,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaResponseDto { +public class AgendaResDto { private String agendaTitle; @@ -54,7 +53,7 @@ public class AgendaResponseDto { private String announcementTitle; @Builder - public AgendaResponseDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, String agendaHost, Location agendaLocation, AgendaStatus agendaStatus, LocalDateTime createdAt, String announcementTitle, @@ -81,7 +80,7 @@ public AgendaResponseDto(String agendaTitle, String agendaContents, LocalDateTim @Mapper public interface MapStruct { - AgendaResponseDto.MapStruct INSTANCE = Mappers.getMapper(AgendaResponseDto.MapStruct.class); + AgendaResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaResDto.MapStruct.class); @Mapping(target = "agendaTitle", source = "agenda.title") @Mapping(target = "agendaContents", source = "agenda.content") @@ -99,7 +98,7 @@ public interface MapStruct { @Mapping(target = "agendaStatus", source = "agenda.status") @Mapping(target = "createdAt", source = "agenda.createdAt") @Mapping(target = "isOfficial", source = "agenda.isOfficial") - @Mapping(target = "announcementTitle", source = "announcement.title") - AgendaResponseDto toDto(Agenda agenda, AgendaAnnouncement announcement); + @Mapping(target = "announcementTitle", source = "announcementTitle") + AgendaResDto toDto(Agenda agenda, String announcementTitle); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java similarity index 85% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java index 2ee1af203..6babae389 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java @@ -1,4 +1,4 @@ -package gg.agenda.api.user.agenda.controller.dto; +package gg.agenda.api.user.agenda.controller.response; import java.time.LocalDateTime; import java.util.UUID; @@ -16,7 +16,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaSimpleResponseDto { +public class AgendaSimpleResDto { private String agendaTitle; private LocalDateTime agendaDeadLine; @@ -40,7 +40,7 @@ public class AgendaSimpleResponseDto { private Boolean isOfficial; @Builder - public AgendaSimpleResponseDto(String agendaTitle, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, + public AgendaSimpleResDto(String agendaTitle, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, Location agendaLocation, UUID agendaKey, boolean isOfficial) { this.agendaTitle = agendaTitle; @@ -58,7 +58,7 @@ public AgendaSimpleResponseDto(String agendaTitle, LocalDateTime agendaDeadLine, @Mapper public interface MapStruct { - AgendaSimpleResponseDto.MapStruct INSTANCE = Mappers.getMapper(AgendaSimpleResponseDto.MapStruct.class); + AgendaSimpleResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaSimpleResDto.MapStruct.class); @Mapping(target = "agendaTitle", source = "title") @Mapping(target = "agendaDeadLine", source = "deadline") @@ -71,6 +71,6 @@ public interface MapStruct { @Mapping(target = "agendaLocation", source = "location") @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "isOfficial", source = "isOfficial") - AgendaSimpleResponseDto toDto(Agenda agenda); + AgendaSimpleResDto toDto(Agenda agenda); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 4e62bdb60..bf635a86f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -2,28 +2,28 @@ import static gg.utils.exception.ErrorCode.*; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; -import gg.utils.dto.PageRequestDto; +import gg.repo.agenda.AgendaTeamRepository; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @@ -35,35 +35,48 @@ public class AgendaService { private final AgendaAnnouncementRepository agendaAnnouncementRepository; + private final AgendaTeamRepository agendaTeamRepository; + @Transactional(readOnly = true) - public AgendaResponseDto findAgendaWithLatestAnnouncement(UUID agendaKey) { - Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + public Agenda findAgendaByAgendaKey(UUID agendaKey) { + return agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - AgendaAnnouncement announcement = agendaAnnouncementRepository - .findLatestByAgenda(agenda).orElse(null); - return AgendaResponseDto.MapStruct.INSTANCE.toDto(agenda, announcement); } @Transactional(readOnly = true) - public List findCurrentAgendaList() { + public Optional findAgendaWithLatestAnnouncement(Agenda agenda) { + return agendaAnnouncementRepository.findLatestByAgenda(agenda); + } + + @Transactional(readOnly = true) + public List findCurrentAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING).stream() .sorted(Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) .thenComparing(Agenda::getDeadline, Comparator.reverseOrder())) - .map(AgendaSimpleResponseDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); } @Transactional - public AgendaKeyResponseDto addAgenda(AgendaCreateDto agendaCreateDto, UserDto user) { - Agenda newAgenda = AgendaCreateDto.MapStruct.INSTANCE.toEntity(agendaCreateDto, user); - Agenda savedAgenda = agendaRepository.save(newAgenda); - return AgendaKeyResponseDto.builder().agendaKey(savedAgenda.getAgendaKey()).build(); + public Agenda addAgenda(AgendaCreateReqDto agendaCreateReqDto, UserDto user) { + Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(agendaCreateReqDto, user); + return agendaRepository.save(newAgenda); } @Transactional(readOnly = true) - public List findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.CONFIRM).getContent().stream() - .map(AgendaSimpleResponseDto.MapStruct.INSTANCE::toDto) - .collect(Collectors.toList()); + public List findHistoryAgendaList(Pageable pageable) { + return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.CONFIRM).getContent(); + } + + @Transactional + public void confirmAgenda(AgendaConfirmReqDto agendaConfirmReqDto, Agenda agenda) { + if (agenda.getIsRanking()) { + agendaConfirmReqDto.getAwards().forEach(award -> { + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndNameAndStatus(agenda, award.getTeamName(), AgendaTeamStatus.CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + agendaTeam.acceptAward(award.getAwardName(), award.getAwardPriority()); + }); + } + agenda.confirm(LocalDateTime.now()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 6c6c9dcb2..42fe188d5 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -220,6 +220,50 @@ public Agenda createAgenda(String intraId) { return agendaRepository.save(agenda); } + public Agenda createAgenda(String intraId, LocalDateTime startTime, boolean rank) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(startTime.minusDays(1)) + .startTime(startTime) + .endTime(startTime.plusDays(1)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId(intraId) + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(rank) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(String intraId, LocalDateTime startTime, AgendaStatus status) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(startTime.minusDays(1)) + .startTime(startTime) + .endTime(startTime.plusDays(1)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId(intraId) + .location(SEOUL) + .status(status) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + public Agenda createAgenda(Location location) { Agenda agenda = Agenda.builder() .title("title") @@ -324,6 +368,40 @@ public AgendaTeam createAgendaTeam(Agenda agenda) { return agendaTeamRepository.save(agendaTeam); } + public AgendaTeam createAgendaTeam(Agenda agenda, String teamName) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name(teamName) + .content("content") + .leaderIntraId("leaderIntraId") + .status(OPEN) + .location(SEOUL) + .mateCount(3) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, String teamName, AgendaTeamStatus status) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name(teamName) + .content("content") + .leaderIntraId("leaderIntraId") + .status(status) + .location(SEOUL) + .mateCount(3) + .award("award") + .awardPriority(-1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + public AgendaTeam createAgendaTeam(Agenda agenda, User user) { AgendaTeam agendaTeam = AgendaTeam.builder() .agenda(agenda) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index ef3b950db..934ec314d 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -8,6 +8,8 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.persistence.EntityManager; import javax.transaction.Transactional; @@ -23,17 +25,20 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; -import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaTeamAwardDto; +import gg.agenda.api.user.agenda.controller.response.AgendaKeyResDto; +import gg.agenda.api.user.agenda.controller.response.AgendaResDto; +import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.data.user.User; import gg.repo.agenda.AgendaRepository; @@ -66,11 +71,13 @@ public class AgendaControllerTest { @Autowired AgendaRepository agendaRepository; + private User user; + private String accessToken; @BeforeEach void setUp() { - User user = testDataUtils.createNewUser(); + user = testDataUtils.createNewUser(); accessToken = testDataUtils.getLoginAccessTokenFromUser(user); } @@ -91,7 +98,7 @@ void getAgendaSuccess() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString())) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + AgendaResDto result = objectMapper.readValue(response, AgendaResDto.class); // then assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); @@ -99,7 +106,7 @@ void getAgendaSuccess() throws Exception { } @Test - @DisplayName("announce가 없는 경우 announcementTitle를 null로 반환합니다.") + @DisplayName("announce가 없는 경우 announcementTitle를 빈 문자열로 반환합니다.") void getAgendaWithNoAnnounce() throws Exception { // given Agenda agenda = agendaMockData.createOfficialAgenda(); @@ -110,11 +117,11 @@ void getAgendaWithNoAnnounce() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString())) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + AgendaResDto result = objectMapper.readValue(response, AgendaResDto.class); // then assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); - assertThat(result.getAnnouncementTitle()).isEqualTo(null); + assertThat(result.getAnnouncementTitle()).isEqualTo(""); } @Test @@ -132,7 +139,7 @@ void getAgendaWithLatestAnnounce() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString())) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaResponseDto result = objectMapper.readValue(response, AgendaResponseDto.class); + AgendaResDto result = objectMapper.readValue(response, AgendaResDto.class); // then assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); @@ -186,7 +193,7 @@ void getAgendaListSuccess() throws Exception { .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(officialAgendaList.size() + nonOfficialAgendaList.size()); @@ -211,7 +218,7 @@ void getAgendaListSuccessWithNoAgenda() throws Exception { .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(0); @@ -226,7 +233,7 @@ class CreateAgenda { @DisplayName("Agenda를 생성합니다.") void createAgendaSuccess() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -242,7 +249,7 @@ void createAgendaSuccess() throws Exception { .content(request)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - AgendaKeyResponseDto result = objectMapper.readValue(response, AgendaKeyResponseDto.class); + AgendaKeyResDto result = objectMapper.readValue(response, AgendaKeyResDto.class); Optional agenda = agendaRepository.findByAgendaKey(result.getAgendaKey()); // then @@ -255,7 +262,7 @@ void createAgendaSuccess() throws Exception { @DisplayName("deadline이 startTime보다 미래인 경우 400을 반환합니다.") void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(6)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -276,7 +283,7 @@ void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { @DisplayName("deadline이 endTime보다 미래인 경우 400을 반환합니다.") void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -297,7 +304,7 @@ void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { @DisplayName("startTime이 endTime보다 미래인 경우 400을 반환합니다.") void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(7)) @@ -318,7 +325,7 @@ void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { @DisplayName("min team이 max team보다 큰 경우 400을 반환합니다.") void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -339,7 +346,7 @@ void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { @DisplayName("min people이 max people보다 큰 경우 400을 반환합니다.") void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -361,7 +368,7 @@ void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { @DisplayName("min team이 1 이하인 경우 400을 반환합니다.") void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -383,7 +390,7 @@ void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { @DisplayName("min people이 0 이하인 경우 400을 반환합니다.") void createAgendaFailedWhenNegativeMinPeople(int value) throws Exception { // given - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaTitle("title").agendaContents("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) @@ -422,7 +429,7 @@ void getAgendaListHistorySuccess(int page) throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(req)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(size * page < totalCount ? size : totalCount % size); @@ -450,7 +457,7 @@ void getAgendaListHistoryWithNoContent() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(req)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(0); @@ -506,7 +513,7 @@ void getAgendaListHistoryWithExcessPage(int page) throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(req)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(0); @@ -544,7 +551,7 @@ void getAgendaListHistoryWithoutSize() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(req)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResponseDto[] result = objectMapper.readValue(response, AgendaSimpleResponseDto[].class); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); // then assertThat(result.length).isEqualTo(20); @@ -557,4 +564,430 @@ void getAgendaListHistoryWithoutSize() throws Exception { } } } + + @Nested + @DisplayName("Agenda 시상 및 확정") + class ConfirmAgenda { + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상 대회인 경우") + void confirmAgendaSuccess() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isNoContent()); + Agenda result = em.createQuery("select a from Agenda a where a.agendaKey = :agendaKey", Agenda.class) + .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); + + // then + assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + awards.forEach(award -> { + AgendaTeam agendaTeam = em.createQuery( + "select at from AgendaTeam at where at.agenda = :agenda and at.name = :teamName", + AgendaTeam.class) + .setParameter("agenda", agenda) + .setParameter("teamName", award.getTeamName()) + .getSingleResult(); + assertThat(agendaTeam.getAward()).isEqualTo(award.getAwardName()); + assertThat(agendaTeam.getAwardPriority()).isEqualTo(award.getAwardPriority()); + }); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회인 경우") + void confirmAgendaSuccessWithNoRanking() throws Exception { + // given + int teamSize = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false); + IntStream.range(0, teamSize) + .forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); + Agenda result = em.createQuery("select a from Agenda a where a.agendaKey = :agendaKey", Agenda.class) + .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); + + // then + assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") + void confirmAgendaSuccessWithNoRankAndAwards() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isNoContent()); + Agenda result = em.createQuery("select a from Agenda a where a.agendaKey = :agendaKey", Agenda.class) + .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); + + // then + assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + awards.forEach(award -> { + AgendaTeam agendaTeam = em.createQuery( + "select at from AgendaTeam at where at.agenda = :agenda and at.name = :teamName", + AgendaTeam.class) + .setParameter("agenda", agenda) + .setParameter("teamName", award.getTeamName()) + .getSingleResult(); + assertThat(agendaTeam.getAward()).isNotEqualTo(award.getAwardName()); + assertThat(agendaTeam.getAwardPriority()).isNotEqualTo(award.getAwardPriority()); + }); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 존재하지 않는 팀에 대한 시상인 경우") + void confirmAgendaFailedWithInvalidTeam() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + awards.add(AgendaTeamAwardDto.builder() + .teamName("invalid_team").awardName("prize").awardPriority(1).build()); // invalid team + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - Agenda가 없는 경우") + void confirmAgendaFailedWithNoAgenda() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + UUID invalidAgendaKey = UUID.randomUUID(); // invalid agenda key + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", invalidAgendaKey.toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 없는 경우") + void confirmAgendaFailedWithoutAwards() throws Exception { + // given + int teamSize = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + IntStream.range(0, teamSize).forEach(i -> + agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().build(); // null + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 빈 리스트인 경우") + void confirmAgendaFailedWithEmptyAwards() throws Exception { + // given + int teamSize = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + IntStream.range(0, teamSize).forEach(i -> + agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder() + .awards(List.of()) // empty + .build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 개최자가 아닌 경우") + void confirmAgendaFailedNotHost() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + User another = testDataUtils.createNewUser(); + Agenda agenda = agendaMockData.createAgenda(another.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 이미 확정된 경우") + void confirmAgendaFailedAlreadyConfirm() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), AgendaStatus.CONFIRM); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 이미 취소된 경우") + void confirmAgendaFailedAlreadyCancel() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), AgendaStatus.CANCEL); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 아직 시작하지 않은 경우") + void confirmAgendaFailedBeforeStartTime() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().plusDays(1), AgendaStatus.ON_GOING); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - empty awardName") + void confirmAgendaFailedWithEmptyAwardName() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + awards.add(AgendaTeamAwardDto.builder().teamName(agendaTeams.get(awardSize).getName()) + .awardName("").awardPriority(awardSize).build()); // empty award name + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - null awardName") + void confirmAgendaFailedWithNullAwardName() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + awards.add(AgendaTeamAwardDto.builder().teamName(agendaTeams.get(awardSize).getName()) + .awardPriority(awardSize).build()); // null award name + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - empty teamName") + void confirmAgendaFailedWithEmptyTeamName() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + awards.add(AgendaTeamAwardDto.builder().awardName("prize" + awardSize) + .teamName("").awardPriority(awardSize).build()); // empty award name + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - null teamName") + void confirmAgendaFailedWithNullTeamName() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + awards.add(AgendaTeamAwardDto.builder().awardName("prize" + awardSize) + .awardPriority(awardSize).build()); // null award name + AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(response)) + .andExpect(status().isBadRequest()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java similarity index 79% rename from gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java rename to gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java index 780eb69f0..dfc42e846 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateDtoTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java @@ -9,27 +9,28 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.InvalidParameterException; @UnitTest -class AgendaCreateDtoTest { +class AgendaCreateReqDtoTest { @Test @DisplayName("Agenda 생성 성공") void createAgendaSuccess() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(5)) .agendaStartTime(LocalDateTime.now().plusDays(8)) .agendaEndTime(LocalDateTime.now().plusDays(10)) .build(); // when - Agenda agenda = AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user); + Agenda agenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user); // then assertNotNull(agenda); @@ -45,7 +46,7 @@ class CreateAgendaFailed { void createAgendaFailedWhenDeadlineIsBeforeStartTime() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(5)) .agendaStartTime(LocalDateTime.now().plusDays(2)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -53,7 +54,7 @@ void createAgendaFailedWhenDeadlineIsBeforeStartTime() { // expected assertThrows(InvalidParameterException.class, - () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); } @Test @@ -61,7 +62,7 @@ void createAgendaFailedWhenDeadlineIsBeforeStartTime() { void createAgendaFailedWhenDeadlineIsBeforeEndTime() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(5)) .agendaStartTime(LocalDateTime.now().plusDays(7)) .agendaEndTime(LocalDateTime.now().plusDays(6)) @@ -69,7 +70,7 @@ void createAgendaFailedWhenDeadlineIsBeforeEndTime() { // expected assertThrows(InvalidParameterException.class, - () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); } @Test @@ -77,7 +78,7 @@ void createAgendaFailedWhenDeadlineIsBeforeEndTime() { void createAgendaFailedWhenStartTimeIsBeforeEndTime() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(6)) .agendaEndTime(LocalDateTime.now().plusDays(5)) @@ -85,7 +86,7 @@ void createAgendaFailedWhenStartTimeIsBeforeEndTime() { // expected assertThrows(InvalidParameterException.class, - () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); } @Test @@ -93,7 +94,7 @@ void createAgendaFailedWhenStartTimeIsBeforeEndTime() { void createAgendaFailedWhenMinTeamIsGreaterThanMaxTeam() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(6)) .agendaEndTime(LocalDateTime.now().plusDays(8)) @@ -102,7 +103,7 @@ void createAgendaFailedWhenMinTeamIsGreaterThanMaxTeam() { // expected assertThrows(InvalidParameterException.class, - () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); } @Test @@ -110,7 +111,7 @@ void createAgendaFailedWhenMinTeamIsGreaterThanMaxTeam() { void createAgendaFailedWhenMinPeopleIsGreaterThanMaxPeople() { //given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto dto = AgendaCreateDto.builder() + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(6)) .agendaEndTime(LocalDateTime.now().plusDays(8)) @@ -119,7 +120,7 @@ void createAgendaFailedWhenMinPeopleIsGreaterThanMaxPeople() { // expected assertThrows(InvalidParameterException.class, - () -> AgendaCreateDto.MapStruct.INSTANCE.toEntity(dto, user)); + () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResDtoTest.java similarity index 77% rename from gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java rename to gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResDtoTest.java index 7ee1cbc0a..b8e1ef055 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResponseDtoTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaSimpleResDtoTest.java @@ -2,23 +2,21 @@ import static org.assertj.core.api.AssertionsForClassTypes.*; -import java.util.UUID; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; import gg.data.agenda.Agenda; import gg.utils.annotation.UnitTest; @UnitTest -class AgendaSimpleResponseDtoTest { +class AgendaSimpleResDtoTest { @Nested @DisplayName("AgendaSimpleResponseDto 생성") - class CreateAgendaSimpleResponseDto { + class CreateAgendaSimpleResDto { @ParameterizedTest @ValueSource(booleans = {true, false}) @@ -30,7 +28,7 @@ void createAgendaSimpleResponseDtoSuccess(boolean value) { .build(); // given - AgendaSimpleResponseDto dto = AgendaSimpleResponseDto.MapStruct.INSTANCE.toDto(agenda); + AgendaSimpleResDto dto = AgendaSimpleResDto.MapStruct.INSTANCE.toDto(agenda); // then assertThat(dto).isNotNull(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 8e6fbe33f..5113e0523 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -11,6 +11,7 @@ import java.util.UUID; import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -22,14 +23,17 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import gg.agenda.api.user.agenda.controller.dto.AgendaCreateDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaKeyResponseDto; -import gg.agenda.api.user.agenda.controller.dto.AgendaSimpleResponseDto; +import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaTeamAwardDto; import gg.auth.UserDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamRepository; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.NotExistException; import lombok.extern.slf4j.Slf4j; @@ -44,42 +48,73 @@ class AgendaServiceTest { @Mock AgendaAnnouncementRepository agendaAnnouncementRepository; + @Mock + AgendaTeamRepository agendaTeamRepository; + @InjectMocks AgendaService agendaService; @Nested - @DisplayName("Agenda 단건 조회") + @DisplayName("Agenda 상세 조회") class GetAgenda { - @Test - @DisplayName("Agenda 단건 조회 성공") - void getAgendaSuccess() { + @DisplayName("AgendaKey로 Agenda 상세 조회") + void findAgendaByAgendaKeySuccess() { // given - UUID agendaKey = UUID.randomUUID(); - Agenda agenda = mock(Agenda.class); + Agenda agenda = Agenda.builder().build(); + UUID agendaKey = agenda.getAgendaKey(); when(agendaRepository.findByAgendaKey(agendaKey)).thenReturn(Optional.of(agenda)); - when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.empty()); // when - agendaService.findAgendaWithLatestAnnouncement(agendaKey); + Agenda result = agendaService.findAgendaByAgendaKey(agendaKey); // then verify(agendaRepository, times(1)).findByAgendaKey(agendaKey); - verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); + assertThat(result).isEqualTo(agenda); } @Test - @DisplayName("Agenda 단건 조회 실패") - void getAgendaFailedWithnoAgenda() { + @DisplayName("AgendaKey로 Agenda 상세 조회 - 존재하지 않는 AgendaKey인 경우") + void findAgendaByAgendaKeyFailedWithNoAgenda() { // given UUID agendaKey = UUID.randomUUID(); - Agenda agenda = mock(Agenda.class); when(agendaRepository.findByAgendaKey(agendaKey)).thenReturn(Optional.empty()); // expected - assertThrows(NotExistException.class, () -> agendaService.findAgendaWithLatestAnnouncement(agendaKey)); - verify(agendaRepository, times(1)).findByAgendaKey(agendaKey); - verify(agendaAnnouncementRepository, never()).findLatestByAgenda(agenda); + assertThrows(NotExistException.class, + () -> agendaService.findAgendaByAgendaKey(agendaKey)); + } + + @Test + @DisplayName("AgendaAnnouncement 조회 성공 - 최신 공지사항이 있는 경우") + void findAgendaAnnouncementSuccess() { + // given + Agenda agenda = Agenda.builder().build(); + AgendaAnnouncement announcement = AgendaAnnouncement.builder().title("title").content("content").build(); + when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.of(announcement)); + + // when + Optional result = agendaService.findAgendaWithLatestAnnouncement(agenda); + + // then + verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); + assertThat(result).isPresent(); + assertThat(result.get().getTitle()).isEqualTo(announcement.getTitle()); + } + + @Test + @DisplayName("AgendaAnnouncement 조회 성공 - 최신 공지사항이 없는 경우") + void findAgendaAnnouncementSuccessWithNoAnnounce() { + // given + Agenda agenda = Agenda.builder().build(); + when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.empty()); + + // when + Optional result = agendaService.findAgendaWithLatestAnnouncement(agenda); + + // then + verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); + assertThat(result).isEmpty(); } } @@ -101,7 +136,7 @@ void getAgendaListSuccess() { when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); // when - List result = agendaService.findCurrentAgendaList(); + List result = agendaService.findCurrentAgendaList(); // then verify(agendaRepository, times(1)).findAllByStatusIs(any()); @@ -110,7 +145,7 @@ void getAgendaListSuccess() { if (i == 0 || i == officialSize) { continue; } - assertThat(result.get(i).getAgendaDeadLine()).isBefore(result.get(i - 1).getAgendaDeadLine()); + assertThat(result.get(i).getDeadline()).isBefore(result.get(i - 1).getDeadline()); } } @@ -138,7 +173,7 @@ class CreateAgenda { void createAgendaSuccess() { // given UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateDto agendaCreateDto = AgendaCreateDto.builder() + AgendaCreateReqDto agendaCreateReqDto = AgendaCreateReqDto.builder() .agendaDeadLine(LocalDateTime.now().plusDays(5)) .agendaStartTime(LocalDateTime.now().plusDays(8)) .agendaEndTime(LocalDateTime.now().plusDays(10)) @@ -149,12 +184,11 @@ void createAgendaSuccess() { when(agendaRepository.save(any(Agenda.class))).thenReturn(agenda); // when - AgendaKeyResponseDto agendaKeyResponseDto = agendaService.addAgenda(agendaCreateDto, user); + Agenda result = agendaService.addAgenda(agendaCreateReqDto, user); // then verify(agendaRepository, times(1)).save(any(Agenda.class)); - assertThat(agendaKeyResponseDto).isNotNull(); - assertThat(agendaKeyResponseDto.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); + assertThat(result.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); } } @@ -179,16 +213,187 @@ void getAgendaListHistorySuccess() { .thenReturn(agendaPage); // when - List result = agendaService.findHistoryAgendaList(pageable); + List result = agendaService.findHistoryAgendaList(pageable); // then verify(agendaRepository, times(1)) .findAllByStatusIs(pageable, AgendaStatus.CONFIRM); assertThat(result.size()).isEqualTo(size); for (int i = 1; i < result.size(); i++) { - assertThat(result.get(i).getAgendaStartTime()) - .isBefore(result.get(i - 1).getAgendaStartTime()); + assertThat(result.get(i).getStartTime()) + .isBefore(result.get(i - 1).getStartTime()); } } } + + @Nested + @DisplayName("Agenda 시상 및 확정") + class ConfirmAgenda { + + int seq; + + @BeforeEach + void setUp() { + seq = 0; + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공") + void confirmAgendaSuccess() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(true).build(); + List agendaTeams = new ArrayList<>(); + IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); + AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + .teamName("team1").awardName("award").awardPriority(1).build(); + UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); + UUID agendaKey = agenda.getAgendaKey(); + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + .awards(List.of(awardDto)).build(); + + when(agendaTeamRepository.findByAgendaAndNameAndStatus(any(), any(), any())) + .thenReturn(Optional.of(agendaTeams.get(seq++))); + + // when + agendaService.confirmAgenda(confirmDto, agenda); + + // then + verify(agendaTeamRepository, times(1)).findByAgendaAndNameAndStatus(any(), any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회인 경우") + void confirmAgendaSuccessWithNoRank() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(false).build(); + UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); + UUID agendaKey = agenda.getAgendaKey(); + + // when + agendaService.confirmAgenda(null, agenda); + + // then + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") + void confirmAgendaSuccessWithNoRankAndAwards() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(false).build(); + List agendaTeams = new ArrayList<>(); + IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); + AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + .teamName("team1").awardName("award").awardPriority(1).build(); + UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); + UUID agendaKey = agenda.getAgendaKey(); + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + .awards(List.of(awardDto)).build(); + + // when + agendaService.confirmAgenda(confirmDto, agenda); + + // then + verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 빈 리스트로 들어온 경우") + void confirmAgendaSuccessWithNoRankAndEmtpyAwards() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(false).build(); + List agendaTeams = new ArrayList<>(); + IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); + UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); + UUID agendaKey = agenda.getAgendaKey(); + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + .awards(List.of()).build(); + + // when + agendaService.confirmAgenda(confirmDto, agenda); + + // then + verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 null로 들어온 경우") + void confirmAgendaSuccessWithNoRankAndNullAwards() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(false).build(); + List agendaTeams = new ArrayList<>(); + IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); + UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); + UUID agendaKey = agenda.getAgendaKey(); + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder().build(); + + // when + agendaService.confirmAgenda(confirmDto, agenda); + + // then + verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 null인 경우") + void confirmAgendaFailedWithoutAwards() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(true).build(); + + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder().build(); + + // expected + assertThrows(NullPointerException.class, + () -> agendaService.confirmAgenda(confirmDto, agenda)); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 매개변수가 null인 경우") + void confirmAgendaFailedWithNullDto() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(true).build(); + + // expected + assertThrows(NullPointerException.class, + () -> agendaService.confirmAgenda(null, agenda)); + } + + @Test + @DisplayName("Agenda 시상 및 확정 실패 - 존재하지 않는 팀에 대한 시상인 경우") + void confirmAgendaFailedWithInvalidTeam() { + // given + Agenda agenda = Agenda.builder() + .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) + .status(AgendaStatus.ON_GOING).isRanking(true).build(); + AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + .teamName("invalidTeam").awardName("award").awardPriority(1).build(); + AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + .awards(List.of(awardDto)).build(); + + when(agendaTeamRepository.findByAgendaAndNameAndStatus(any(), any(), any())) + .thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaService.confirmAgenda(confirmDto, agenda)); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 5a36f20c4..d26f13b25 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -123,6 +123,19 @@ public void addTeam(Location location, LocalDateTime now) { this.currentTeam++; } + public void confirm(LocalDateTime confirmTime) { + if (this.status == AgendaStatus.CONFIRM) { + throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); + } + if (this.status == AgendaStatus.CANCEL) { + throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); + } + if (this.startTime.isAfter(confirmTime)) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + this.status = AgendaStatus.CONFIRM; + } + private void mustBeWithinLocation(Location location) { if (this.location != Location.MIX && this.location != location) { throw new InvalidParameterException(LOCATION_NOT_VALID); diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index bfeb632f5..39fbe303a 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -81,4 +81,8 @@ public AgendaTeam(Agenda agenda, UUID teamKey, String name, String content, Stri this.isPrivate = isPrivate; } + public void acceptAward(String award, int awardPriority) { + this.award = award; + this.awardPriority = awardPriority; + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 0b19919d3..8f107ca8e 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -23,4 +23,7 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.teamKey = :teamKey") Optional findByTeamKey(UUID teamKey); + + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :name AND a.status = :status") + Optional findByAgendaAndNameAndStatus(Agenda agenda, String name, AgendaTeamStatus status); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index e964ae8f7..a303e330a 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -198,12 +198,17 @@ public enum ErrorCode { AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), + CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), - AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."); + AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), + AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 종료된 일정입니다."), + AGENDA_ALREADY_CANCELED(409, "AG", "이미 취소된 일정입니다."), + AGENDA_AWARD_EMPTY(400, "AG", "시상 정보가 없습니다."), + AGENDA_AWARD_PRIORITY_DUPLICATE(400, "AG", "시상 우선순위가 중복됩니다."); private final int status; private final String errCode; From 3d9c74b75b2f1af83a5e3050e783d5911942f988 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:03:59 +0900 Subject: [PATCH 034/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#844=20Team=20?= =?UTF-8?q?=ED=99=95=EC=A0=95=ED=95=98=EA=B8=B0=20API=20(#884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 14 +- ...mDetailsReqDto.java => TeamKeyReqDto.java} | 4 +- .../agendateam/service/AgendaTeamService.java | 26 ++- .../java/gg/agenda/api/AgendaMockData.java | 46 +++- .../agendateam/AgendaTeamControllerTest.java | 211 +++++++++++++++++- .../src/main/java/gg/data/agenda/Agenda.java | 7 + .../main/java/gg/data/agenda/AgendaTeam.java | 13 ++ .../java/gg/utils/exception/ErrorCode.java | 4 + 8 files changed, 307 insertions(+), 18 deletions(-) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/{TeamDetailsReqDto.java => TeamKeyReqDto.java} (80%) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index db09e4b52..7ae3618b7 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -8,6 +8,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -15,7 +16,7 @@ import org.springframework.web.bind.annotation.RestController; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; -import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; @@ -54,8 +55,8 @@ public ResponseEntity> myTeamSimpleDetails( */ @GetMapping public ResponseEntity agendaTeamDetails(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamDetailsReqDto teamDetailsReqDto, @RequestParam("agenda_key") UUID agendaKey) { - TeamDetailsResDto teamDetailsResDto = agendaTeamService.detailsAgendaTeam(user, agendaKey, teamDetailsReqDto); + @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + TeamDetailsResDto teamDetailsResDto = agendaTeamService.detailsAgendaTeam(user, agendaKey, teamKeyReqDto); return ResponseEntity.ok(teamDetailsResDto); } @@ -70,4 +71,11 @@ public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) TeamCreateResDto teamCreateResDto = agendaTeamService.addAgendaTeam(user, teamCreateReqDto, agendaKey); return ResponseEntity.status(HttpStatus.CREATED).body(teamCreateResDto); } + + @PatchMapping("/confirm") + public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + agendaTeamService.confirmTeam(user, agendaKey, teamKeyReqDto); + return ResponseEntity.ok().build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamKeyReqDto.java similarity index 80% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamKeyReqDto.java index 8c007447d..80cac539d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamDetailsReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamKeyReqDto.java @@ -9,11 +9,11 @@ @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class TeamDetailsReqDto { +public class TeamKeyReqDto { @NotNull private UUID teamKey; - public TeamDetailsReqDto(UUID teamKey) { + public TeamKeyReqDto(UUID teamKey) { this.teamKey = teamKey; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index b0cecb953..d5dec63f3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -13,7 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; -import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; @@ -80,12 +80,12 @@ public Optional detailsMyTeamSimple(UserDto user, UUID agend * @return 만들어진 팀 상세 정보 */ @Transactional(readOnly = true) - public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamDetailsReqDto teamDetailsReqDto) { + public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamKeyReqDto teamKeyReqDto) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); AgendaTeam agendaTeam = agendaTeamRepository - .findByAgendaAndTeamKeyAndStatus(agenda, teamDetailsReqDto.getTeamKey(), OPEN, CONFIRM) + .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); List agendaTeamProfileList = agendaTeamProfileRepository @@ -145,4 +145,24 @@ public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateR agendaTeamProfileRepository.save(agendaTeamProfile); return new TeamCreateResDto(agendaTeam.getTeamKey().toString()); } + + @Transactional + public void confirmTeam(UserDto user, UUID agendaKey, TeamKeyReqDto teamKeyReqDto) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + + if (!agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + throw new ForbiddenException(TEAM_LEADER_FORBIDDEN); + } + if (agendaTeam.getMateCount() < agenda.getMinPeople()) { + throw new BusinessException(NOT_ENOUGH_TEAM_MEMBER); + } + agenda.checkAgenda(agendaTeam.getLocation(), LocalDateTime.now()); + agendaTeam.confirm(); + agendaTeamRepository.save(agendaTeam); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 42fe188d5..fa822ff6f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,7 +1,6 @@ package gg.agenda.api; import static gg.data.agenda.type.AgendaStatus.*; -import static gg.data.agenda.type.AgendaStatus.CONFIRM; import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Coalition.*; import static gg.data.agenda.type.Location.*; @@ -59,7 +58,7 @@ public Agenda createOfficialAgenda() { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(AgendaStatus.ON_GOING) + .status(ON_GOING) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -81,7 +80,7 @@ public Agenda createNonOfficialAgenda() { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(AgendaStatus.ON_GOING) + .status(ON_GOING) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -164,7 +163,7 @@ public List createAgendaHistory(int size) { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(CONFIRM) + .status(AgendaStatus.CONFIRM) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -308,6 +307,28 @@ public Agenda createAgenda(int curruentTeam) { return agendaRepository.save(agenda); } + public Agenda createNeedMorePeopleAgenda(int curruentTeam) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(1) + .maxTeam(5) + .currentTeam(curruentTeam) + .minPeople(3) + .maxPeople(5) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + public Agenda createAgenda(AgendaStatus status) { Agenda agenda = Agenda.builder() .title("title") @@ -453,6 +474,23 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, return agendaTeamRepository.save(agendaTeam); } + public AgendaTeam createAgendaTeam(int currentTeam, Agenda agenda, User user, Location location) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(location) + .mateCount(currentTeam) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, AgendaTeamStatus status, Boolean isPrivate) { AgendaTeam agendaTeam = AgendaTeam.builder() diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index a0b0c370b..c712c509b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -21,7 +21,7 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; -import gg.agenda.api.user.agendateam.controller.request.TeamDetailsReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.data.agenda.Agenda; @@ -443,7 +443,7 @@ public void teamDetailsGetSuccess() throws Exception { AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); agendaMockData.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); - TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( @@ -472,7 +472,7 @@ public void teamDetailsGetSuccess() throws Exception { @DisplayName("404 agenda가 없음으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByNoAgenda() throws Exception { //given - TeamDetailsReqDto req = new TeamDetailsReqDto(UUID.randomUUID()); + TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( @@ -490,7 +490,7 @@ public void teamDetailsGetFailByNoAgenda() throws Exception { public void teamDetailsGetFailByNoTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - TeamDetailsReqDto req = new TeamDetailsReqDto(UUID.randomUUID()); + TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( @@ -509,7 +509,7 @@ public void teamDetailsGetFailByConfirmTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); - TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( @@ -529,7 +529,7 @@ public void teamDetailsGetFailByCancelTeam() throws Exception { Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - TeamDetailsReqDto req = new TeamDetailsReqDto(team.getTeamKey()); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then String res = mockMvc.perform( @@ -638,4 +638,203 @@ public void noAgendaProfileFail() throws Exception { .andReturn().getResponse().getContentAsString(); } } + + @Nested + @DisplayName("팀 CONFIRM 테스트") + class ConfirmTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 팀 CONFIRM 성공") + public void confirmTeamSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assertThat(updatedTeam.getStatus()).isEqualTo(AgendaTeamStatus.CONFIRM); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + UUID noTeamKey = UUID.randomUUID(); + TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("404 team 없음으로 인한 실패") + public void noTeamFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 호스트가 아님으로 인한 실패") + public void notValidTeamHostFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(seoulUser.getIntraId()); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + User notHostUser = testDataUtils.createNewUser(); + String notHostUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notHostUser); + agendaMockData.createAgendaTeamProfile(team, agendaMockData.createAgendaProfile(notHostUser, SEOUL)); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + notHostUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("404 OPEN 상태가 아닌 팀으로 인한 실패 -> 서비스 로직에서 처리됨, 엔티티는 별개") + public void notValidTeamStatusFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CANCEL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("409 이미 CONFIRM된 팀으로 인한 실패") + public void alreadyConfirmTeamFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("403 참여 불가능한 Agenda Location 으로 인한 실패") + public void notValidAgendaLocation() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + + @Test + @DisplayName("400 참여 불가능한 Agenda Team 인원으로 인한 실패") + public void notValidAgendaTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createNeedMorePeopleAgenda(5); + AgendaTeam team = agendaMockData.createAgendaTeam(2, agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + User notHostUser = testDataUtils.createNewUser(); + agendaMockData.createAgendaTeamProfile(team, agendaMockData.createAgendaProfile(notHostUser, SEOUL)); + String content = objectMapper.writeValueAsString(req); + // when && then + String res = mockMvc.perform( + patch("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index d26f13b25..209aa1027 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -123,6 +123,13 @@ public void addTeam(Location location, LocalDateTime now) { this.currentTeam++; } + public void checkAgenda(Location location, LocalDateTime now) { + mustBeWithinLocation(location); + mustStatusOnGoing(); + mustBeforeDeadline(now); + mustHaveCapacity(); + } + public void confirm(LocalDateTime confirmTime) { if (this.status == AgendaStatus.CONFIRM) { throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 39fbe303a..3c29eb5b9 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -1,5 +1,7 @@ package gg.data.agenda; +import static gg.utils.exception.ErrorCode.*; + import java.util.UUID; import javax.persistence.Column; @@ -15,6 +17,7 @@ import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; +import gg.utils.exception.custom.BusinessException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -85,4 +88,14 @@ public void acceptAward(String award, int awardPriority) { this.award = award; this.awardPriority = awardPriority; } + + public void confirm() { + if (this.status == AgendaTeamStatus.CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + if (this.status == AgendaTeamStatus.CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } + this.status = AgendaTeamStatus.CONFIRM; + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index a303e330a..c533ade95 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -194,10 +194,14 @@ public enum ErrorCode { // agenda AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), + AGENDA_TEAM_ALREADY_CONFIRM(409, "AG", "이미 확정된 팀입니다."), + AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), + NOT_ENOUGH_TEAM_MEMBER(400, "AG", "팀원이 부족합니다."), AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), + TEAM_LEADER_FORBIDDEN(403, "AG", "팀장만 팀을 확정할 수 있습니다."), CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), From ea2a357f7259270de2bc251f33c2de1369e7a1e0 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:20:52 +0900 Subject: [PATCH 035/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#861=20#862=20A?= =?UTF-8?q?nnouncement=20=EC=83=9D=EC=84=B1=ED=95=98=EA=B8=B0=20API=20=20(?= =?UTF-8?q?#885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 10 +- .../user/agenda/service/AgendaService.java | 7 - .../AgendaAnnouncementController.java | 62 ++++ .../AgendaAnnouncementCreateReqDto.java | 47 +++ .../response/AgendaAnnouncementResDto.java | 40 +++ .../service/AgendaAnnouncementService.java | 38 +++ .../java/gg/agenda/api/AgendaMockData.java | 27 ++ .../agenda/service/AgendaServiceTest.java | 36 --- .../AgendaAnnouncementControllerTest.java | 300 ++++++++++++++++++ .../AgendaAnnouncementServiceTest.java | 94 ++++++ .../src/main/java/gg/data/agenda/Agenda.java | 6 + .../agenda/AgendaAnnouncementRepository.java | 9 + .../java/gg/utils/exception/ErrorCode.java | 2 +- 13 files changed, 629 insertions(+), 49 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/request/AgendaAnnouncementCreateReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/response/AgendaAnnouncementResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index ee444312b..4201c15d8 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -28,12 +28,12 @@ import gg.agenda.api.user.agenda.controller.response.AgendaResDto; import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; import gg.agenda.api.user.agenda.service.AgendaService; +import gg.agenda.api.user.agendaannouncement.service.AgendaAnnouncementService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.utils.dto.PageRequestDto; -import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.InvalidParameterException; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -46,6 +46,8 @@ public class AgendaController { private final AgendaService agendaService; + private final AgendaAnnouncementService agendaAnnouncementService; + @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Agenda 상세 조회 성공"), @@ -54,7 +56,7 @@ public class AgendaController { }) public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); - Optional announcement = agendaService.findAgendaWithLatestAnnouncement(agenda); + Optional announcement = agendaAnnouncementService.findAgendaWithLatestAnnouncement(agenda); String announcementTitle = announcement.map(AgendaAnnouncement::getTitle).orElse(""); AgendaResDto agendaResDto = AgendaResDto.MapStruct.INSTANCE.toDto(agenda, announcementTitle); return ResponseEntity.ok(agendaResDto); @@ -106,9 +108,7 @@ public ResponseEntity> agendaListHistory( public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user, @RequestBody(required = false) @Valid AgendaConfirmReqDto agendaConfirmReqDto) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); - if (!user.getIntraId().equals(agenda.getHostIntraId())) { - throw new ForbiddenException(CONFIRM_FORBIDDEN); - } + agenda.mustModifiedByHost(user.getIntraId()); if (agenda.getIsRanking() && agendaConfirmReqDto == null) { throw new InvalidParameterException(AGENDA_INVALID_PARAM); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index bf635a86f..6287515ee 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -33,8 +33,6 @@ public class AgendaService { private final AgendaRepository agendaRepository; - private final AgendaAnnouncementRepository agendaAnnouncementRepository; - private final AgendaTeamRepository agendaTeamRepository; @Transactional(readOnly = true) @@ -43,11 +41,6 @@ public Agenda findAgendaByAgendaKey(UUID agendaKey) { .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); } - @Transactional(readOnly = true) - public Optional findAgendaWithLatestAnnouncement(Agenda agenda) { - return agendaAnnouncementRepository.findLatestByAgenda(agenda); - } - @Transactional(readOnly = true) public List findCurrentAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING).stream() diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java new file mode 100644 index 000000000..d7fb6d70e --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java @@ -0,0 +1,62 @@ +package gg.agenda.api.user.agendaannouncement.controller; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.agenda.service.AgendaService; +import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; +import gg.agenda.api.user.agendaannouncement.controller.response.AgendaAnnouncementResDto; +import gg.agenda.api.user.agendaannouncement.service.AgendaAnnouncementService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import gg.data.agenda.Agenda; +import gg.utils.dto.PageRequestDto; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/agenda/announcement") +@RequiredArgsConstructor +public class AgendaAnnouncementController { + + private final AgendaService agendaService; + + private final AgendaAnnouncementService agendaAnnouncementService; + + @PostMapping + public ResponseEntity agendaAnnouncementAdd(@Login UserDto user, @RequestParam("agenda_key") UUID agendaKey, + @RequestBody @Valid AgendaAnnouncementCreateReqDto agendaAnnouncementCreateReqDto) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + agenda.mustModifiedByHost(user.getIntraId()); + agendaAnnouncementService.addAgendaAnnouncement(agendaAnnouncementCreateReqDto, agenda); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping + public ResponseEntity> agendaAnnouncementList( + @RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid PageRequestDto pageRequest) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List announceDto = agendaAnnouncementService + .findAnnouncementListByAgenda(pageable, agenda).stream() + .map(AgendaAnnouncementResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(announceDto); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/request/AgendaAnnouncementCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/request/AgendaAnnouncementCreateReqDto.java new file mode 100644 index 000000000..77a682f85 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/request/AgendaAnnouncementCreateReqDto.java @@ -0,0 +1,47 @@ +package gg.agenda.api.user.agendaannouncement.controller.request; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAnnouncementCreateReqDto { + + @NotNull + @NotEmpty + private String title; + + @NotNull + @NotEmpty + private String content; + + @Builder + public AgendaAnnouncementCreateReqDto(String title, String content) { + this.title = title; + this.content = content; + } + + @Mapper + public interface MapStruct { + + MapStruct INSTANCE = Mappers.getMapper(MapStruct.class); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "title", source = "dto.title") + @Mapping(target = "content", source = "dto.content") + @Mapping(target = "isShow", constant = "true") + @Mapping(target = "agenda", source = "agenda") + AgendaAnnouncement toEntity(AgendaAnnouncementCreateReqDto dto, Agenda agenda); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/response/AgendaAnnouncementResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/response/AgendaAnnouncementResDto.java new file mode 100644 index 000000000..97dc5f879 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/response/AgendaAnnouncementResDto.java @@ -0,0 +1,40 @@ +package gg.agenda.api.user.agendaannouncement.controller.response; + +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.AgendaAnnouncement; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAnnouncementResDto { + + long id; + + String title; + + String content; + + LocalDateTime createdAt; + + @Builder + public AgendaAnnouncementResDto(long id, String title, String content, LocalDateTime createdAt) { + this.id = id; + this.title = title; + this.content = content; + this.createdAt = createdAt; + } + + @Mapper + public interface MapStruct { + MapStruct INSTANCE = Mappers.getMapper(MapStruct.class); + + AgendaAnnouncementResDto toDto(AgendaAnnouncement announcement); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java new file mode 100644 index 000000000..02057d1f9 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java @@ -0,0 +1,38 @@ +package gg.agenda.api.user.agendaannouncement.service; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.repo.agenda.AgendaAnnouncementRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaAnnouncementService { + + private final AgendaAnnouncementRepository agendaAnnouncementRepository; + + @Transactional + public void addAgendaAnnouncement(AgendaAnnouncementCreateReqDto announceCreateDto, Agenda agenda) { + AgendaAnnouncement newAnnounce = AgendaAnnouncementCreateReqDto + .MapStruct.INSTANCE.toEntity(announceCreateDto, agenda); + agendaAnnouncementRepository.save(newAnnounce); + } + + @Transactional(readOnly = true) + public List findAnnouncementListByAgenda(Pageable pageable, Agenda agenda) { + return agendaAnnouncementRepository.findListByAgenda(pageable, agenda); + } + + @Transactional(readOnly = true) + public Optional findAgendaWithLatestAnnouncement(Agenda agenda) { + return agendaAnnouncementRepository.findLatestByAgenda(agenda); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index fa822ff6f..b5f400e65 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -7,6 +7,7 @@ import static java.util.UUID.*; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -151,6 +152,32 @@ public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { return announcement; } + public List createAgendaAnnouncementList(Agenda agenda, int size) { + List announcements = new ArrayList<>(); + for (int i = 0; i < size; i++) { + announcements.add(AgendaAnnouncement.builder() + .title("title " + i) + .content("content " + i) + .isShow(true) + .agenda(agenda) + .build()); + } + return agendaAnnouncementRepository.saveAll(announcements); + } + + public List createAgendaAnnouncementList(Agenda agenda, int size, boolean isShow) { + List announcements = new ArrayList<>(); + for (int i = 0; i < size; i++) { + announcements.add(AgendaAnnouncement.builder() + .title("title " + i) + .content("content " + i) + .isShow(isShow) + .agenda(agenda) + .build()); + } + return agendaAnnouncementRepository.saveAll(announcements); + } + public List createAgendaHistory(int size) { List agendas = IntStream.range(0, size).mapToObj(i -> Agenda.builder() .title("title " + UUID.randomUUID()) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 5113e0523..50717c176 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -28,7 +28,6 @@ import gg.agenda.api.user.agenda.controller.request.AgendaTeamAwardDto; import gg.auth.UserDto; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaAnnouncementRepository; @@ -45,9 +44,6 @@ class AgendaServiceTest { @Mock AgendaRepository agendaRepository; - @Mock - AgendaAnnouncementRepository agendaAnnouncementRepository; - @Mock AgendaTeamRepository agendaTeamRepository; @@ -84,38 +80,6 @@ void findAgendaByAgendaKeyFailedWithNoAgenda() { assertThrows(NotExistException.class, () -> agendaService.findAgendaByAgendaKey(agendaKey)); } - - @Test - @DisplayName("AgendaAnnouncement 조회 성공 - 최신 공지사항이 있는 경우") - void findAgendaAnnouncementSuccess() { - // given - Agenda agenda = Agenda.builder().build(); - AgendaAnnouncement announcement = AgendaAnnouncement.builder().title("title").content("content").build(); - when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.of(announcement)); - - // when - Optional result = agendaService.findAgendaWithLatestAnnouncement(agenda); - - // then - verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); - assertThat(result).isPresent(); - assertThat(result.get().getTitle()).isEqualTo(announcement.getTitle()); - } - - @Test - @DisplayName("AgendaAnnouncement 조회 성공 - 최신 공지사항이 없는 경우") - void findAgendaAnnouncementSuccessWithNoAnnounce() { - // given - Agenda agenda = Agenda.builder().build(); - when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.empty()); - - // when - Optional result = agendaService.findAgendaWithLatestAnnouncement(agenda); - - // then - verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); - assertThat(result).isEmpty(); - } } @Nested diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java new file mode 100644 index 000000000..b5356c309 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java @@ -0,0 +1,300 @@ +package gg.agenda.api.user.agendaannouncement.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.awt.print.Pageable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; +import gg.agenda.api.user.agendaannouncement.controller.response.AgendaAnnouncementResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.user.User; +import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaAnnouncementControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaMockData agendaMockData; + + @Autowired + EntityManager em; + + @Autowired + AgendaAnnouncementRepository agendaAnnouncementRepository; + + private User user; + + private String accessToken; + + @BeforeEach + void setUp() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("AgendaAnnouncement 생성") + class CreateAgendaAnnouncement { + + @Test + @DisplayName("AgendaAnnouncement 생성 성공") + void createAgendaAnnouncementSuccess() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").content("content").build(); + String request = objectMapper.writeValueAsString(dto); + + // when + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isCreated()); + Optional latestAnnounce = agendaAnnouncementRepository.findLatestByAgenda(agenda); + + // then + assertThat(latestAnnounce).isPresent(); + latestAnnounce.ifPresent(announcement -> assertThat(announcement.getTitle()).isEqualTo(dto.getTitle())); + latestAnnounce.ifPresent(announcement -> assertThat(announcement.getContent()).isEqualTo(dto.getContent())); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - title이 null인 경우") + void createAgendaAnnouncementFailedWithNoTitle() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .content("content").build(); // title이 null인 경우 + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - title이 빈 문자열 경우") + void createAgendaAnnouncementFailedWithEmptyTitle() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("").content("content").build(); // title이 empty인 경우 + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - Content가 null인 경우") + void createAgendaAnnouncementFailedWithNoContent() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").build(); // content가 null인 경우 + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - Content가 빈 문자열 경우") + void createAgendaAnnouncementFailedWithEmptyContent() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").content("").build(); // content가 empty인 경우 + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - Agenda가 없는 경우") + void createAgendaAnnouncementFailedWithNoAgenda() throws Exception { + // given + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").content("content").build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", UUID.randomUUID().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("AgendaAnnouncement 생성 실패 - 개최자가 아닌 경우") + void createAgendaAnnouncementFailedWithNotHost() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda("another"); // 다른 사용자가 생성한 Agenda + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").content("content").build(); + String request = objectMapper.writeValueAsString(dto); + + // expected + mockMvc.perform(post("/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); + } + } + + @Nested + @DisplayName("AgendaAnnouncement 전체 조회") + class GetAgendaAnnouncementList { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3}) + @DisplayName("AgendaAnnouncement 전체 조회 성공") + void getAgendaAnnouncementListSuccess(int page) throws Exception { + // given + int total = 35; + int size = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + agendaMockData.createAgendaAnnouncementList(agenda, 30, false); + List announcements = agendaMockData + .createAgendaAnnouncementList(agenda, total, true); + PageRequestDto pageRequest = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequest); + + // when + String response = mockMvc.perform(get("/agenda/announcement") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + + // then + assertThat(result).hasSize(size * page < total ? size : total % size); + announcements.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); + assertThat(result[i].getTitle()).isEqualTo(announcements.get(i + (page - 1) * size).getTitle()); + assertThat(result[i].getContent()).isEqualTo(announcements.get(i + (page - 1) * size).getContent()); + if (i == 0) { + continue; + } + assertThat(result[i].getId()).isLessThan(result[i - 1].getId()); + } + } + + @Test + @DisplayName("AgendaAnnouncement 전체 조회 성공 - 데이터 없는 경우") + void getAgendaAnnouncementListSuccessWhenNoEntity() throws Exception { + // given + int page = 1; + int size = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + agendaMockData.createAgendaAnnouncementList(agenda, 30, false); + PageRequestDto pageRequest = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequest); + + // when + String response = mockMvc.perform(get("/agenda/announcement") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + + // then + assertThat(result).hasSize(0); + } + + @Test + @DisplayName("AgendaAnnouncement 전체 조회 실패 - Agenda가 없는 경우") + void getAgendaAnnouncementListFailedWithInvalidAgenda() throws Exception { + // given + int page = 1; + int size = 10; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); + agendaMockData.createAgendaAnnouncementList(agenda, 30, false); + agendaMockData.createAgendaAnnouncementList(agenda, 30, true); + PageRequestDto pageRequest = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequest); + + // when + mockMvc.perform(get("/agenda/announcement") + .param("agenda_key", UUID.randomUUID().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java new file mode 100644 index 000000000..da6e81ef2 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java @@ -0,0 +1,94 @@ +package gg.agenda.api.user.agendaannouncement.service; + +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.Pageable; + +import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.utils.annotation.UnitTest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@UnitTest +public class AgendaAnnouncementServiceTest { + + @Mock + private AgendaAnnouncementRepository agendaAnnouncementRepository; + + @InjectMocks + private AgendaAnnouncementService agendaAnnouncementService; + + @Nested + @DisplayName("AgendaAnnouncement 생성") + class CreateAgendaAnnouncement { + + @Test + @DisplayName("AgendaAnnouncement 생성 성공") + void createAgendaAnnouncementSuccess() { + // given + Agenda agenda = mock(Agenda.class); + AgendaAnnouncement newAnnounce = mock(AgendaAnnouncement.class); + AgendaAnnouncementCreateReqDto dto = AgendaAnnouncementCreateReqDto.builder() + .title("title").content("content").build(); + when(agendaAnnouncementRepository.save(any())).thenReturn(newAnnounce); + + // when + agendaAnnouncementService.addAgendaAnnouncement(dto, agenda); + + // then + verify(agendaAnnouncementRepository, times(1)).save(any()); + } + } + + @Nested + @DisplayName("AgendaAnnouncement 전체 조회") + class GetAgendaAnnouncementList { + + @Test + @DisplayName("AgendaAnnouncement 전체 조회 성공") + void getAgendaAnnouncementListSuccess() { + // given + Agenda agenda = mock(Agenda.class); + Pageable pageable = mock(Pageable.class); + when(agendaAnnouncementRepository.findListByAgenda(pageable, agenda)) + .thenReturn(List.of()); + + // when + agendaAnnouncementService.findAnnouncementListByAgenda(pageable, agenda); + + // then + verify(agendaAnnouncementRepository, times(1)).findListByAgenda(pageable, agenda); + } + } + + @Nested + @DisplayName("마지막 AgendaAnnouncement 조회") + class GetAgendaAnnouncementLatest { + + @Test + @DisplayName("마지막 AgendaAnnouncement 조회 성공") + void getAgendaAnnouncementLatestSuccess() { + // given + Agenda agenda = mock(Agenda.class); + AgendaAnnouncement announcement = mock(AgendaAnnouncement.class); + when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.of(announcement)); + + // when + agendaAnnouncementService.findAgendaWithLatestAnnouncement(agenda); + + // then + verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); + } + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 209aa1027..523b73589 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -166,4 +166,10 @@ private void mustHaveCapacity() { throw new ForbiddenException(AGENDA_NO_CAPACITY); } } + + public void mustModifiedByHost(String userIntraId) { + if (!this.hostIntraId.equals(userIntraId)) { + throw new ForbiddenException(AGENDA_MODIFICATION_FORBIDDEN); + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java index 834349f7d..26a3111fd 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java @@ -1,8 +1,11 @@ package gg.repo.agenda; +import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; @@ -11,7 +14,13 @@ public interface AgendaAnnouncementRepository extends JpaRepository findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(Agenda agenda); + List findAllByAgendaAndIsShowIsTrueOrderByIdDesc(Pageable pageable, Agenda agenda); + default Optional findLatestByAgenda(Agenda agenda) { return findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(agenda); } + + default List findListByAgenda(Pageable pageable, Agenda agenda) { + return findAllByAgendaAndIsShowIsTrueOrderByIdDesc(pageable, agenda); + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index c533ade95..f0018687e 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -202,7 +202,7 @@ public enum ErrorCode { AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), TEAM_LEADER_FORBIDDEN(403, "AG", "팀장만 팀을 확정할 수 있습니다."), - CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), + AGENDA_MODIFICATION_FORBIDDEN(403, "AG", "개최자만 일정을 수정할 수 있습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), From e44d6feefba6d38efd69825e5d596c156e103fbb Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:14:31 +0900 Subject: [PATCH 036/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#886=20Admin=20?= =?UTF-8?q?=EB=8C=80=ED=9A=8C=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20(#890)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/agenda/AgendaAdminRepository.java | 10 ++ .../controller/AgendaAdminController.java | 45 ++++++ .../response/AgendaAdminResDto.java | 84 +++++++++++ .../agenda/service/AgendaAdminService.java | 22 +++ .../controller/AgendaAdminControllerTest.java | 132 ++++++++++++++++++ .../service/AgendaAdminServiceTest.java | 71 ++++++++++ 6 files changed, 364 insertions(+) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java new file mode 100644 index 000000000..58a4a9ace --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java @@ -0,0 +1,10 @@ +package gg.admin.repo.agenda; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.Agenda; + +@Repository +public interface AgendaAdminRepository extends JpaRepository { +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java new file mode 100644 index 000000000..58dc62d75 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -0,0 +1,45 @@ +package gg.agenda.api.admin.agenda.controller; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; +import gg.agenda.api.admin.agenda.service.AgendaAdminService; +import gg.utils.dto.PageRequestDto; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/admin/agenda") +@RequiredArgsConstructor +public class AgendaAdminController { + + private final AgendaAdminService agendaAdminService; + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공") + }) + @GetMapping("/request/list") + public ResponseEntity> getAgendaRequestList( + @RequestBody @Valid PageRequestDto pageDto) { + int page = pageDto.getPage(); + int size = pageDto.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List agendaDtos = agendaAdminService.getAgendaRequestList(pageable).stream() + .map(AgendaAdminResDto.MapStruct.INSTANCE::toAgendaAdminResDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(agendaDtos); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java new file mode 100644 index 000000000..f0b2fdb3b --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java @@ -0,0 +1,84 @@ +package gg.agenda.api.admin.agenda.controller.response; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAdminResDto { + + private Long agendaId; + + private String agendaTitle; + + private String agendaDeadLine; + + private String agendaStartTime; + + private String agendaEndTime; + + private int agendaCurrentTeam; + + private int agendaMaxTeam; + + private int agendaMinPeople; + + private int agendaMaxPeople; + + private Location agendaLocation; + + private Boolean isRanking; + + private Boolean isOfficial; + + private AgendaStatus agendaStatus; + + @Builder + public AgendaAdminResDto(Long agendaId, String agendaTitle, String agendaDeadLine, String agendaStartTime, + String agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, + Location agendaLocation, Boolean isRanking, Boolean isOfficial, AgendaStatus agendaStatus) { + this.agendaId = agendaId; + this.agendaTitle = agendaTitle; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaCurrentTeam = agendaCurrentTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + this.agendaLocation = agendaLocation; + this.isRanking = isRanking; + this.isOfficial = isOfficial; + this.agendaStatus = agendaStatus; + } + + @Mapper + public interface MapStruct { + + AgendaAdminResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaAdminResDto.MapStruct.class); + + @Mapping(target = "agendaId", source = "id") + @Mapping(target = "agendaTitle", source = "title") + @Mapping(target = "agendaDeadLine", source = "deadline") + @Mapping(target = "agendaStartTime", source = "startTime") + @Mapping(target = "agendaEndTime", source = "endTime") + @Mapping(target = "agendaCurrentTeam", source = "currentTeam") + @Mapping(target = "agendaMaxTeam", source = "maxTeam") + @Mapping(target = "agendaMinPeople", source = "minPeople") + @Mapping(target = "agendaMaxPeople", source = "maxPeople") + @Mapping(target = "agendaLocation", source = "location") + @Mapping(target = "isRanking", source = "isRanking") + @Mapping(target = "isOfficial", source = "isOfficial") + @Mapping(target = "agendaStatus", source = "status") + AgendaAdminResDto toAgendaAdminResDto(Agenda agenda); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java new file mode 100644 index 000000000..d6aecd645 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -0,0 +1,22 @@ +package gg.agenda.api.admin.agenda.service; + +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.data.agenda.Agenda; +import gg.repo.agenda.AgendaRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaAdminService { + + private final AgendaAdminRepository agendaAdminRepository; + + public List getAgendaRequestList(Pageable pageable) { + return agendaAdminRepository.findAll(pageable).getContent(); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java new file mode 100644 index 000000000..d0b768dd8 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -0,0 +1,132 @@ +package gg.agenda.api.admin.agenda.controller; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.test.web.servlet.MockMvcBuilder.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MockMvcBuilder; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import gg.data.user.User; +import gg.repo.agenda.AgendaRepository; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaAdminControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaMockData agendaMockData; + + @Autowired + EntityManager em; + + @Autowired + AgendaRepository agendaRepository; + + private User user; + + private String accessToken; + + @BeforeEach + void setUp() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("Admin Agenda 상세 조회") + class GetAgendaAdmin { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("Admin Agenda 상세 조회 성공") + void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { + // given + int size = 10; + List agendas = new ArrayList<>(); + agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.ON_GOING)); + agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.CONFIRM)); + agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.CANCEL)); + agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.ON_GOING)); + agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.CONFIRM)); + agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.CANCEL)); + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/admin/agenda/request/list") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + + // then + assertThat(result).hasSize(((page - 1) * size) < agendas.size() + ? Math.min(size, agendas.size() - (page - 1) * size) : 0); + agendas.sort((a, b) -> b.getId().compareTo(a.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getAgendaId()).isEqualTo(agendas.get(i + (page - 1) * size).getId()); + } + } + + @Test + @DisplayName("Admin Agenda 상세 조회 성공 - 대회가 존재하지 않는 경우") + void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { + // given + int page = 1; + int size = 10; + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/admin/agenda/request/list") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + + // then + assertThat(result).isEmpty(); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java new file mode 100644 index 000000000..928f02355 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -0,0 +1,71 @@ +package gg.agenda.api.admin.agenda.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.data.agenda.Agenda; +import gg.utils.annotation.UnitTest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@UnitTest +public class AgendaAdminServiceTest { + + @Mock + AgendaAdminRepository agendaAdminRepository; + + @InjectMocks + AgendaAdminService agendaAdminService; + + @Nested + @DisplayName("Admin Agenda 상세 조회") + class GetAgendaAdmin { + + @Test + @DisplayName("Admin Agenda 상세 조회 성공") + void findAgendaByAgendaKeySuccessAdmin() { + // given + Pageable pageable = mock(Pageable.class); + List agendas = new ArrayList<>(); + agendas.add(Agenda.builder().build()); + Page page = new PageImpl<>(agendas); + when(agendaAdminRepository.findAll(pageable)).thenReturn(page); + + // when + List result = agendaAdminService.getAgendaRequestList(pageable); + + // then + verify(agendaAdminRepository, times(1)).findAll(pageable); + assertThat(result).isNotEmpty(); + } + + @Test + @DisplayName("Admin Agenda 상세 조회 성공 - 빈 리스트인 경우") + void findAgendaByAgendaKeySuccessAdminWithNoContent() { + // given + Pageable pageable = mock(Pageable.class); + Page page = new PageImpl<>(List.of()); + when(agendaAdminRepository.findAll(pageable)).thenReturn(page); + + // when + List result = agendaAdminService.getAgendaRequestList(pageable); + + // then + verify(agendaAdminRepository, times(1)).findAll(pageable); + assertThat(result).isEmpty(); + } + } +} From dd902d372fc4a64d2cece71364107843a25bdc18 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:34:16 +0900 Subject: [PATCH 037/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#845=20Team=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=ED=95=98=EA=B8=B0=20API=20(#892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AgendaProfileFindService.java | 2 +- .../controller/AgendaTeamController.java | 27 +- .../controller/request/TeamCreateReqDto.java | 2 + ...amCreateResDto.java => TeamKeyResDto.java} | 4 +- .../agendateam/service/AgendaTeamService.java | 69 +++- .../user/ticket/service/TicketService.java | 37 ++ .../java/gg/agenda/api/AgendaMockData.java | 45 ++- .../agendateam/AgendaTeamControllerTest.java | 319 +++++++++++++----- .../src/main/java/gg/data/agenda/Agenda.java | 7 +- .../main/java/gg/data/agenda/AgendaTeam.java | 15 + .../gg/data/agenda/AgendaTeamProfile.java | 4 + .../src/main/java/gg/data/agenda/Ticket.java | 45 ++- .../resources/db/migration/V3__agenda.sql | 10 +- .../java/gg/repo/agenda/TicketRepository.java | 4 +- .../java/gg/utils/exception/ErrorCode.java | 2 + 15 files changed, 483 insertions(+), 109 deletions(-) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/{TeamCreateResDto.java => TeamKeyResDto.java} (75%) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 0876f9f2e..0616bc451 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -37,7 +37,7 @@ public AgendaProfileDetailsResDto getAgendaProfileDetails(@Login UserDto user) { AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApproveTrue(agendaProfile.getId()) + int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(agendaProfile.getId()) .size(); return new AgendaProfileDetailsResDto(loginUser, agendaProfile, ticketCount); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 7ae3618b7..487a2c5ee 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -18,9 +18,10 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; -import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import io.swagger.v3.oas.annotations.Parameter; @@ -31,6 +32,7 @@ @RequestMapping("/agenda/team") public class AgendaTeamController { private final AgendaTeamService agendaTeamService; + private final TicketService ticketService; /** * 내 팀 간단 정보 조회 @@ -66,16 +68,31 @@ public ResponseEntity agendaTeamDetails(@Parameter(hidden = t * @return 만들어진 팀 KEY */ @PostMapping - public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Login UserDto user, + public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Login UserDto user, @RequestBody @Valid TeamCreateReqDto teamCreateReqDto, @RequestParam("agenda_key") UUID agendaKey) { - TeamCreateResDto teamCreateResDto = agendaTeamService.addAgendaTeam(user, teamCreateReqDto, agendaKey); - return ResponseEntity.status(HttpStatus.CREATED).body(teamCreateResDto); + TeamKeyResDto teamKeyReqDto = agendaTeamService.addAgendaTeam(user, teamCreateReqDto, agendaKey); + return ResponseEntity.status(HttpStatus.CREATED).body(teamKeyReqDto); } + /** + * 아젠다 팀 확정하기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ @PatchMapping("/confirm") public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto user, @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - agendaTeamService.confirmTeam(user, agendaKey, teamKeyReqDto); + agendaTeamService.confirmTeam(user, agendaKey, teamKeyReqDto.getTeamKey()); return ResponseEntity.ok().build(); } + + /** + * 아젠다 팀 나가기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ + @PatchMapping("/cancel") + public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + agendaTeamService.agendaTeamLeave(user, agendaKey, teamKeyReqDto.getTeamKey()); + return ResponseEntity.noContent().build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java index 10c46affc..6550f5f3e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java @@ -13,8 +13,10 @@ import gg.data.agenda.type.Location; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class TeamCreateReqDto { @NotBlank @Size(max = 30) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamKeyResDto.java similarity index 75% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamKeyResDto.java index 29a18ade3..04814be7b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamCreateResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamKeyResDto.java @@ -5,10 +5,10 @@ @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class TeamCreateResDto { +public class TeamKeyResDto { String teamKey; - public TeamCreateResDto(String teamKey) { + public TeamKeyResDto(String teamKey) { this.teamKey = teamKey; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index d5dec63f3..74f998000 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -10,13 +10,15 @@ import java.util.stream.Collectors; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; -import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -39,6 +41,7 @@ @Service @RequiredArgsConstructor public class AgendaTeamService { + private final TicketService ticketService; private final AgendaRepository agendaRepository; private final TicketRepository ticketRepository; private final AgendaTeamRepository agendaTeamRepository; @@ -106,7 +109,7 @@ public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamKey * @return 만들어진 팀 KEY */ @Transactional - public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqDto, UUID agendaKey) { + public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqDto, UUID agendaKey) { AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); @@ -128,9 +131,9 @@ public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateR }); if (agenda.getIsOfficial()) { - Ticket ticket = ticketRepository.findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(agendaProfile) + Ticket ticket = ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(agendaProfile) .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); - ticket.useTicket(); + ticket.useTicket(agenda.getAgendaKey()); } agendaTeamRepository.findByAgendaAndTeamNameAndStatus(agenda, teamCreateReqDto.getTeamName(), OPEN, CONFIRM) @@ -143,16 +146,20 @@ public TeamCreateResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateR agendaRepository.save(agenda); agendaTeamRepository.save(agendaTeam); agendaTeamProfileRepository.save(agendaTeamProfile); - return new TeamCreateResDto(agendaTeam.getTeamKey().toString()); + return new TeamKeyResDto(agendaTeam.getTeamKey().toString()); } + /** + * 아젠다 팀 확정하기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ @Transactional - public void confirmTeam(UserDto user, UUID agendaKey, TeamKeyReqDto teamKeyReqDto) { + public void confirmTeam(UserDto user, UUID agendaKey, UUID teamKey) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); AgendaTeam agendaTeam = agendaTeamRepository - .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) + .findByAgendaAndTeamKeyAndStatus(agenda, teamKey, OPEN, CONFIRM) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); if (!agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { @@ -161,8 +168,54 @@ public void confirmTeam(UserDto user, UUID agendaKey, TeamKeyReqDto teamKeyReqDt if (agendaTeam.getMateCount() < agenda.getMinPeople()) { throw new BusinessException(NOT_ENOUGH_TEAM_MEMBER); } - agenda.checkAgenda(agendaTeam.getLocation(), LocalDateTime.now()); + agenda.confirmTeam(agendaTeam.getLocation(), LocalDateTime.now()); agendaTeam.confirm(); agendaTeamRepository.save(agendaTeam); } + + /** + * 아젠다 팀 나가기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + * 트랜잭션의 원자성을 보장하기 위해 팀 나가기와 티켓 환불을 한 메서드에서 처리 + */ + @Transactional + public void agendaTeamLeave(UserDto user, UUID agendaKey, UUID teamKey) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndTeamKeyAndStatus(agenda, teamKey, OPEN, CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + + agenda.cancelTeam(LocalDateTime.now()); + + List profiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); + List changedProfiles = agendaTeamProfileLeave(user, agendaTeam, profiles); + ticketService.refundTickets(changedProfiles, agendaKey); + } + + /** + * 아젠다 팀 나가기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + * @Annotation 트랜잭션의 원자성을 보장하기 위해 부모 트랜잭션이 없을경우 예외를 발생시키는 Propagation.MANDATORY로 설정 + */ + @Transactional(propagation = Propagation.MANDATORY) + public List agendaTeamProfileLeave(UserDto user, AgendaTeam agendaTeam, + List profiles) { + if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + List changedProfiles = profiles + .stream() + .peek(AgendaTeamProfile::leaveTeam) + .map(AgendaTeamProfile::getProfile) + .collect(Collectors.toList()); + agendaTeam.leaveTeamLeader(); + return changedProfiles; + } + AgendaTeamProfile teamMate = profiles.stream() + .filter(profile -> profile.getProfile().getUserId().equals(user.getId())) + .findFirst().orElseThrow(() -> new ForbiddenException(NOT_TEAM_MATE)); + teamMate.leaveTeam(); + agendaTeam.leaveTeamMate(); + return List.of(teamMate.getProfile()); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java new file mode 100644 index 000000000..c638ebfee --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -0,0 +1,37 @@ +package gg.agenda.api.user.ticket.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.repo.agenda.TicketRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TicketService { + private final TicketRepository ticketRepository; + + /** + * 티켓 환불 + * @param changedProfiles 변경된 프로필 목록 + * @param agendaKey 아젠다 키 + * @Annotation 트랜잭션의 원자성을 보장하기 위해 부모 트랜잭션이 없을경우 예외를 발생시키는 Propagation.MANDATORY로 설정 + */ + @Transactional(propagation = Propagation.MANDATORY) + public void refundTickets(List changedProfiles, UUID agendaKey) { + List tickets = new ArrayList<>(); + for ( + AgendaProfile profile : changedProfiles) { + Ticket ticket = Ticket.refundTicket(profile, agendaKey); + tickets.add(ticket); + } + ticketRepository.saveAll(tickets); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index b5f400e65..119b68885 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -290,6 +290,28 @@ public Agenda createAgenda(String intraId, LocalDateTime startTime, AgendaStatus return agendaRepository.save(agenda); } + public Agenda createAgenda(LocalDateTime deadline) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(deadline) + .startTime(deadline.plusDays(1)) + .endTime(deadline.plusDays(2)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + public Agenda createAgenda(Location location) { Agenda agenda = Agenda.builder() .title("title") @@ -393,8 +415,12 @@ public AgendaProfile createAgendaProfile(User user, Location location) { public Ticket createTicket(AgendaProfile agendaProfile) { Ticket ticket = Ticket.builder() .agendaProfile(agendaProfile) - .isApprove(true) + .issuedFrom(null) + .usedTo(null) + .isApproved(true) + .approvedAt(LocalDateTime.now().minusDays(1)) .isUsed(false) + .usedAt(null) .build(); return ticketRepository.save(ticket); } @@ -467,6 +493,23 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user) { return agendaTeamRepository.save(agendaTeam); } + public AgendaTeam createAgendaTeam(Agenda agenda, User user, int mateCount) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(SEOUL) + .mateCount(mateCount) + .award("award") + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location) { AgendaTeam agendaTeam = AgendaTeam.builder() .agenda(agenda) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index c712c509b..fcdad5f6b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -5,6 +5,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.time.LocalDateTime; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; @@ -22,8 +23,8 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; -import gg.agenda.api.user.agendateam.controller.response.TeamCreateResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; +import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; @@ -32,7 +33,9 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.user.User; +import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; +import gg.repo.agenda.TicketRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; @@ -49,13 +52,20 @@ public class AgendaTeamControllerTest { @Autowired private AgendaMockData agendaMockData; @Autowired - AgendaTeamRepository agendaTeamRepository; + private TicketRepository ticketRepository; + @Autowired + private AgendaTeamRepository agendaTeamRepository; + @Autowired + private AgendaTeamProfileRepository agendaTeamProfileRepository; User seoulUser; User gyeongsanUser; + User anotherSeoulUser; String seoulUserAccessToken; String gyeongsanUserAccessToken; + String anotherSeoulUserAccessToken; AgendaProfile seoulUserAgendaProfile; AgendaProfile gyeongsanUserAgendaProfile; + AgendaProfile anotherSeoulUserAgendaProfile; @Nested @DisplayName("팀 생성 테스트") @@ -88,7 +98,7 @@ public void addNewTeamStatusSeoul() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -116,7 +126,7 @@ public void addNewTeamStatusGyeongsan() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -144,7 +154,7 @@ public void addNewTeamStatusMixFromSeoul() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -172,7 +182,7 @@ public void addNewTeamStatusMixFromGyeongsan() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -200,7 +210,7 @@ public void addNewTeamStatusMixFromMixToSeoul() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -228,7 +238,7 @@ public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); - TeamCreateResDto result = objectMapper.readValue(res, TeamCreateResDto.class); + TeamKeyResDto result = objectMapper.readValue(res, TeamKeyResDto.class); // then AgendaTeam createdTeam = agendaTeamRepository.findByTeamKey(UUID.fromString(result.getTeamKey())) .orElse(null); @@ -248,14 +258,13 @@ public void noAgendaFail() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -268,14 +277,13 @@ public void notValidAgendaLocation() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -288,14 +296,13 @@ public void notValidAgendaStatus() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -308,14 +315,13 @@ public void notValidAgendaTeam() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isForbidden()); } @Test @@ -328,14 +334,13 @@ public void notValidAgendaDeadline() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -348,14 +353,13 @@ public void agendaHostFail() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isForbidden()); } @Test @@ -368,14 +372,13 @@ public void notValidUserLocation() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -389,14 +392,13 @@ public void alreadyTeamNameExist() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isConflict()); } @Test @@ -411,14 +413,13 @@ public void alreadyTeamExistForAgenda() throws Exception { "teamContent"); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( post("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isConflict()); } } @@ -475,14 +476,13 @@ public void teamDetailsGetFailByNoAgenda() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", UUID.randomUUID().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -493,14 +493,13 @@ public void teamDetailsGetFailByNoTeam() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -512,14 +511,13 @@ public void teamDetailsGetFailByConfirmTeam() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isForbidden()); } @Test @@ -532,14 +530,13 @@ public void teamDetailsGetFailByCancelTeam() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } } @@ -614,12 +611,11 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team/my") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString())) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -630,12 +626,11 @@ public void noAgendaProfileFail() throws Exception { User noProfileUser = testDataUtils.createNewUser(); String noProfileUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(noProfileUser); // when && then - String res = mockMvc.perform( + mockMvc.perform( get("/agenda/team/my") .header("Authorization", "Bearer " + noProfileUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString())) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } } @@ -662,14 +657,13 @@ public void confirmTeamSuccess() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isOk()); // then AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); assertThat(updatedTeam.getStatus()).isEqualTo(AgendaTeamStatus.CONFIRM); @@ -684,14 +678,13 @@ public void noAgendaFail() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -702,14 +695,13 @@ public void noTeamFail() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -725,14 +717,13 @@ public void notValidTeamHostFail() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + notHostUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isForbidden()); } @Test @@ -745,14 +736,13 @@ public void notValidTeamStatusFail() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isNotFound()); } @Test @@ -765,14 +755,13 @@ public void alreadyConfirmTeamFail() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isConflict()); } @Test @@ -785,14 +774,13 @@ public void notValidAgendaStatus() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -805,14 +793,13 @@ public void notValidAgendaLocation() throws Exception { TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); } @Test @@ -827,14 +814,186 @@ public void notValidAgendaTeam() throws Exception { agendaMockData.createAgendaTeamProfile(team, agendaMockData.createAgendaProfile(notHostUser, SEOUL)); String content = objectMapper.writeValueAsString(req); // when && then - String res = mockMvc.perform( + mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andReturn().getResponse().getContentAsString(); + .andExpect(status().isBadRequest()); + } + } + + @Nested + @DisplayName("팀 Leave 테스트") + class LeaveTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + anotherSeoulUser = testDataUtils.createNewUser(); + anotherSeoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(anotherSeoulUser); + anotherSeoulUserAgendaProfile = agendaMockData.createAgendaProfile(anotherSeoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 팀원 팀 나가기 성공") + public void leaveTeamMateSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assertThat(updatedTeam.getMateCount()).isEqualTo(1); + AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assertThat(updatedAtp.getIsExist()).isFalse(); + ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + } + + @Test + @DisplayName("200 팀리더 팀 나가기 성공") + public void leaveTeamLeaderSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + AgendaTeamProfile atpLeader = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assertThat(updatedTeam.getMateCount()).isEqualTo(0); + AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assertThat(updatedAtp.getIsExist()).isFalse(); + AgendaTeamProfile updatedAtpLeader = agendaTeamProfileRepository.findById(atpLeader.getId()).orElse(null); + assertThat(updatedAtpLeader.getIsExist()).isFalse(); + ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtpLeader.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + UUID noTeamKey = UUID.randomUUID(); + TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("404 team 없음으로 인한 실패") + public void noTeamFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("400 탈퇴 불가능한 Agenda 시간으로 인한 실패") + public void notValidAgendaDeadline() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(LocalDateTime.now().minusHours(1)); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("403 팀원이 아님으로 인한 실패") + public void notTeamMateFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); } } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 523b73589..c568b53cf 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -123,13 +123,18 @@ public void addTeam(Location location, LocalDateTime now) { this.currentTeam++; } - public void checkAgenda(Location location, LocalDateTime now) { + public void confirmTeam(Location location, LocalDateTime now) { mustBeWithinLocation(location); mustStatusOnGoing(); mustBeforeDeadline(now); mustHaveCapacity(); } + public void cancelTeam(LocalDateTime now) { + mustStatusOnGoing(); + mustBeforeDeadline(now); + } + public void confirm(LocalDateTime confirmTime) { if (this.status == AgendaStatus.CONFIRM) { throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 3c29eb5b9..1251a379d 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -98,4 +98,19 @@ public void confirm() { } this.status = AgendaTeamStatus.CONFIRM; } + + public void leaveTeamLeader() { + if (this.status == AgendaTeamStatus.CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + this.status = AgendaTeamStatus.CANCEL; + this.mateCount = 0; + } + + public void leaveTeamMate() { + if (this.status == AgendaTeamStatus.CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + this.mateCount--; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index 219c1f127..629c70577 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -46,4 +46,8 @@ public AgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile profile) { this.profile = profile; this.isExist = true; } + + public void leaveTeam() { + this.isExist = false; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index 777ed2d0c..3faee57ad 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -1,5 +1,8 @@ package gg.data.agenda; +import java.time.LocalDateTime; +import java.util.UUID; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -30,21 +33,51 @@ public class Ticket extends BaseTimeEntity { @JoinColumn(name = "profile_id", nullable = false) private AgendaProfile agendaProfile; + @Column(name = "issued_from", columnDefinition = "BINARY(16)") + private UUID issuedFrom; + + @Column(name = "used_to", columnDefinition = "BINARY(16)") + private UUID usedTo; + + @Column(name = "is_approved", nullable = false, columnDefinition = "BIT(1)") + private Boolean isApproved; + + @Column(name = "approved_at", columnDefinition = "DATETIME") + private LocalDateTime approvedAt; + @Column(name = "is_used", nullable = false, columnDefinition = "BIT(1)") private Boolean isUsed; - @Column(name = "is_approve", nullable = false, columnDefinition = "BIT(1)") - private Boolean isApprove; + @Column(name = "used_at", columnDefinition = "DATETIME") + private LocalDateTime usedAt; @Builder - public Ticket(AgendaProfile agendaProfile, Boolean isUsed, Boolean isApprove) { + public Ticket(AgendaProfile agendaProfile, UUID issuedFrom, UUID usedTo, Boolean isApproved, + LocalDateTime approvedAt, Boolean isUsed, LocalDateTime usedAt) { this.agendaProfile = agendaProfile; + this.issuedFrom = issuedFrom; + this.usedTo = usedTo; + this.isApproved = isApproved; + this.approvedAt = approvedAt; this.isUsed = isUsed; - this.isApprove = isApprove; + this.usedAt = usedAt; + } + + public static Ticket refundTicket(AgendaProfile agendaProfile, UUID issuedFrom) { + return Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(issuedFrom) + .usedTo(null) + .isApproved(true) + .approvedAt(LocalDateTime.now()) + .isUsed(false) + .usedAt(null) + .build(); } - public void useTicket() { + public void useTicket(UUID usedTo) { + this.usedTo = usedTo; + this.usedAt = LocalDateTime.now(); this.isUsed = true; } } - diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 69a489076..035250cae 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -95,11 +95,15 @@ CREATE TABLE `ticket` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `profile_id` BIGINT NOT NULL, - `is_used` BIT(1) NOT NULL, - `is_approve` BIT(1) NOT NULL, + `issued_from` BINARY(16) NULL, + `used_to` BINARY(16) NULL, + `is_approved` BOOLEAN NOT NULL, + `approved_at` DATETIME NULL, + `is_used` BOOLEAN NOT NULL, + `used_at` DATETIME NULL, `created_at` DATETIME NOT NULL, `modified_at` DATETIME NOT NULL, PRIMARY KEY (`id`), KEY `fk_ticket_profile_profile_id` (`profile_id`), CONSTRAINT `fk_ticket_profile_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `agenda_profile` (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 4a70c2943..d25688b46 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -9,7 +9,7 @@ import gg.data.agenda.Ticket; public interface TicketRepository extends JpaRepository { - Optional findByAgendaProfileAndIsApproveTrueAndIsUsedFalse(AgendaProfile agendaProfile); + Optional findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(AgendaProfile agendaProfile); - List findByAgendaProfileIdAndIsUsedFalseAndIsApproveTrue(Long agendaProfileId); + List findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(Long agendaProfileId); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index f0018687e..656b3b2f5 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -202,9 +202,11 @@ public enum ErrorCode { AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), TEAM_LEADER_FORBIDDEN(403, "AG", "팀장만 팀을 확정할 수 있습니다."), + CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), AGENDA_MODIFICATION_FORBIDDEN(403, "AG", "개최자만 일정을 수정할 수 있습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), + NOT_TEAM_MATE(403, "AG", "팀원이 아닙니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), From dc8163effe8821a8e47492852aead13708650dc9 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:54:14 +0900 Subject: [PATCH 038/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#847=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=EC=A4=91=EC=9D=B8=20=ED=8C=80=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20(#893)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 20 +++++ .../controller/request/TeamCreateReqDto.java | 1 - .../controller/response/OpenTeamResDto.java | 21 +++++ .../agendateam/service/AgendaTeamService.java | 17 ++++ .../java/gg/agenda/api/AgendaMockData.java | 27 ++++-- .../agendateam/AgendaTeamControllerTest.java | 88 ++++++++++++++++++- .../main/java/gg/data/agenda/AgendaTeam.java | 5 +- .../gg/repo/agenda/AgendaTeamRepository.java | 5 ++ 8 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 487a2c5ee..260109276 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,10 +1,14 @@ package gg.agenda.api.user.agendateam.controller; +import java.util.List; import java.util.Optional; import java.util.UUID; import javax.validation.Valid; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -18,12 +22,14 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; +import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.utils.dto.PageRequestDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -95,4 +101,18 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use agendaTeamService.agendaTeamLeave(user, agendaKey, teamKeyReqDto.getTeamKey()); return ResponseEntity.noContent().build(); } + + /** + * 아젠다 팀 공개 모집인 팀 목록 조회 + * @param user 사용자 정보, PageRequestDto 페이지네이션 요청 정보, agendaId 아젠다 아이디 + */ + @GetMapping("/open") + public ResponseEntity> openTeamList(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List openTeamResDtoList = agendaTeamService.listOpenTeam(user, agendaKey, pageable); + return ResponseEntity.ok(openTeamResDtoList); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java index 6550f5f3e..865a20c71 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamCreateReqDto.java @@ -51,7 +51,6 @@ public static AgendaTeam toEntity(TeamCreateReqDto teamCreateReqDto, Agenda agen .status(OPEN) .location(Location.valueOf(teamCreateReqDto.getTeamLocation())) .mateCount(1) - .award("award") .awardPriority(1) .isPrivate(teamCreateReqDto.getTeamIsPrivate()) .build(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java new file mode 100644 index 000000000..b28162e31 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java @@ -0,0 +1,21 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import gg.data.agenda.AgendaTeam; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class OpenTeamResDto { + private String teamName; + private String teamLeaderIntraId; + private int teamMateCount; + private String teamKey; + + public OpenTeamResDto(AgendaTeam agendaTeam) { + this.teamName = agendaTeam.getName(); + this.teamLeaderIntraId = agendaTeam.getLeaderIntraId(); + this.teamMateCount = agendaTeam.getMateCount(); + this.teamKey = agendaTeam.getTeamKey().toString(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 74f998000..8cb9b040a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -9,6 +9,7 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -16,6 +17,7 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; +import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.ticket.service.TicketService; @@ -218,4 +220,19 @@ public List agendaTeamProfileLeave(UserDto user, AgendaTeam agend agendaTeam.leaveTeamMate(); return List.of(teamMate.getProfile()); } + + /** + * 아젠다 팀 공개 모집인 팀 목록 조회 + * @param user 사용자 정보, PageRequestDto 페이지네이션 요청 정보, agendaId 아젠다 아이디 + */ + public List listOpenTeam(UserDto user, UUID agendaKey, Pageable pageable) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + List agendaTeams = agendaTeamRepository.findByAgendaAndStatusAndIsPrivateFalse(agenda, OPEN, + pageable).getContent(); + return agendaTeams.stream() + .map(OpenTeamResDto::new) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 119b68885..6a439bb1c 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -435,13 +435,30 @@ public AgendaTeam createAgendaTeam(Agenda agenda) { .status(OPEN) .location(SEOUL) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(false) .build(); return agendaTeamRepository.save(agendaTeam); } + public List createAgendaTeamList(Agenda agenda, int size, AgendaTeamStatus status) { + List agendaTeams = IntStream.range(0, size).mapToObj(i -> AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId("leaderIntraId") + .status(status) + .location(SEOUL) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build() + ) + .collect(Collectors.toList()); + return agendaTeamRepository.saveAll(agendaTeams); + } + public AgendaTeam createAgendaTeam(Agenda agenda, String teamName) { AgendaTeam agendaTeam = AgendaTeam.builder() .agenda(agenda) @@ -452,7 +469,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, String teamName) { .status(OPEN) .location(SEOUL) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -469,7 +485,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, String teamName, AgendaTeamSta .status(status) .location(SEOUL) .mateCount(3) - .award("award") .awardPriority(-1) .isPrivate(false) .build(); @@ -486,7 +501,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user) { .status(OPEN) .location(SEOUL) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -503,7 +517,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, int mateCount) { .status(OPEN) .location(SEOUL) .mateCount(mateCount) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -520,7 +533,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location) .status(OPEN) .location(location) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -537,7 +549,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, .status(status) .location(location) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -554,7 +565,6 @@ public AgendaTeam createAgendaTeam(int currentTeam, Agenda agenda, User user, Lo .status(OPEN) .location(location) .mateCount(currentTeam) - .award("award") .awardPriority(1) .isPrivate(false) .build(); @@ -572,7 +582,6 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, .status(status) .location(location) .mateCount(3) - .award("award") .awardPriority(1) .isPrivate(isPrivate) .build(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index fcdad5f6b..39db22910 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -1,17 +1,21 @@ package gg.agenda.api.user.agendateam; import static gg.data.agenda.type.Location.*; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; @@ -23,6 +27,7 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.data.agenda.Agenda; @@ -38,6 +43,7 @@ import gg.repo.agenda.TicketRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; @IntegrationTest @AutoConfigureMockMvc @@ -996,4 +1002,84 @@ public void notTeamMateFail() throws Exception { .andExpect(status().isForbidden()); } } + + @Nested + @DisplayName("OPEN팀 조회 테스트") + class OpenTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("200 OPEN팀 조회 성공") + public void openTeamGetSuccess(int page) throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + List teams = new ArrayList<>(); + teams.addAll(agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.OPEN)); + PageRequestDto req = new PageRequestDto(page, 5); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + get("/agenda/team/open") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("page", String.valueOf(page)) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); + // then + assertThat(result).hasSize(((page - 1) * 5) < teams.size() + ? Math.min(5, teams.size() - (page - 1) * 5) : 0); + teams.sort((a, b) -> b.getId().compareTo(a.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); + } + } + + @Test + @DisplayName("200 OPEN팀 없을때 조회 성공") + public void openTeamGetSuccessNoTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + get("/agenda/team/open") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + get("/agenda/team/open") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 1251a379d..7e9657f09 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -27,6 +27,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class AgendaTeam extends BaseTimeEntity { + public static final String DEFAULT_AWARD = "participant"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) @@ -70,7 +71,7 @@ public class AgendaTeam extends BaseTimeEntity { @Builder public AgendaTeam(Agenda agenda, UUID teamKey, String name, String content, String leaderIntraId, - AgendaTeamStatus status, Location location, int mateCount, String award, int awardPriority, Boolean isPrivate) { + AgendaTeamStatus status, Location location, int mateCount, int awardPriority, Boolean isPrivate) { this.agenda = agenda; this.teamKey = teamKey; this.name = name; @@ -79,7 +80,7 @@ public AgendaTeam(Agenda agenda, UUID teamKey, String name, String content, Stri this.status = status; this.location = location; this.mateCount = mateCount; - this.award = award; + this.award = DEFAULT_AWARD; this.awardPriority = awardPriority; this.isPrivate = isPrivate; } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 8f107ca8e..13ccd9d18 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -3,6 +3,8 @@ import java.util.Optional; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -26,4 +28,7 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :name AND a.status = :status") Optional findByAgendaAndNameAndStatus(Agenda agenda, String name, AgendaTeamStatus status); + + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") + Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); } From 527aca8f82a9408e72d6a690de556dc9c40bda21 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:43:05 +0900 Subject: [PATCH 039/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#853=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20API=20(#891)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: seungsje --- .../controller/AgendaProfileController.java | 30 ++++- .../request/AgendaProfileChangeReqDto.java | 29 +++++ .../service/AgendaProfileFindService.java | 9 +- .../service/AgendaProfileService.java | 39 ++++++ .../AgendaProfileControllerTest.java | 116 +++++++++++++++++- .../java/gg/data/agenda/AgendaProfile.java | 5 + 6 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 01d57068a..624fe5daf 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -1,15 +1,24 @@ package gg.agenda.api.user.agendaprofile.controller; +import javax.validation.Valid; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @RestController @@ -17,17 +26,32 @@ @RequestMapping("/agenda/profile") public class AgendaProfileController { private final AgendaProfileFindService agendaProfileFindService; + private final AgendaProfileService agendaProfileService; + private static final Logger log = LoggerFactory.getLogger(AgendaProfileController.class); /** * AgendaProfile 상세 조회 API - * * @param user 로그인한 사용자 정보 * @return AgendaProfileDetailsResDto 객체와 HTTP 상태 코드를 포함한 ResponseEntity */ @GetMapping - public ResponseEntity getMyAgendaProfile(@Login UserDto user) { - AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.getAgendaProfileDetails(user); + public ResponseEntity myAgendaProfileDetails( + @Login @Parameter(hidden = true) UserDto user) { + AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.detailsAgendaProfile(user.getId()); return ResponseEntity.status(HttpStatus.OK).body(agendaProfileDetails); } + + /** + * AgendaProfile 변경 API + * @param user 로그인한 사용자 정보 + * @param reqDto 변경할 프로필 정보 + * @return HTTP 상태 코드와 빈 응답 + */ + @PatchMapping + public ResponseEntity agendaProfileModify(@Login @Parameter(hidden = true) UserDto user, + @RequestBody @Valid AgendaProfileChangeReqDto reqDto) { + agendaProfileService.modifyAgendaProfile(user.getId(), reqDto); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java new file mode 100644 index 000000000..6a280f4ad --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agendaprofile.controller.request; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import org.hibernate.validator.constraints.URL; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AgendaProfileChangeReqDto { + + @NotBlank + @Size(max = 50, message = "userContent의 길이가 허용된 범위를 초과합니다.") + private String userContent; + + @URL + @Size(max = 100, message = "userGithub의 길이가 허용된 범위를 초과합니다.") + private String userGithub; + + @Builder + public AgendaProfileChangeReqDto(String userContent, String userGithub) { + this.userContent = userContent; + this.userGithub = userGithub; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 0616bc451..37ab75d3c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -6,8 +6,6 @@ import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; -import gg.auth.UserDto; -import gg.auth.argumentresolver.Login; import gg.data.agenda.AgendaProfile; import gg.data.user.User; import gg.repo.agenda.AgendaProfileRepository; @@ -26,13 +24,12 @@ public class AgendaProfileFindService { /** * AgendaProfile 상세 정보를 조회하는 메서드 - * - * @param user 로그인한 유저의 UserDto + * @param userId 로그인한 유저의 id * @return AgendaProfileDetailsResDto 객체 */ @Transactional(readOnly = true) - public AgendaProfileDetailsResDto getAgendaProfileDetails(@Login UserDto user) { - User loginUser = userRepository.getById(user.getId()); + public AgendaProfileDetailsResDto detailsAgendaProfile(Long userId) { + User loginUser = userRepository.getById(userId); AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java new file mode 100644 index 000000000..279b35456 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java @@ -0,0 +1,39 @@ +package gg.agenda.api.user.agendaprofile.service; + +import static gg.utils.exception.ErrorCode.*; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Service; + +import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; +import gg.data.agenda.AgendaProfile; +import gg.data.user.User; +import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.user.UserRepository; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaProfileService { + + private final UserRepository userRepository; + private final AgendaProfileRepository agendaProfileRepository; + + /** + * AgendaProfile 변경 메서드 + * @param userId 로그인한 유저의 id + * @param reqDto 변경할 프로필 정보 + */ + @Transactional + public void modifyAgendaProfile(Long userId, AgendaProfileChangeReqDto reqDto) { + User user = userRepository.getById(userId); + + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + agendaProfile.updateProfile(reqDto.getUserContent(), reqDto.getUserGithub()); + agendaProfileRepository.save(agendaProfile); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 9fb8af369..133c53e42 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -13,15 +13,17 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; +import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.data.agenda.AgendaProfile; -import gg.data.agenda.Ticket; import gg.data.user.User; +import gg.repo.agenda.AgendaProfileRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; @@ -37,6 +39,8 @@ public class AgendaProfileControllerTest { private TestDataUtils testDataUtils; @Autowired private AgendaMockData agendaMockData; + @Autowired + private AgendaProfileRepository agendaProfileRepository; User user; String accessToken; @@ -55,16 +59,13 @@ void beforeEach() { void test() throws Exception { //given AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); - Ticket ticket = agendaMockData.createTicket(agendaProfile); - + agendaMockData.createTicket(agendaProfile); // when String response = mockMvc.perform(get("/agenda/profile") .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaProfileDetailsResDto result = objectMapper.readValue(response, AgendaProfileDetailsResDto.class); - // then assertThat(result.getUserIntraId()).isEqualTo(user.getIntraId()); assertThat(result.getUserContent()).isEqualTo(agendaProfile.getContent()); @@ -79,7 +80,6 @@ void test() throws Exception { void testInvalidUser() throws Exception { // given: 유효하지 않은 유저의 액세스 토큰 String invalidAccessToken = "invalid-access-token"; - // when & then: 예외가 발생해야 함 mockMvc.perform(get("/agenda/profile") .header("Authorization", "Bearer " + invalidAccessToken)) @@ -97,4 +97,108 @@ void testAgendaProfileNotFound() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("개인 프로필 정보 변경") + class UpdateAgendaProfile { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("유효한 정보로 개인 프로필을 변경합니다.") + void updateProfileWithValidData() throws Exception { + // Given + AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + agendaMockData.createTicket(agendaProfile); + AgendaProfileChangeReqDto requestDto = new AgendaProfileChangeReqDto("Valid user content", + "https://github.com/validUser"); + String content = objectMapper.writeValueAsString(requestDto); + // When + mockMvc.perform(patch("/agenda/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNoContent()); + // Then + AgendaProfile result = agendaProfileRepository.findByUserId(user.getId()).orElseThrow(null); + assertThat(result.getContent()).isEqualTo(requestDto.getUserContent()); + assertThat(result.getGithubUrl()).isEqualTo(requestDto.getUserGithub()); + } + + @Test + @DisplayName("userContent 없이 개인 프로필을 변경합니다.") + void updateProfileWithoutUserContent() throws Exception { + // Given + AgendaProfileChangeReqDto requestDto = new AgendaProfileChangeReqDto("", "https://github.com/validUser"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("잘못된 형식의 userGithub로 개인 프로필을 변경합니다.") + void updateProfileWithInvalidUserGithub() throws Exception { + // Given + AgendaProfileChangeReqDto requestDto = new AgendaProfileChangeReqDto("Valid user content", + "invalidGithubUrl"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("userContent가 허용된 길이를 초과하여 개인 프로필을 변경합니다.") + void updateProfileWithExceededUserContentLength() throws Exception { + // Given + String longContent = "a".repeat(1001); // Assuming the limit is 1000 characters + AgendaProfileChangeReqDto requestDto = new AgendaProfileChangeReqDto(longContent, + "https://github.com/validUser"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("userGithub가 허용된 길이를 초과하여 개인 프로필을 변경합니다.") + void updateProfileWithExceededUserGithubLength() throws Exception { + // Given + String longGithubUrl = "https://github.com/" + "a".repeat(256); // Assuming the limit is 255 characters + AgendaProfileChangeReqDto requestDto = new AgendaProfileChangeReqDto("Valid user content", longGithubUrl); + + String content = objectMapper.writeValueAsString(requestDto); + + // When & Then + mockMvc.perform(patch("/agenda/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("해당 로그인 유저의 아젠다 프로필이 없을 때") + void testAgendaProfileNotFound() throws Exception { + // given: 특정 유저와 관련된 AgendaProfile이 없음 + // when & then: 예외가 발생해야 함 + mockMvc.perform(get("/agenda/profile") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + } } + diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index 68cf619d8..e75d317f3 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -57,4 +57,9 @@ public AgendaProfile(String content, String githubUrl, Coalition coalition, Loca this.intraId = intraId; this.userId = userId; } + + public void updateProfile(String content, String githubUrl) { + this.content = content; + this.githubUrl = githubUrl; + } } From 250d0e292a6669c8b63c5fd6650bea0a4585bc98 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:54:28 +0900 Subject: [PATCH 040/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#887=20Admin=20?= =?UTF-8?q?=EB=8C=80=ED=9A=8C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EA=B8=B0=20(#894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/agenda/AgendaAdminRepository.java | 7 + .../agenda/AgendaTeamAdminRepository.java | 17 + .../controller/AgendaAdminController.java | 22 +- .../request/AgendaAdminUpdateReqDto.java | 69 ++++ .../agenda/service/AgendaAdminService.java | 30 +- .../request/AgendaCreateReqDto.java | 7 +- .../java/gg/agenda/api/AgendaMockData.java | 139 ++++++- .../controller/AgendaAdminControllerTest.java | 347 +++++++++++++++++- .../service/AgendaAdminServiceTest.java | 273 +++++++++++++- .../src/main/java/gg/data/agenda/Agenda.java | 101 ++++- .../java/gg/data/agenda/type/Location.java | 9 +- .../java/gg/utils/exception/ErrorCode.java | 3 + 12 files changed, 997 insertions(+), 27 deletions(-) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java index 58a4a9ace..d14c78ac7 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java @@ -1,10 +1,17 @@ package gg.admin.repo.agenda; +import java.util.Optional; +import java.util.UUID; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import gg.data.agenda.Agenda; @Repository public interface AgendaAdminRepository extends JpaRepository { + + @Query("SELECT a FROM Agenda a WHERE a.agendaKey = :agendaKey") + Optional findByAgendaKey(UUID agendaKey); } diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java new file mode 100644 index 000000000..7b299bfea --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java @@ -0,0 +1,17 @@ +package gg.admin.repo.agenda; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; + +@Repository +public interface AgendaTeamAdminRepository extends JpaRepository { + + @Query("SELECT at FROM AgendaTeam at WHERE at.agenda = :agenda") + List findAllByAgenda(Agenda agenda); +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index 58dc62d75..f32505758 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -1,6 +1,7 @@ package gg.agenda.api.admin.agenda.controller; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import javax.validation.Valid; @@ -8,12 +9,16 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; import gg.agenda.api.admin.agenda.service.AgendaAdminService; import gg.utils.dto.PageRequestDto; @@ -32,7 +37,7 @@ public class AgendaAdminController { @ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공") }) @GetMapping("/request/list") - public ResponseEntity> getAgendaRequestList( + public ResponseEntity> agendaList( @RequestBody @Valid PageRequestDto pageDto) { int page = pageDto.getPage(); int size = pageDto.getSize(); @@ -42,4 +47,19 @@ public ResponseEntity> getAgendaRequestList( .collect(Collectors.toList()); return ResponseEntity.ok(agendaDtos); } + + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Agenda 수정 성공"), + @ApiResponse(responseCode = "400", description = "Agenda 수정 요청이 잘못됨"), + @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음"), + @ApiResponse(responseCode = "409", description = "Agenda 지역을 변경할 수 없음"), + @ApiResponse(responseCode = "409", description = "Agenda 팀 제한을 변경할 수 없음"), + @ApiResponse(responseCode = "409", description = "Agenda 팀 인원 제한을 변경할 수 없음") + }) + @PatchMapping("/request") + public ResponseEntity agendaUpdate(@RequestParam("agenda_key") UUID agendaKey, + @RequestBody @Valid AgendaAdminUpdateReqDto agendaDto) { + agendaAdminService.updateAgenda(agendaKey, agendaDto); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java new file mode 100644 index 000000000..5f21a5930 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java @@ -0,0 +1,69 @@ +package gg.agenda.api.admin.agenda.controller.request; + +import java.time.LocalDateTime; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAdminUpdateReqDto { + + private String agendaTitle; + + private String agendaContents; + + private String agendaPoster; + + private Boolean isOfficial; + + private Boolean isRanking; + + private AgendaStatus agendaStatus; + + private LocalDateTime agendaDeadLine; + + private LocalDateTime agendaStartTime; + + private LocalDateTime agendaEndTime; + + private Location agendaLocation; + + private int agendaMinTeam; + + private int agendaMaxTeam; + + private int agendaMinPeople; + + private int agendaMaxPeople; + + @Builder + public AgendaAdminUpdateReqDto(String agendaTitle, String agendaContents, String agendaPoster, Boolean isOfficial, + Boolean isRanking, AgendaStatus agendaStatus, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, + LocalDateTime agendaEndTime, Location agendaLocation, int agendaMinTeam, int agendaMaxTeam, + int agendaMinPeople, int agendaMaxPeople) { + this.agendaTitle = agendaTitle; + this.agendaContents = agendaContents; + this.agendaPoster = agendaPoster; + this.isOfficial = isOfficial; + this.isRanking = isRanking; + this.agendaStatus = agendaStatus; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaLocation = agendaLocation; + this.agendaMinTeam = agendaMinTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index d6aecd645..f7c004244 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -1,13 +1,20 @@ package gg.agenda.api.admin.agenda.service; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; +import java.util.UUID; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.data.agenda.Agenda; -import gg.repo.agenda.AgendaRepository; +import gg.data.agenda.AgendaTeam; +import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @Service @@ -16,7 +23,28 @@ public class AgendaAdminService { private final AgendaAdminRepository agendaAdminRepository; + private final AgendaTeamAdminRepository agendaTeamAdminRepository; + + @Transactional(readOnly = true) public List getAgendaRequestList(Pageable pageable) { return agendaAdminRepository.findAll(pageable).getContent(); } + + @Transactional + public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto) { + Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + List teams = agendaTeamAdminRepository.findAllByAgenda(agenda); + + agenda.updateInformation(agendaDto.getAgendaTitle(), agendaDto.getAgendaContents(), + agendaDto.getAgendaPoster()); + agenda.updateIsOfficial(agendaDto.getIsOfficial()); + agenda.updateIsRanking(agendaDto.getIsRanking()); + agenda.updateAgendaStatus(agendaDto.getAgendaStatus()); + agenda.updateSchedule(agendaDto.getAgendaDeadLine(), agendaDto.getAgendaStartTime(), + agendaDto.getAgendaEndTime()); + agenda.updateLocation(agendaDto.getAgendaLocation(), teams); + agenda.updateAgendaCapacity(agendaDto.getAgendaMinTeam(), agendaDto.getAgendaMaxTeam(), teams); + agenda.updateAgendaTeamCapacity(agendaDto.getAgendaMinPeople(), agendaDto.getAgendaMaxPeople(), teams); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index 60129f6d9..1915f482b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -7,6 +7,7 @@ import javax.validation.constraints.Future; import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @@ -28,12 +29,10 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class AgendaCreateReqDto { - @NotNull - @NotEmpty + @NotBlank private String agendaTitle; - @NotNull - @NotEmpty + @NotBlank private String agendaContent; @NotNull diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 6a439bb1c..ad23ec79a 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,6 +1,7 @@ package gg.agenda.api; import static gg.data.agenda.type.AgendaStatus.*; +import static gg.data.agenda.type.AgendaStatus.CONFIRM; import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Coalition.*; import static gg.data.agenda.type.Location.*; @@ -33,6 +34,7 @@ import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.repo.agenda.TicketRepository; +import gg.utils.TestDataUtils; import lombok.RequiredArgsConstructor; @Component @@ -46,6 +48,7 @@ public class AgendaMockData { private final AgendaProfileRepository agendaProfileRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaAnnouncementRepository agendaAnnouncementRepository; + private final TestDataUtils testDataUtils; public Agenda createOfficialAgenda() { Agenda agenda = Agenda.builder() @@ -190,7 +193,7 @@ public List createAgendaHistory(int size) { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(AgendaStatus.CONFIRM) + .status(CONFIRM) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -312,20 +315,64 @@ public Agenda createAgenda(LocalDateTime deadline) { return agendaRepository.save(agenda); } - public Agenda createAgenda(Location location) { + public Agenda createAgendaWithAgendaCapacity(int min, int max) { Agenda agenda = Agenda.builder() .title("title") .content("content") .deadline(LocalDateTime.now().plusDays(1)) .startTime(LocalDateTime.now().plusDays(2)) .endTime(LocalDateTime.now().plusDays(3)) - .minTeam(1) - .maxTeam(5) + .minTeam(min) + .maxTeam(max) .currentTeam(0) .minPeople(1) .maxPeople(3) .posterUri("posterUri") .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgendaWithAgendaTeamCapacity(int min, int max) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(2) + .maxTeam(10) + .currentTeam(0) + .minPeople(min) + .maxPeople(max) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(ON_GOING) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(Location location) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(2) + .maxTeam(20) + .currentTeam(0) + .minPeople(1) + .maxPeople(20) + .posterUri("posterUri") + .hostIntraId("hostIntraId") .location(location) .status(ON_GOING) .isOfficial(true) @@ -596,4 +643,88 @@ public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam agendaTeam, AgendaPr .build(); return agendaTeamProfileRepository.save(agendaTeamProfile); } + + public Agenda createAgendaWithTeam(int teamCount) { + Agenda agenda = createAgenda(SEOUL); + for (int i = 0; i < teamCount; i++) { + AgendaTeam agendaTeam = createAgendaTeam(agenda); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + return agenda; + } + + public Agenda createAgendaWithTeamGyeongsan(int teamCount) { + Agenda agenda = createAgenda(GYEONGSAN); + for (int i = 0; i < teamCount; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, GYEONGSAN); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + return agenda; + } + + public Agenda createAgendaWithTeamMix(int teamCount) { + Agenda agenda = createAgenda(MIX); + int half = teamCount / 2; + for (int i = 0; i < teamCount - half; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, SEOUL); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + for (int i = 0; i < half; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, GYEONGSAN); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + return agenda; + } + + public Agenda createAgendaWithTeamAndAgendaCapacity(int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaCapacity(min, max); + for (int i = 0; i < teamCount; i++) { + AgendaTeam agendaTeam = createAgendaTeam(agenda); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + return agenda; + } + + public Agenda createAgendaWithTeamAndAgendaCapacityAndConfirm(int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaCapacity(min, max); + for (int i = 0; i < teamCount; i++) { + AgendaTeam agendaTeam = createAgendaTeam(agenda); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + agenda.updateSchedule(LocalDateTime.now().minusDays(2), + LocalDateTime.now().minusDays(1), + LocalDateTime.now().plusDays(1)); + agenda.confirm(LocalDateTime.now()); + em.persist(agenda); + em.flush(); + em.clear(); + return agenda; + } + + public Agenda createAgendaWithTeamAndAgendaTeamCapacity(int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaTeamCapacity(min, max); + for (int i = 0; i < teamCount; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, 10); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } + return agenda; + } + + public Agenda createAgendaWithTeamAndAgendaTeamCapacityAndConfirm(int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaTeamCapacity(min, max); + for (int i = 0; i < teamCount; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, 3); + agendaTeam.confirm(); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + em.persist(agendaTeam); + em.flush(); + em.clear(); + } + return agenda; + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index d0b768dd8..d65f85aee 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -1,12 +1,15 @@ package gg.agenda.api.admin.agenda.controller; +import static gg.data.agenda.type.AgendaStatus.*; +import static gg.data.agenda.type.Location.*; import static org.assertj.core.api.AssertionsForClassTypes.*; -import static org.springframework.test.web.servlet.MockMvcBuilder.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.UUID; import javax.persistence.EntityManager; import javax.transaction.Transactional; @@ -16,22 +19,23 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MockMvcBuilder; import com.fasterxml.jackson.databind.ObjectMapper; +import gg.admin.repo.agenda.AgendaAdminRepository; import gg.agenda.api.AgendaMockData; +import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; import gg.data.agenda.Agenda; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; import gg.data.user.User; -import gg.repo.agenda.AgendaRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; @@ -59,7 +63,7 @@ public class AgendaAdminControllerTest { EntityManager em; @Autowired - AgendaRepository agendaRepository; + AgendaAdminRepository agendaAdminRepository; private User user; @@ -67,7 +71,7 @@ public class AgendaAdminControllerTest { @BeforeEach void setUp() { - user = testDataUtils.createNewUser(); + user = testDataUtils.createAdminUser(); accessToken = testDataUtils.getLoginAccessTokenFromUser(user); } @@ -94,14 +98,13 @@ void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { // when String response = mockMvc.perform(get("/admin/agenda/request/list") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .contentType(MediaType.APPLICATION_JSON).content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); // then - assertThat(result).hasSize(((page - 1) * size) < agendas.size() - ? Math.min(size, agendas.size() - (page - 1) * size) : 0); + assertThat(result).hasSize( + ((page - 1) * size) < agendas.size() ? Math.min(size, agendas.size() - (page - 1) * size) : 0); agendas.sort((a, b) -> b.getId().compareTo(a.getId())); for (int i = 0; i < result.length; i++) { assertThat(result[i].getAgendaId()).isEqualTo(agendas.get(i + (page - 1) * size).getId()); @@ -120,8 +123,7 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { // when String response = mockMvc.perform(get("/admin/agenda/request/list") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .contentType(MediaType.APPLICATION_JSON).content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); @@ -129,4 +131,325 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { assertThat(result).isEmpty(); } } + + @Nested + @DisplayName("Admin Agenda 수정 및 삭제") + class UpdateAgendaAdmin { + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 기본 정보") + void updateAgendaAdminSuccessWithInformation() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaTitle("updated title").agendaContents("updated content") + .agendaPoster("updated poster").agendaStatus(CONFIRM).isOfficial(!agenda.getIsOfficial()) + .isRanking(!agenda.getIsRanking()).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getTitle()).isEqualTo(agendaDto.getAgendaTitle()); + assertThat(updated.get().getContent()).isEqualTo(agendaDto.getAgendaContents()); + assertThat(updated.get().getPosterUri()).isEqualTo(agendaDto.getAgendaPoster()); + assertThat(updated.get().getStatus()).isEqualTo(agendaDto.getAgendaStatus()); + assertThat(updated.get().getIsOfficial()).isEqualTo(agendaDto.getIsOfficial()); + assertThat(updated.get().getIsRanking()).isEqualTo(agendaDto.getIsRanking()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 스케쥴 정보") + void updateAgendaAdminSuccessWithSchedule() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaDeadLine(agenda.getDeadline().plusDays(1)) + .agendaStartTime(agenda.getStartTime().plusDays(1)).agendaEndTime(agenda.getEndTime().plusDays(1)) + .build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getDeadline()).isEqualTo(agendaDto.getAgendaDeadLine()); + assertThat(updated.get().getStartTime()).isEqualTo(agendaDto.getAgendaStartTime()); + assertThat(updated.get().getEndTime()).isEqualTo(agendaDto.getAgendaEndTime()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 서울 대회를 MIX로 변경") + void updateAgendaAdminSuccessWithLocationSeoulToMix() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); // SEOUL + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(MIX).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getLocation()).isEqualTo(agendaDto.getAgendaLocation()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 서울 대회를 경산으로 변경") + void updateAgendaAdminSuccessWithLocationSeoulToGyeongsan() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getLocation()).isEqualTo(agendaDto.getAgendaLocation()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 경산 대회를 서울로 변경") + void updateAgendaAdminSuccessWithLocationGyeongsanToSeoul() throws Exception { + // given + Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getLocation()).isEqualTo(agendaDto.getAgendaLocation()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - 경산 대회를 MIX로 변경") + void updateAgendaAdminSuccessWithLocationGyeongsanToMix() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamGyeongsan(10); // SEOUL + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(MIX).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getLocation()).isEqualTo(agendaDto.getAgendaLocation()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - Agenda 팀 제한 정보") + void updateAgendaAdminSuccessWithAgendaCapacity() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam() + 1) + .agendaMaxTeam(agenda.getMaxTeam() + 1).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getMinTeam()).isEqualTo(agendaDto.getAgendaMinTeam()); + assertThat(updated.get().getMaxTeam()).isEqualTo(agendaDto.getAgendaMaxTeam()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 - Agenda 팀 허용 인원 제한 정보") + void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(agenda.getMinPeople() + 1) + .agendaMaxPeople(agenda.getMaxPeople() + 1).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // when + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getMinPeople()).isEqualTo(agendaDto.getAgendaMinPeople()); + assertThat(updated.get().getMaxPeople()).isEqualTo(agendaDto.getAgendaMaxPeople()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 대회가 존재하지 않는 경우") + void updateAgendaAdminFailedWithNoAgenda() throws Exception { + // given + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", UUID.randomUUID().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 서울 대회를 경산으로 변경할 수 없는 경우") + void updateAgendaAdminFailedWithLocationSeoulToGyeongSan() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 경산 대회를 서울 대회로 변경할 수 없는 경우") + void updateAgendaAdminFailedWithLocationGyeongSanToSeoul() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamGyeongsan(10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(SEOUL).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @EnumSource(value = Location.class, names = {"SEOUL", "GYEONGSAN"}) + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 혼합 대회를 다른 지역 대회로 변경할 수 없는 경우") + void updateAgendaAdminFailedWithLocationMixToSeoul() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamMix(10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(SEOUL).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 maxTeam 이상의 팀이 존재하는 경우") + void updateAgendaAdminFailedWithAgendaInvalidCapacity() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacity(10, 2, 10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinTeam(10).agendaMaxTeam(2).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 maxTeam 이상의 팀이 존재하는 경우") + void updateAgendaAdminFailedWithMaxTeam() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacity(10, 2, 10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam()) + .agendaMaxTeam(agenda.getMaxTeam() - 5).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 확정된 대회에 minTeam 이하의 팀이 참여한 경우") + void updateAgendaAdminFailedWithMinTeam() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacityAndConfirm(5, 5, 10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam() + 2) + .agendaMaxTeam((agenda.getMaxTeam())).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - minPeople이 maxPeople보다 큰 경우") + void updateAgendaAdminFailedWithAgendaTeamInvalidCapacity() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacity(10, 2, 10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(10).agendaMaxPeople(2).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 팀에 maxPeople 이상의 인원이 참여한 경우") + void updateAgendaAdminFailedWithMaxPeople() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacity(10, 2, 10); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(agenda.getMinPeople()) + .agendaMaxPeople(agenda.getMaxPeople() - 5).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 확정된 팀에 minPeople 이하의 인원이 참여한 경우") + void updateAgendaAdminFailedWithMinPeople() throws Exception { + // given + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacityAndConfirm(10, 3, 10); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(5).agendaMaxPeople(agenda.getMaxPeople()).build(); + String request = objectMapper.writeValueAsString(agendaDto); + + // expected + mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) + .content(request)).andExpect(status().isBadRequest()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java index 928f02355..e6b4349c6 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -1,10 +1,14 @@ package gg.agenda.api.admin.agenda.service; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,17 +20,26 @@ import org.springframework.data.domain.Pageable; import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; import gg.utils.annotation.UnitTest; -import lombok.extern.slf4j.Slf4j; +import gg.utils.exception.custom.InvalidParameterException; +import gg.utils.exception.custom.NotExistException; -@Slf4j @UnitTest public class AgendaAdminServiceTest { @Mock AgendaAdminRepository agendaAdminRepository; + @Mock + AgendaTeamAdminRepository agendaTeamAdminRepository; + @InjectMocks AgendaAdminService agendaAdminService; @@ -68,4 +81,260 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() { assertThat(result).isEmpty(); } } + + @Nested + @DisplayName("Admin Agenda 수정") + class UpdateAgenda { + + @Test + @DisplayName("Admin Agenda 수정 성공 - 기본 정보") + void updateAgendaSuccessWithInformation() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + Agenda agenda = Agenda.builder().title("title").content("content").posterUri("posterUri").isOfficial(false) + .isRanking(true).status(AgendaStatus.CONFIRM).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaTitle("Updated title").agendaContents("Updated content") + .agendaPoster("Updated posterUri").isOfficial(true).isRanking(true) + .agendaStatus(AgendaStatus.CANCEL).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // when + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); + verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); + assertThat(agenda.getTitle()).isEqualTo(agendaDto.getAgendaTitle()); + assertThat(agenda.getContent()).isEqualTo(agendaDto.getAgendaContents()); + assertThat(agenda.getPosterUri()).isEqualTo(agendaDto.getAgendaPoster()); + assertThat(agenda.getIsOfficial()).isEqualTo(agendaDto.getIsOfficial()); + assertThat(agenda.getIsRanking()).isEqualTo(agendaDto.getIsRanking()); + assertThat(agenda.getStatus()).isEqualTo(agendaDto.getAgendaStatus()); + } + + @Test + @DisplayName("Admin Agenda 수정 성공 - 스케줄 정보") + void updateAgendaSuccessWithSchedule() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + Agenda agenda = + Agenda.builder().deadline(LocalDateTime.now().minusDays(3)).startTime(LocalDateTime.now().plusDays(1)) + .endTime(LocalDateTime.now().plusDays(3)).build(); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaDeadLine(LocalDateTime.now()) + .agendaStartTime(LocalDateTime.now().plusDays(3)).agendaEndTime(LocalDateTime.now().plusDays(5)) + .build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // when + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); + verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); + assertThat(agenda.getDeadline()).isEqualTo(agendaDto.getAgendaDeadLine()); + assertThat(agenda.getStartTime()).isEqualTo(agendaDto.getAgendaStartTime()); + assertThat(agenda.getEndTime()).isEqualTo(agendaDto.getAgendaEndTime()); + } + + @Test + @DisplayName("Admin Agenda 수정 성공 - 지역 정보") + void updateAgendaSuccessWithLocation() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + Agenda agenda = Agenda.builder().location(Location.SEOUL).build(); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(Location.MIX).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // when + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); + verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); + assertThat(agenda.getLocation()).isEqualTo(agendaDto.getAgendaLocation()); + } + + @Test + @DisplayName("Admin Agenda 수정 성공 - Agenda 팀 제한 정보") + void updateAgendaSuccessWithAgendaCapacity() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + Agenda agenda = Agenda.builder().minTeam(5).maxTeam(10).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinTeam(2).agendaMaxTeam(20).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // when + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); + verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); + assertThat(agenda.getMinTeam()).isEqualTo(agendaDto.getAgendaMinTeam()); + assertThat(agenda.getMaxTeam()).isEqualTo(agendaDto.getAgendaMaxTeam()); + } + + @Test + @DisplayName("Admin Agenda 수정 성공 - Agenda 팀 인원 제한 정보") + void updateAgendaSuccessWithAgendaTeamCapacity() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + Agenda agenda = Agenda.builder().minPeople(1).maxPeople(10).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(2).agendaMaxPeople(20).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // when + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); + verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); + assertThat(agenda.getMinTeam()).isEqualTo(agendaDto.getAgendaMinTeam()); + assertThat(agenda.getMaxTeam()).isEqualTo(agendaDto.getAgendaMaxTeam()); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda를 찾을 수 없음") + void updateAgendaFailAdminWithNotExistAgenda() { + // given + AgendaAdminUpdateReqDto agendaDto = mock(AgendaAdminUpdateReqDto.class); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, () -> agendaAdminService.updateAgenda(UUID.randomUUID(), agendaDto)); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda 지역을 변경할 수 없음") + void updateAgendaFailAdminWithCannotChangeLocation() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } // SEOUL + teams.add(AgendaTeam.builder().location(Location.GYEONGSAN).mateCount(3).build()); // GYEONGSAN + Agenda agenda = Agenda.builder().currentTeam(teams.size()).location(Location.MIX).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaLocation(Location.SEOUL).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 제한을 변경할 수 없음") + void updateAgendaFailAdminWithCannotChangeMinTeam() { + // given + int teamCount = 5; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } // 10개 팀 + Agenda agenda = + Agenda.builder().currentTeam(teams.size()).minTeam(5).maxTeam(10).status(AgendaStatus.CONFIRM).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinTeam(10).agendaMaxTeam(20).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 제한을 변경할 수 없음") + void updateAgendaFailAdminWithCannotChangeMaxTeam() { + // given + int teamCount = 10; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } // 10개 팀 + Agenda agenda = Agenda.builder().currentTeam(teams.size()).minTeam(5).maxTeam(10).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinTeam(2).agendaMaxTeam(5).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 인원 제한을 변경할 수 없음") + void updateAgendaFailAdminWithCannotChangeMaxPeople() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(10).status(AgendaTeamStatus.CONFIRM) + .build()); // mateCount 10 + Agenda agenda = Agenda.builder().currentTeam(teams.size()).minPeople(1).maxPeople(10).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(2).agendaMaxPeople(5).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + } + + @Test + @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 인원 제한을 변경할 수 없음") + void updateAgendaFailAdminWithCannotChangeMinPeople() { + // given + int teamCount = 3; + List teams = new ArrayList<>(); + for (int i = 0; i < teamCount; i++) { + teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); + } + teams.add( + AgendaTeam.builder().location(Location.SEOUL).mateCount(3).status(AgendaTeamStatus.CONFIRM).build()); + Agenda agenda = Agenda.builder().currentTeam(teams.size()).minPeople(1).maxPeople(10).build(); + AgendaAdminUpdateReqDto agendaDto = + AgendaAdminUpdateReqDto.builder().agendaMinPeople(5).agendaMaxPeople(20).build(); + when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index c568b53cf..df1a6e597 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -3,6 +3,8 @@ import static gg.utils.exception.ErrorCode.*; import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; import java.util.UUID; import javax.persistence.Column; @@ -17,6 +19,7 @@ import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.InvalidParameterException; @@ -148,6 +151,90 @@ public void confirm(LocalDateTime confirmTime) { this.status = AgendaStatus.CONFIRM; } + public void updateInformation(String title, String content, String posterUri) { + if (Objects.nonNull(title) && !title.isBlank()) { + this.title = title; + } + if (Objects.nonNull(content) && !title.isBlank()) { + this.content = content; + } + if (Objects.nonNull(posterUri) && !title.isBlank()) { + this.posterUri = posterUri; + } + } + + public void updateIsOfficial(Boolean isOfficial) { + if (Objects.nonNull(isOfficial)) { + this.isOfficial = isOfficial; + } + } + + public void updateIsRanking(Boolean isRanking) { + if (Objects.nonNull(isRanking)) { + this.isRanking = isRanking; + } + } + + public void updateAgendaStatus(AgendaStatus agendaStatus) { + if (Objects.nonNull(agendaStatus)) { + this.status = agendaStatus; + } + } + + public void updateSchedule(LocalDateTime deadline, LocalDateTime startTime, LocalDateTime endTime) { + if (Objects.isNull(deadline) || Objects.isNull(startTime) || Objects.isNull(endTime)) { + return; + } + mustHaveValidSchedule(); + this.deadline = deadline; + this.startTime = startTime; + this.endTime = endTime; + } + + public void updateLocation(Location location, List teams) { + if (Objects.isNull(location)) { + return; + } + boolean conflictAgendaLocation = teams.stream() + .map(AgendaTeam::getLocation) + .anyMatch(teamLocation -> !Location.isUnderLocation(location, teamLocation)); + if (conflictAgendaLocation) { + throw new InvalidParameterException(AGENDA_UPDATE_LOCATION_CONFLICT); + } + this.location = location; + } + + public void updateAgendaCapacity(int minTeam, int maxTeam, List teams) { + if (minTeam < 2 || maxTeam < 2) { + return; + } + if (minTeam > maxTeam || teams.size() > maxTeam) { + throw new InvalidParameterException(AGENDA_CAPACITY_CONFLICT); + } + if (this.status == AgendaStatus.CONFIRM && teams.size() < minTeam) { + throw new InvalidParameterException(AGENDA_CAPACITY_CONFLICT); + } + this.minTeam = minTeam; + this.maxTeam = maxTeam; + } + + public void updateAgendaTeamCapacity(int minPeople, int maxPeople, List teams) { + if (minPeople < 1 || maxPeople < 1) { + return; + } + if (minPeople > maxPeople) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + boolean conflictAgendaTeamCapacity = teams.stream() + .anyMatch(team -> team.getMateCount() > maxPeople + || (team.getStatus() == AgendaTeamStatus.CONFIRM && team.getMateCount() < minPeople)); + if (conflictAgendaTeamCapacity) { + throw new InvalidParameterException(AGENDA_TEAM_CAPACITY_CONFLICT); + } + this.minPeople = minPeople; + this.maxPeople = maxPeople; + } + private void mustBeWithinLocation(Location location) { if (this.location != Location.MIX && this.location != location) { throw new InvalidParameterException(LOCATION_NOT_VALID); @@ -172,9 +259,19 @@ private void mustHaveCapacity() { } } + private void mustHaveValidSchedule() { + if (this.deadline.isAfter(this.startTime)) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + if (this.startTime.isAfter(this.endTime)) { + throw new InvalidParameterException(AGENDA_INVALID_PARAM); + } + } + public void mustModifiedByHost(String userIntraId) { - if (!this.hostIntraId.equals(userIntraId)) { - throw new ForbiddenException(AGENDA_MODIFICATION_FORBIDDEN); + if (this.hostIntraId.equals(userIntraId)) { + return; } + throw new ForbiddenException(AGENDA_MODIFICATION_FORBIDDEN); } } diff --git a/gg-data/src/main/java/gg/data/agenda/type/Location.java b/gg-data/src/main/java/gg/data/agenda/type/Location.java index eaeba1843..9f027f1e2 100644 --- a/gg-data/src/main/java/gg/data/agenda/type/Location.java +++ b/gg-data/src/main/java/gg/data/agenda/type/Location.java @@ -10,7 +10,7 @@ public enum Location { GYEONGSAN("GYEONGSAN"), MIX("MIX"); - private String location; + private final String location; public static Location valueOfLocation(String location) { String locationToUpper = location.toUpperCase(); @@ -21,4 +21,11 @@ public static Location valueOfLocation(String location) { } return MIX; } + + public static boolean isUnderLocation(Location criteria, Location target) { + if (criteria == MIX) { + return true; + } + return criteria == target; + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 656b3b2f5..328f06c53 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -200,6 +200,9 @@ public enum ErrorCode { AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), + AGENDA_UPDATE_LOCATION_CONFLICT(409, "AG", "지역을 변경할 수 없습니다."), + AGENDA_CAPACITY_CONFLICT(409, "AG", "팀 제한을 변경할 수 없습니다."), + AGENDA_TEAM_CAPACITY_CONFLICT(409, "AG", "팀 인원 제한을 변경할 수 없습니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), TEAM_LEADER_FORBIDDEN(403, "AG", "팀장만 팀을 확정할 수 있습니다."), CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), From 4dfb83958881b82c4f44f79db98c0283f5811038 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:19:43 +0900 Subject: [PATCH 041/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20#848=20?= =?UTF-8?q?=ED=99=95=EC=A0=95=EB=90=9C=20=ED=8C=80=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20API=20(#8?= =?UTF-8?q?95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 25 ++++-- .../response/ConfirmTeamResDto.java | 28 +++++++ .../agendateam/service/AgendaTeamService.java | 31 ++++++- .../agendateam/AgendaTeamControllerTest.java | 84 ++++++++++++++++++- .../agenda/AgendaTeamProfileRepository.java | 2 +- .../gg/repo/agenda/AgendaTeamRepository.java | 3 + 6 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/ConfirmTeamResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 260109276..a79b0d311 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -21,12 +21,12 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; -import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.utils.dto.PageRequestDto; @@ -38,7 +38,6 @@ @RequestMapping("/agenda/team") public class AgendaTeamController { private final AgendaTeamService agendaTeamService; - private final TicketService ticketService; /** * 내 팀 간단 정보 조회 @@ -104,15 +103,29 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use /** * 아젠다 팀 공개 모집인 팀 목록 조회 - * @param user 사용자 정보, PageRequestDto 페이지네이션 요청 정보, agendaId 아젠다 아이디 + * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ @GetMapping("/open") - public ResponseEntity> openTeamList(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { + public ResponseEntity> openTeamList(@RequestBody @Valid PageRequestDto pageRequest, + @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List openTeamResDtoList = agendaTeamService.listOpenTeam(user, agendaKey, pageable); + List openTeamResDtoList = agendaTeamService.listOpenTeam(agendaKey, pageable); return ResponseEntity.ok(openTeamResDtoList); } + + /** + * 아젠다 팀 확정된 팀 목록 조회 + * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 + */ + @GetMapping("/confirm") + public ResponseEntity> confirmTeamList(@RequestBody @Valid PageRequestDto pageRequest, + @RequestParam("agenda_key") UUID agendaKey) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List confirmTeamResDto = agendaTeamService.listConfirmTeam(agendaKey, pageable); + return ResponseEntity.ok(confirmTeamResDto); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/ConfirmTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/ConfirmTeamResDto.java new file mode 100644 index 000000000..e73d2a2af --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/ConfirmTeamResDto.java @@ -0,0 +1,28 @@ +package gg.agenda.api.user.agendateam.controller.response; + +import java.util.List; + +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Coalition; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class ConfirmTeamResDto { + private String teamName; + private String teamLeaderIntraId; + private int teamMateCount; + private String teamAward; + private int awardPriority; + private List coalitions; + + public ConfirmTeamResDto(AgendaTeam agendaTeam, List coalitions) { + this.teamName = agendaTeam.getName(); + this.teamLeaderIntraId = agendaTeam.getLeaderIntraId(); + this.teamMateCount = agendaTeam.getMateCount(); + this.teamAward = agendaTeam.getAward(); + this.awardPriority = agendaTeam.getAwardPriority(); + this.coalitions = coalitions; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 8cb9b040a..62e1c4509 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -16,6 +16,7 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; @@ -62,7 +63,8 @@ public Optional detailsMyTeamSimple(UserDto user, UUID agend AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - Optional agendaTeam = agendaTeamProfileRepository.findByAgendaAndIsExistTrue(agenda, agendaProfile) + Optional agendaTeam = agendaTeamProfileRepository.findByAgendaProfileAndIsExistTrue(agenda, + agendaProfile) .map(AgendaTeamProfile::getAgendaTeam); if (agendaTeam.isEmpty()) { return Optional.empty(); @@ -128,7 +130,7 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD throw new BusinessException(LOCATION_NOT_VALID); } - agendaTeamProfileRepository.findByAgendaAndIsExistTrue(agenda, agendaProfile).ifPresent(teamProfile -> { + agendaTeamProfileRepository.findByAgendaProfileAndIsExistTrue(agenda, agendaProfile).ifPresent(teamProfile -> { throw new DuplicationException(TEAM_FORBIDDEN); }); @@ -223,9 +225,9 @@ public List agendaTeamProfileLeave(UserDto user, AgendaTeam agend /** * 아젠다 팀 공개 모집인 팀 목록 조회 - * @param user 사용자 정보, PageRequestDto 페이지네이션 요청 정보, agendaId 아젠다 아이디 + * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ - public List listOpenTeam(UserDto user, UUID agendaKey, Pageable pageable) { + public List listOpenTeam(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); @@ -235,4 +237,25 @@ public List listOpenTeam(UserDto user, UUID agendaKey, Pageable .map(OpenTeamResDto::new) .collect(Collectors.toList()); } + + /** + * 아젠다 팀 확정된 팀 목록 조회 + * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 + */ + public List listConfirmTeam(UUID agendaKey, Pageable pageable) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + List agendaTeams = agendaTeamRepository.findByAgendaAndStatus(agenda, CONFIRM, pageable) + .getContent(); + return agendaTeams.stream() + .map(agendaTeam -> { + List coalitions = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam).stream() + .map(agendaTeamProfile -> agendaTeamProfile.getProfile().getCoalition()) + .collect(Collectors.toList()); + return new ConfirmTeamResDto(agendaTeam, coalitions); + }) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 39db22910..c00a21de6 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -27,6 +27,7 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; @@ -1005,7 +1006,7 @@ public void notTeamMateFail() throws Exception { @Nested @DisplayName("OPEN팀 조회 테스트") - class OpenTeamTest { + class OpenTeamListTest { @BeforeEach void beforeEach() { seoulUser = testDataUtils.createNewUser(); @@ -1082,4 +1083,85 @@ public void noAgendaFail() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("CONFIRM팀 조회 테스트") + class ConfirmTeamListTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("200 CONFIRM팀 조회 성공") + public void confirmTeamGetSuccess(int page) throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + List teams = new ArrayList<>(); + teams.addAll(agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.CONFIRM)); + PageRequestDto req = new PageRequestDto(page, 5); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + get("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("page", String.valueOf(page)) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); + // then + assertThat(result).hasSize(((page - 1) * 5) < teams.size() + ? Math.min(5, teams.size() - (page - 1) * 5) : 0); + teams.sort((a, b) -> b.getId().compareTo(a.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); + } + } + + @Test + @DisplayName("200 CONFIRM팀 없을때 조회 성공") + public void confirmTeamGetSuccessNoTeam() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + // when + String res = mockMvc.perform( + get("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + get("/agenda/team/confirm") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 5e52a8427..85f6e91c8 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -14,7 +14,7 @@ public interface AgendaTeamProfileRepository extends JpaRepository { @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam.agenda = :agenda " + "AND atp.profile = :agendaProfile AND atp.isExist = true") - Optional findByAgendaAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + Optional findByAgendaProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") List findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 13ccd9d18..5b14c161d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -31,4 +31,7 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); + + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status") + Page findByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status, Pageable pageable); } From 41e61384f2dd4eca0b0aa204e103cfc13866c21f Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:04:37 +0900 Subject: [PATCH 042/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8C=80=20?= =?UTF-8?q?=EC=B0=B8=EA=B0=80=ED=95=98=EA=B8=B0=20API=20#849=20(#903)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 11 + .../agendateam/service/AgendaTeamService.java | 35 +- .../java/gg/agenda/api/AgendaMockData.java | 1 + .../agendateam/AgendaTeamControllerTest.java | 318 ++++++++++++++++-- .../src/main/java/gg/data/agenda/Agenda.java | 6 + .../main/java/gg/data/agenda/AgendaTeam.java | 26 +- .../gg/data/agenda/AgendaTeamProfile.java | 10 +- .../resources/db/migration/V3__agenda.sql | 2 + .../agenda/AgendaTeamProfileRepository.java | 2 + .../java/gg/utils/exception/ErrorCode.java | 3 +- .../agenda/AgendaAnnouncementFixture.java | 59 ++++ .../utils/fixture/agenda/AgendaFixture.java | 109 ++++++ .../fixture/agenda/AgendaTeamFixture.java | 86 +++++ .../agenda/AgendaTeamProfileFixture.java | 27 ++ .../utils/fixture/agenda/TicketFixture.java | 29 ++ 15 files changed, 684 insertions(+), 40 deletions(-) create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index a79b0d311..19f92b5c0 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -128,4 +128,15 @@ public ResponseEntity> confirmTeamList(@RequestBody @Val List confirmTeamResDto = agendaTeamService.listConfirmTeam(agendaKey, pageable); return ResponseEntity.ok(confirmTeamResDto); } + + /** + * 아젠다 팀 참여하기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ + @PostMapping("/join") + public ResponseEntity attendTeamModify(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + agendaTeamService.modifyAttendTeam(user, teamKeyReqDto, agendaKey); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 62e1c4509..7898b6412 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -101,7 +101,7 @@ public TeamDetailsResDto detailsAgendaTeam(UserDto user, UUID agendaKey, TeamKey if (agendaTeam.getStatus().equals(CONFIRM)) { // 팀이 확정 상태인 경우에 if (agendaTeamProfileList.stream() // 팀에 속한 유저가 아닌 경우 .noneMatch(profile -> profile.getProfile().getUserId().equals(user.getId()))) { - throw new ForbiddenException(TEAM_FORBIDDEN); // 조회 불가 + throw new ForbiddenException(NOT_TEAM_MATE); // 조회 불가 } } return new TeamDetailsResDto(agendaTeam, agendaTeamProfileList); @@ -131,7 +131,7 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD } agendaTeamProfileRepository.findByAgendaProfileAndIsExistTrue(agenda, agendaProfile).ifPresent(teamProfile -> { - throw new DuplicationException(TEAM_FORBIDDEN); + throw new DuplicationException(AGENDA_TEAM_FORBIDDEN); }); if (agenda.getIsOfficial()) { @@ -146,7 +146,7 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD }); AgendaTeam agendaTeam = TeamCreateReqDto.toEntity(teamCreateReqDto, agenda, user.getIntraId()); - AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(agendaTeam, agendaProfile); + AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(agendaTeam, agenda, agendaProfile); agendaRepository.save(agenda); agendaTeamRepository.save(agendaTeam); agendaTeamProfileRepository.save(agendaTeamProfile); @@ -258,4 +258,33 @@ public List listConfirmTeam(UUID agendaKey, Pageable pageable }) .collect(Collectors.toList()); } + + /** + * 아젠다 팀 참여하기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ + public void modifyAttendTeam(UserDto user, TeamKeyReqDto teamKeyReqDto, UUID agendaKey) { + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + + Ticket ticket = ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(agendaProfile) + .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); + + agendaTeamProfileRepository.findByAgendaAndProfileAndIsExistTrue(agenda, agendaProfile) + .ifPresent(profile -> { + throw new ForbiddenException(AGENDA_TEAM_FORBIDDEN); + }); + + agenda.attendTeam(agendaProfile.getLocation(), LocalDateTime.now()); + agendaTeam.attendTeam(agenda); + ticket.useTicket(agenda.getAgendaKey()); + agendaTeamProfileRepository.save(new AgendaTeamProfile(agendaTeam, agenda, agendaProfile)); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index ad23ec79a..1884f5a3a 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -638,6 +638,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile agendaProfile) { AgendaTeamProfile agendaTeamProfile = AgendaTeamProfile.builder() .agendaTeam(agendaTeam) + .agenda(agendaTeam.getAgenda()) .profile(agendaProfile) .isExist(true) .build(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index c00a21de6..ac59b43c2 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -1,12 +1,12 @@ package gg.agenda.api.user.agendateam; +import static gg.data.agenda.type.AgendaStatus.*; import static gg.data.agenda.type.Location.*; import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -35,8 +35,6 @@ import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; -import gg.data.agenda.Ticket; -import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.user.User; import gg.repo.agenda.AgendaTeamProfileRepository; @@ -45,6 +43,10 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaTeamFixture; +import gg.utils.fixture.agenda.AgendaTeamProfileFixture; +import gg.utils.fixture.agenda.TicketFixture; @IntegrationTest @AutoConfigureMockMvc @@ -64,6 +66,14 @@ public class AgendaTeamControllerTest { private AgendaTeamRepository agendaTeamRepository; @Autowired private AgendaTeamProfileRepository agendaTeamProfileRepository; + @Autowired + private AgendaFixture agendaFixture; + @Autowired + private AgendaTeamFixture agendaTeamFixture; + @Autowired + private AgendaTeamProfileFixture agendaTeamProfileFixture; + @Autowired + private TicketFixture ticketFixture; User seoulUser; User gyeongsanUser; User anotherSeoulUser; @@ -92,7 +102,7 @@ void beforeEach() { public void addNewTeamStatusSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -120,7 +130,7 @@ public void addNewTeamStatusSeoul() throws Exception { public void addNewTeamStatusGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -148,7 +158,7 @@ public void addNewTeamStatusGyeongsan() throws Exception { public void addNewTeamStatusMixFromSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -176,7 +186,7 @@ public void addNewTeamStatusMixFromSeoul() throws Exception { public void addNewTeamStatusMixFromGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -204,7 +214,7 @@ public void addNewTeamStatusMixFromGyeongsan() throws Exception { public void addNewTeamStatusMixFromMixToSeoul() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -232,7 +242,7 @@ public void addNewTeamStatusMixFromMixToSeoul() throws Exception { public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(MIX); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "MIX", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -260,7 +270,7 @@ public void addNewTeamStatusMixFromMixToGyeongsan() throws Exception { public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -279,7 +289,7 @@ public void noAgendaFail() throws Exception { public void notValidAgendaLocation() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -297,8 +307,8 @@ public void notValidAgendaLocation() throws Exception { @DisplayName("400 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + Agenda agenda = agendaMockData.createAgenda(CONFIRM); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -317,7 +327,7 @@ public void notValidAgendaStatus() throws Exception { public void notValidAgendaTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(5); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -336,7 +346,7 @@ public void notValidAgendaTeam() throws Exception { public void notValidAgendaDeadline() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "GYEONGSAN", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -355,7 +365,7 @@ public void notValidAgendaDeadline() throws Exception { public void agendaHostFail() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(seoulUser.getIntraId()); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -374,7 +384,7 @@ public void agendaHostFail() throws Exception { public void notValidUserLocation() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(gyeongsanUserAgendaProfile); + agendaMockData.createTicket(gyeongsanUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -393,7 +403,7 @@ public void notValidUserLocation() throws Exception { public void alreadyTeamNameExist() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); AgendaTeam team = agendaMockData.createAgendaTeam(agenda); TeamCreateReqDto req = new TeamCreateReqDto(team.getName(), true, "SEOUL", "teamContent"); @@ -413,9 +423,9 @@ public void alreadyTeamNameExist() throws Exception { public void alreadyTeamExistForAgenda() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - Ticket ticket = agendaMockData.createTicket(seoulUserAgendaProfile); + agendaMockData.createTicket(seoulUserAgendaProfile); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser); - AgendaTeamProfile agendaTeamProfile = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("newName", true, "SEOUL", "teamContent"); String content = objectMapper.writeValueAsString(req); @@ -513,7 +523,7 @@ public void teamDetailsGetFailByNoTeam() throws Exception { @DisplayName("403 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByConfirmTeam() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + Agenda agenda = agendaMockData.createAgenda(CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); @@ -531,7 +541,7 @@ public void teamDetailsGetFailByConfirmTeam() throws Exception { @DisplayName("404 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByCancelTeam() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + Agenda agenda = agendaMockData.createAgenda(CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -775,7 +785,7 @@ public void alreadyConfirmTeamFail() throws Exception { @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + Agenda agenda = agendaMockData.createAgenda(CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -867,8 +877,10 @@ public void leaveTeamMateSuccess() throws Exception { .andExpect(status().isNoContent()); // then AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; assertThat(updatedTeam.getMateCount()).isEqualTo(1); AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) .ifPresent(ticket -> { @@ -896,10 +908,13 @@ public void leaveTeamLeaderSuccess() throws Exception { .andExpect(status().isNoContent()); // then AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; assertThat(updatedTeam.getMateCount()).isEqualTo(0); AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); AgendaTeamProfile updatedAtpLeader = agendaTeamProfileRepository.findById(atpLeader.getId()).orElse(null); + assert updatedAtpLeader != null; assertThat(updatedAtpLeader.getIsExist()).isFalse(); ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) .ifPresent(ticket -> { @@ -969,7 +984,7 @@ public void notValidAgendaDeadline() throws Exception { @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(AgendaStatus.CONFIRM); + Agenda agenda = agendaMockData.createAgenda(CONFIRM); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -1023,8 +1038,7 @@ void beforeEach() { public void openTeamGetSuccess(int page) throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - List teams = new ArrayList<>(); - teams.addAll(agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.OPEN)); + List teams = agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.OPEN); PageRequestDto req = new PageRequestDto(page, 5); String content = objectMapper.writeValueAsString(req); // when @@ -1104,8 +1118,7 @@ void beforeEach() { public void confirmTeamGetSuccess(int page) throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - List teams = new ArrayList<>(); - teams.addAll(agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.CONFIRM)); + List teams = agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.CONFIRM); PageRequestDto req = new PageRequestDto(page, 5); String content = objectMapper.writeValueAsString(req); // when @@ -1164,4 +1177,253 @@ public void noAgendaFail() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("팀 참가 신청 테스트") + class ApplyTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("201 팀 참가 신청 성공") + public void applyTeamSuccess() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; + assertThat(updatedTeam.getMateCount()).isEqualTo(2); + AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findByAgendaAndProfileAndIsExistTrue(agenda, + seoulUserAgendaProfile).orElse(null); + assertThat(updatedAtp.getIsExist()).isTrue(); + } + + @Test + @DisplayName("404 agendaProfile 없음으로 인한 실패") + public void noAgendaProfileFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + User noProfileUser = testDataUtils.createNewUser(); + String noProfileUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(noProfileUser); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + noProfileUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + UUID noTeamKey = UUID.randomUUID(); + TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("404 team 없음으로 인한 실패") + public void noTeamFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("400 참가 불가능한 지역으로 인한 실패") + public void notValidAgendaLocation() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(gyeongsanUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 참가 불가능한 인원으로 인한 실패") + public void notValidAgendaTeam() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda.getMaxPeople(), agenda, seoulUser, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 참가 불가능한 Agenda 시간으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(LocalDateTime.now().minusHours(50)); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 참가 불가능한 Agenda status 으로 인한 실패") + public void notValidAgendaDeadline() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(CONFIRM); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("409 이미 같은 아젠다에 참가 신청으로 인한 실패") + public void alreadyApplyFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + ticketFixture.createTicket(seoulUserAgendaProfile); + agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("403 티켓 없음으로 인한 실패") + public void noTicketFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("404 참가 불가능한 Team Status Cancel로 인한 실패") + public void notValidTeamStatus() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL, AgendaTeamStatus.CANCEL); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("409 참가 불가능한 Team Status Confirm으로 인한 실패") + public void notValidTeamStatusConfirm() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(SEOUL); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL, AgendaTeamStatus.CONFIRM); + ticketFixture.createTicket(seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + post("/agenda/team/join") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index df1a6e597..e438aa55e 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -133,6 +133,12 @@ public void confirmTeam(Location location, LocalDateTime now) { mustHaveCapacity(); } + public void attendTeam(Location location, LocalDateTime now) { + mustBeWithinLocation(location); + mustStatusOnGoing(); + mustBeforeDeadline(now); + } + public void cancelTeam(LocalDateTime now) { mustStatusOnGoing(); mustBeforeDeadline(now); diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 7e9657f09..5bab5a877 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -1,5 +1,6 @@ package gg.data.agenda; +import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.utils.exception.ErrorCode.*; import java.util.UUID; @@ -91,27 +92,40 @@ public void acceptAward(String award, int awardPriority) { } public void confirm() { - if (this.status == AgendaTeamStatus.CANCEL) { + if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); } - if (this.status == AgendaTeamStatus.CONFIRM) { + if (this.status == CONFIRM) { throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); } - this.status = AgendaTeamStatus.CONFIRM; + this.status = CONFIRM; } public void leaveTeamLeader() { - if (this.status == AgendaTeamStatus.CANCEL) { + if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); } - this.status = AgendaTeamStatus.CANCEL; + this.status = CANCEL; this.mateCount = 0; } public void leaveTeamMate() { - if (this.status == AgendaTeamStatus.CANCEL) { + if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); } this.mateCount--; } + + public void attendTeam(Agenda agenda) { + if (this.status == CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + if (this.status == CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } + if (this.mateCount >= agenda.getMaxPeople()) { + throw new BusinessException(AGENDA_TEAM_FULL); + } + this.mateCount++; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index 629c70577..46dcad229 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -27,6 +27,10 @@ public class AgendaTeamProfile extends BaseTimeEntity { @JoinColumn(name = "profile_id", nullable = false) private AgendaProfile profile; + @ManyToOne + @JoinColumn(name = "agenda_id", nullable = false) + private Agenda agenda; + @ManyToOne @JoinColumn(name = "agenda_team_id", nullable = false) private AgendaTeam agendaTeam; @@ -35,14 +39,16 @@ public class AgendaTeamProfile extends BaseTimeEntity { private Boolean isExist; @Builder - public AgendaTeamProfile(AgendaProfile profile, AgendaTeam agendaTeam, Boolean isExist) { + public AgendaTeamProfile(AgendaProfile profile, Agenda agenda, AgendaTeam agendaTeam, Boolean isExist) { this.profile = profile; + this.agenda = agenda; this.agendaTeam = agendaTeam; this.isExist = isExist; } - public AgendaTeamProfile(AgendaTeam agendaTeam, AgendaProfile profile) { + public AgendaTeamProfile(AgendaTeam agendaTeam, Agenda agenda, AgendaProfile profile) { this.agendaTeam = agendaTeam; + this.agenda = agenda; this.profile = profile; this.isExist = true; } diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 035250cae..b9cff480f 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -80,12 +80,14 @@ CREATE TABLE `agenda_team_profile` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `profile_id` BIGINT NOT NULL, + `agenda_id` BIGINT NOT NULL, `agenda_team_id` BIGINT NOT NULL, `is_exist` BIT(1) NOT NULL, `created_at` DATETIME NOT NULL, `modified_at` DATETIME NOT NULL, PRIMARY KEY (`id`), KEY `fk_agenda_team_profile_profile_profile_id` (`profile_id`), + KEY `fk_agenda_team_profile_agenda_agenda_id` (`agenda_id`), KEY `fk_agenda_team_profile_agenda_team_agenda_team_id` (`agenda_team_id`), CONSTRAINT `fk_agenda_team_profile_profile_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `agenda_profile` (`id`), CONSTRAINT `fk_agenda_team_profile_agenda_team_agenda_team_id` FOREIGN KEY (`agenda_team_id`) REFERENCES `agenda_team` (`id`) diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 85f6e91c8..1894e4b35 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -18,4 +18,6 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); + + Optional findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 328f06c53..08ff980e4 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -197,6 +197,7 @@ public enum ErrorCode { AGENDA_TEAM_ALREADY_CONFIRM(409, "AG", "이미 확정된 팀입니다."), AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), NOT_ENOUGH_TEAM_MEMBER(400, "AG", "팀원이 부족합니다."), + AGENDA_TEAM_FULL(400, "AG", "팀이 꽉 찼습니다."), AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), @@ -208,7 +209,7 @@ public enum ErrorCode { CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), AGENDA_MODIFICATION_FORBIDDEN(403, "AG", "개최자만 일정을 수정할 수 있습니다."), LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), - TEAM_FORBIDDEN(403, "AG", "일정에는 한 팀으로만 참여할 수 있습니다."), + AGENDA_TEAM_FORBIDDEN(403, "AG", "일정에 참여한 팀이 있습니다."), NOT_TEAM_MATE(403, "AG", "팀원이 아닙니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java new file mode 100644 index 000000000..07ee694b3 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java @@ -0,0 +1,59 @@ +package gg.utils.fixture.agenda; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.repo.agenda.AgendaAnnouncementRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AgendaAnnouncementFixture { + + private final AgendaAnnouncementRepository agendaAnnouncementRepository; + + private final EntityManager em; + + public AgendaAnnouncement createAgendaAnnouncement(Agenda agenda) { + AgendaAnnouncement announcement = AgendaAnnouncement.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .isShow(true) + .agenda(agenda) + .build(); + em.persist(announcement); + em.flush(); + em.clear(); + return announcement; + } + + public List createAgendaAnnouncementList(Agenda agenda, int size) { + List announcements = new ArrayList<>(); + for (int i = 0; i < size; i++) { + announcements.add(AgendaAnnouncement.builder() + .title("title " + i) + .content("content " + i) + .isShow(true) + .agenda(agenda) + .build()); + } + return agendaAnnouncementRepository.saveAll(announcements); + } + + public List createAgendaAnnouncementList(Agenda agenda, int size, boolean isShow) { + List announcements = new ArrayList<>(); + for (int i = 0; i < size; i++) { + announcements.add(AgendaAnnouncement.builder() + .title("title " + i) + .content("content " + i) + .isShow(isShow) + .agenda(agenda) + .build()); + } + return agendaAnnouncementRepository.saveAll(announcements); + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java new file mode 100644 index 000000000..7c206cb75 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -0,0 +1,109 @@ +package gg.utils.fixture.agenda; + +import static gg.data.agenda.type.AgendaStatus.*; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import gg.repo.agenda.AgendaRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaFixture { + + private final AgendaRepository agendaRepository; + + public Agenda createAgenda() { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(ON_GOING) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(Location location) { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(ON_GOING) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(location) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(LocalDateTime localDateTime) { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(localDateTime) + .startTime(localDateTime.plusDays(2)) + .endTime(localDateTime.plusDays(3)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(ON_GOING) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(AgendaStatus agendaStatus) { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(agendaStatus) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java new file mode 100644 index 000000000..95e5483b8 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -0,0 +1,86 @@ +package gg.utils.fixture.agenda; + +import static gg.data.agenda.type.AgendaTeamStatus.*; +import static gg.data.agenda.type.Location.*; + +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import gg.repo.agenda.AgendaTeamRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaTeamFixture { + private final AgendaTeamRepository agendaTeamRepository; + + public AgendaTeam createAgendaTeam(Agenda agenda) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId("leaderIntraId") + .status(OPEN) + .location(MIX) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, Location location) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId("leaderIntraId") + .status(OPEN) + .location(location) + .mateCount(1) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(int mateCount, Agenda agenda, User seoulUser, Location location) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId(seoulUser.getIntraId()) + .status(OPEN) + .location(location) + .mateCount(mateCount) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, Location location, AgendaTeamStatus agendaTeamStatus) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId("leaderIntraId") + .status(agendaTeamStatus) + .location(location) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java new file mode 100644 index 000000000..0577fb385 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java @@ -0,0 +1,27 @@ +package gg.utils.fixture.agenda; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; +import gg.repo.agenda.AgendaTeamProfileRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaTeamProfileFixture { + private final AgendaTeamProfileRepository agendaTeamProfileRepository; + + public AgendaTeamProfile createAgendaTeamProfile(Agenda agenda, AgendaTeam agendaTeam, + AgendaProfile agendaProfile) { + AgendaTeamProfile agendaTeamProfile = AgendaTeamProfile.builder() + .agenda(agenda) + .agendaTeam(agendaTeam) + .profile(agendaProfile) + .isExist(true) + .build(); + return agendaTeamProfileRepository.save(agendaTeamProfile); + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java new file mode 100644 index 000000000..4a4c1c787 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java @@ -0,0 +1,29 @@ +package gg.utils.fixture.agenda; + +import java.time.LocalDateTime; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.repo.agenda.TicketRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class TicketFixture { + private final TicketRepository ticketRepository; + + public Ticket createTicket(AgendaProfile agendaProfile) { + Ticket ticket = Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(null) + .usedTo(null) + .isApproved(true) + .approvedAt(LocalDateTime.now().minusDays(1)) + .isUsed(false) + .usedAt(null) + .build(); + return ticketRepository.save(ticket); + } +} From b25a61d29c15471d3ae05927586fa13cdfc089bd Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:07:14 +0900 Subject: [PATCH 043/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20isRank?= =?UTF-8?q?ing=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20#899=20(#900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/agenda/controller/response/AgendaResDto.java | 6 +++++- .../user/agenda/controller/response/AgendaSimpleResDto.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java index ec62320c3..6b6373a44 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java @@ -50,6 +50,8 @@ public class AgendaResDto { private Boolean isOfficial; + private Boolean isRanking; + private String announcementTitle; @Builder @@ -57,7 +59,7 @@ public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime age LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, String agendaHost, Location agendaLocation, AgendaStatus agendaStatus, LocalDateTime createdAt, String announcementTitle, - boolean isOfficial) { + Boolean isOfficial, Boolean isRanking) { this.agendaTitle = agendaTitle; this.agendaContents = agendaContents; this.agendaDeadLine = agendaDeadLine; @@ -75,6 +77,7 @@ public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime age this.createdAt = createdAt; this.announcementTitle = announcementTitle; this.isOfficial = isOfficial; + this.isRanking = isRanking; } @Mapper @@ -98,6 +101,7 @@ public interface MapStruct { @Mapping(target = "agendaStatus", source = "agenda.status") @Mapping(target = "createdAt", source = "agenda.createdAt") @Mapping(target = "isOfficial", source = "agenda.isOfficial") + @Mapping(target = "isRanking", source = "agenda.isRanking") @Mapping(target = "announcementTitle", source = "announcementTitle") AgendaResDto toDto(Agenda agenda, String announcementTitle); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java index 6babae389..a384da73c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java @@ -39,10 +39,12 @@ public class AgendaSimpleResDto { private Boolean isOfficial; + private Boolean isRanking; + @Builder public AgendaSimpleResDto(String agendaTitle, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, - int agendaMaxPeople, Location agendaLocation, UUID agendaKey, boolean isOfficial) { + int agendaMaxPeople, Location agendaLocation, UUID agendaKey, Boolean isOfficial, Boolean isRanking) { this.agendaTitle = agendaTitle; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; @@ -54,6 +56,7 @@ public AgendaSimpleResDto(String agendaTitle, LocalDateTime agendaDeadLine, Loca this.agendaLocation = agendaLocation; this.agendaKey = agendaKey; this.isOfficial = isOfficial; + this.isRanking = isRanking; } @Mapper @@ -71,6 +74,7 @@ public interface MapStruct { @Mapping(target = "agendaLocation", source = "location") @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "isOfficial", source = "isOfficial") + @Mapping(target = "isRanking", source = "isRanking") AgendaSimpleResDto toDto(Agenda agenda); } } From 1f8dfc10f81c2dd122dfe3903a5cfe143a34f83e Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:13:50 +0900 Subject: [PATCH 044/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20#888=20(#897)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgendaAnnouncementAdminRepository.java | 15 ++ .../controller/AgendaAdminController.java | 13 +- .../AgendaAnnouncementAdminController.java | 46 +++++ .../AgendaAnnouncementAdminResDto.java | 45 +++++ .../AgendaAnnouncementAdminService.java | 33 ++++ ...AgendaAnnouncementAdminControllerTest.java | 158 ++++++++++++++++++ .../AgendaAnnouncementAdminServiceTest.java | 99 +++++++++++ .../java/gg/utils/AgendaTestDataUtils.java | 23 +++ 8 files changed, 423 insertions(+), 9 deletions(-) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAnnouncementAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/response/AgendaAnnouncementAdminResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAnnouncementAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAnnouncementAdminRepository.java new file mode 100644 index 000000000..23caaa92f --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAnnouncementAdminRepository.java @@ -0,0 +1,15 @@ +package gg.admin.repo.agenda; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; + +@Repository +public interface AgendaAnnouncementAdminRepository extends JpaRepository { + + Page findAllByAgenda(Agenda agenda, Pageable pageable); +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index f32505758..6c4f69ad7 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -33,12 +33,9 @@ public class AgendaAdminController { private final AgendaAdminService agendaAdminService; - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공") - }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공")}) @GetMapping("/request/list") - public ResponseEntity> agendaList( - @RequestBody @Valid PageRequestDto pageDto) { + public ResponseEntity> agendaList(@RequestBody @Valid PageRequestDto pageDto) { int page = pageDto.getPage(); int size = pageDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); @@ -48,14 +45,12 @@ public ResponseEntity> agendaList( return ResponseEntity.ok(agendaDtos); } - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Agenda 수정 성공"), + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Agenda 수정 성공"), @ApiResponse(responseCode = "400", description = "Agenda 수정 요청이 잘못됨"), @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음"), @ApiResponse(responseCode = "409", description = "Agenda 지역을 변경할 수 없음"), @ApiResponse(responseCode = "409", description = "Agenda 팀 제한을 변경할 수 없음"), - @ApiResponse(responseCode = "409", description = "Agenda 팀 인원 제한을 변경할 수 없음") - }) + @ApiResponse(responseCode = "409", description = "Agenda 팀 인원 제한을 변경할 수 없음")}) @PatchMapping("/request") public ResponseEntity agendaUpdate(@RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid AgendaAdminUpdateReqDto agendaDto) { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java new file mode 100644 index 000000000..1d55639f6 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java @@ -0,0 +1,46 @@ +package gg.agenda.api.admin.agendaannouncement.controller; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.admin.agendaannouncement.controller.response.AgendaAnnouncementAdminResDto; +import gg.agenda.api.admin.agendaannouncement.service.AgendaAnnouncementAdminService; +import gg.data.agenda.AgendaAnnouncement; +import gg.utils.dto.PageRequestDto; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/admin/agenda/announcement") +@RequiredArgsConstructor +public class AgendaAnnouncementAdminController { + + private final AgendaAnnouncementAdminService agendaAnnouncementAdminService; + + @GetMapping() + public ResponseEntity> agendaAnnouncementList( + @RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid PageRequestDto pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List announcements = agendaAnnouncementAdminService + .getAgendaAnnouncementList(agendaKey, pageable); + List announceDtos = announcements.stream() + .map(AgendaAnnouncementAdminResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(announceDtos); + } + +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/response/AgendaAnnouncementAdminResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/response/AgendaAnnouncementAdminResDto.java new file mode 100644 index 000000000..d70d1cdbe --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/response/AgendaAnnouncementAdminResDto.java @@ -0,0 +1,45 @@ +package gg.agenda.api.admin.agendaannouncement.controller.response; + +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.AgendaAnnouncement; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAnnouncementAdminResDto { + + private Long id; + + private String title; + + private String content; + + private Boolean isShow; + + private LocalDateTime createdAt; + + @Builder + public AgendaAnnouncementAdminResDto(Long id, String title, String content, Boolean isShow, + LocalDateTime createdAt) { + this.id = id; + this.title = title; + this.content = content; + this.isShow = isShow; + this.createdAt = createdAt; + } + + @Mapper + public interface MapStruct { + + MapStruct INSTANCE = Mappers.getMapper(MapStruct.class); + + AgendaAnnouncementAdminResDto toDto(AgendaAnnouncement announcement); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java new file mode 100644 index 000000000..b33d6b41f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java @@ -0,0 +1,33 @@ +package gg.agenda.api.admin.agendaannouncement.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaAnnouncementAdminService { + + private final AgendaAdminRepository agendaAdminRepository; + + private final AgendaAnnouncementAdminRepository agendaAnnouncementAdminRepository; + + @Transactional(readOnly = true) + public List getAgendaAnnouncementList(UUID agendaKey, Pageable pageable) { + Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + return agendaAnnouncementAdminRepository.findAllByAgenda(agenda, pageable).getContent(); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java new file mode 100644 index 000000000..102c2d5db --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java @@ -0,0 +1,158 @@ +package gg.agenda.api.admin.agendaannouncement.controller; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.agenda.api.user.agendaannouncement.controller.response.AgendaAnnouncementResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.user.User; +import gg.utils.AgendaTestDataUtils; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; +import gg.utils.fixture.agenda.AgendaAnnouncementFixture; +import gg.utils.fixture.agenda.AgendaFixture; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaAnnouncementAdminControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaFixture agendaFixture; + + @Autowired + private AgendaAnnouncementFixture agendaAnnouncementFixture; + + @Autowired + private AgendaTestDataUtils agendaTestDataUtils; + + @Autowired + EntityManager em; + + @Autowired + AgendaAdminRepository agendaAdminRepository; + + @Autowired + AgendaAnnouncementAdminRepository agendaAnnouncementAdminRepository; + + private User user; + + private String accessToken; + + @BeforeEach + void setUp() { + user = testDataUtils.createAdminUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("Admin AgendaAnnouncement 상세 조회") + class GetAgendaAnnouncementListAdmin { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5, 6}) + @DisplayName("Admin AgendaAnnouncement 상세 조회 성공") + void getAgendaAnnouncementAdminSuccess(int page) throws Exception { + // given + int size = 10; + Agenda agenda = agendaFixture.createAgenda(); + List announcements = + agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, 37); + PageRequestDto pageDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageDto); + + // when + String response = mockMvc.perform(get("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAnnouncementResDto[] result = + objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + + // then + assertThat(result).hasSize(((page - 1) * size) < announcements.size() + ? Math.min(size, announcements.size() - (page - 1) * size) : 0); + announcements.sort((a, b) -> b.getId().compareTo(a.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); + } + } + + @Test + @DisplayName("Admin AgendaAnnouncement 상세 조회 성공 - 빈 리스트 반환") + void getAgendaAnnouncementAdminSuccessWithNoContent() throws Exception { + // given + int page = 1; + int size = 10; + Agenda agenda = agendaFixture.createAgenda(); + PageRequestDto pageDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageDto); + + // when + String response = mockMvc.perform(get("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaAnnouncementResDto[] result = + objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 상세 조회 실패 - Agenda가 없는 경우") + void getAgendaAnnouncementAdminFailedWithNoAgenda() throws Exception { + // given + int page = 1; + int size = 10; + PageRequestDto pageDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageDto); + + // expected + mockMvc.perform(get("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", UUID.randomUUID().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java new file mode 100644 index 000000000..4c8ea2d94 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java @@ -0,0 +1,99 @@ +package gg.agenda.api.admin.agendaannouncement.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.utils.annotation.UnitTest; +import gg.utils.exception.custom.NotExistException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@UnitTest +public class AgendaAnnouncementAdminServiceTest { + + @Mock + private AgendaAdminRepository agendaAdminRepository; + + @Mock + private AgendaAnnouncementAdminRepository agendaAnnouncementAdminRepository; + + @InjectMocks + private AgendaAnnouncementAdminService agendaAnnouncementAdminService; + + @Nested + @DisplayName("Admin AgendaAnnouncement 상세 조회") + class GetAgendaAnnouncementListAdmin { + + @Test + @DisplayName("Admin AgendaAnnouncement 상세 조회 성공") + void getAgendaAnnouncementAdminSuccess() { + // given + Agenda agenda = Agenda.builder().build(); + List announcements = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + announcements.add(AgendaAnnouncement.builder().agenda(agenda).build()); + } + Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending()); + when(agendaAdminRepository.findByAgendaKey(any(UUID.class))).thenReturn(Optional.of(agenda)); + when(agendaAnnouncementAdminRepository.findAllByAgenda(any(Agenda.class), any(Pageable.class))) + .thenReturn(new PageImpl<>(announcements)); + + // when + agendaAnnouncementAdminService.getAgendaAnnouncementList(agenda.getAgendaKey(), pageable); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any(UUID.class)); + verify(agendaAnnouncementAdminRepository, times(1)).findAllByAgenda(any(Agenda.class), any(Pageable.class)); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 상세 조회 성공 - 빈 리스트 반환") + void getAgendaAnnouncementAdminSuccessWithNoContent() { + // given + Agenda agenda = Agenda.builder().build(); + List announcements = new ArrayList<>(); + Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending()); + when(agendaAdminRepository.findByAgendaKey(any(UUID.class))).thenReturn(Optional.of(agenda)); + when(agendaAnnouncementAdminRepository.findAllByAgenda(any(Agenda.class), any(Pageable.class))) + .thenReturn(new PageImpl<>(announcements)); + + // when + agendaAnnouncementAdminService.getAgendaAnnouncementList(agenda.getAgendaKey(), pageable); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any(UUID.class)); + verify(agendaAnnouncementAdminRepository, times(1)).findAllByAgenda(any(Agenda.class), any(Pageable.class)); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 상세 조회 실패 - Agenda가 없는 경우") + void getAgendaAnnouncementAdminFailedWithNoAgenda() { + // given + Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending()); + when(agendaAdminRepository.findByAgendaKey(any(UUID.class))).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaAnnouncementAdminService.getAgendaAnnouncementList(UUID.randomUUID(), pageable)); + } + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java new file mode 100644 index 000000000..93348accf --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -0,0 +1,23 @@ +package gg.utils; + +import gg.data.agenda.Agenda; +import gg.utils.fixture.agenda.AgendaAnnouncementFixture; +import gg.utils.fixture.agenda.AgendaFixture; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AgendaTestDataUtils { + + private final AgendaFixture agendaFixture; + + private final AgendaAnnouncementFixture agendaAnnouncementFixture; + + public Agenda createAgendaAndAnnouncements(int size) { + Agenda agenda = agendaFixture.createAgenda(); + agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, size / 2, true); + agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, size - size / 2, false); + return agenda; + } +} From afc4a2575bd015e715a891ca4c6ab4a1bb1808f1 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:59:50 +0900 Subject: [PATCH 045/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AD=EC=A0=9C=ED=95=98=EA=B8=B0=20#889=20(#904?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgendaAnnouncementAdminController.java | 8 ++ .../AgendaAnnouncementAdminUpdateReqDto.java | 38 +++++++ .../AgendaAnnouncementAdminService.java | 8 ++ ...AgendaAnnouncementAdminControllerTest.java | 103 ++++++++++++++++++ .../AgendaAnnouncementAdminServiceTest.java | 28 +++++ .../gg/data/agenda/AgendaAnnouncement.java | 6 + .../java/gg/utils/exception/ErrorCode.java | 1 + 7 files changed, 192 insertions(+) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/request/AgendaAnnouncementAdminUpdateReqDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java index 1d55639f6..ce3a00382 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java @@ -11,11 +11,13 @@ import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.admin.agendaannouncement.controller.request.AgendaAnnouncementAdminUpdateReqDto; import gg.agenda.api.admin.agendaannouncement.controller.response.AgendaAnnouncementAdminResDto; import gg.agenda.api.admin.agendaannouncement.service.AgendaAnnouncementAdminService; import gg.data.agenda.AgendaAnnouncement; @@ -43,4 +45,10 @@ public ResponseEntity> agendaAnnouncementLis return ResponseEntity.ok(announceDtos); } + @PatchMapping() + public ResponseEntity updateAgendaAnnouncement( + @RequestBody @Valid AgendaAnnouncementAdminUpdateReqDto updateReqDto) { + agendaAnnouncementAdminService.updateAgendaAnnouncement(updateReqDto); + return ResponseEntity.noContent().build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/request/AgendaAnnouncementAdminUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/request/AgendaAnnouncementAdminUpdateReqDto.java new file mode 100644 index 000000000..667875730 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/request/AgendaAnnouncementAdminUpdateReqDto.java @@ -0,0 +1,38 @@ +package gg.agenda.api.admin.agendaannouncement.controller.request; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.Length; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAnnouncementAdminUpdateReqDto { + + @NotNull + private Long id; + + @NotBlank + @Length(max = 50) + private String title; + + @NotBlank + @Length(max = 1000) + private String content; + + @NotNull + private Boolean isShow; + + @Builder + public AgendaAnnouncementAdminUpdateReqDto(Long id, String title, String content, Boolean isShow) { + this.id = id; + this.title = title; + this.content = content; + this.isShow = isShow; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java index b33d6b41f..1ce2b2cc3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java @@ -11,6 +11,7 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.agenda.api.admin.agendaannouncement.controller.request.AgendaAnnouncementAdminUpdateReqDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.utils.exception.custom.NotExistException; @@ -30,4 +31,11 @@ public List getAgendaAnnouncementList(UUID agendaKey, Pageab .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); return agendaAnnouncementAdminRepository.findAllByAgenda(agenda, pageable).getContent(); } + + @Transactional + public void updateAgendaAnnouncement(AgendaAnnouncementAdminUpdateReqDto updateReqDto) { + AgendaAnnouncement announcement = agendaAnnouncementAdminRepository.findById(updateReqDto.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_ANNOUNCEMENT_NOT_FOUND)); + announcement.updateByAdmin(updateReqDto.getTitle(), updateReqDto.getContent(), updateReqDto.getIsShow()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java index 102c2d5db..02495a2fb 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java @@ -25,6 +25,7 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.agenda.api.admin.agendaannouncement.controller.request.AgendaAnnouncementAdminUpdateReqDto; import gg.agenda.api.user.agendaannouncement.controller.response.AgendaAnnouncementResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; @@ -155,4 +156,106 @@ void getAgendaAnnouncementAdminFailedWithNoAgenda() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("Admin AgendaAnnouncement 수정 및 삭제") + class UpdateAgendaAnnouncementAdmin { + @Test + @DisplayName("Admin AgendaAnnouncement 수정 성공") + void updateAgendaAnnouncementAdminSuccess() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaAnnouncement announcement = agendaAnnouncementFixture.createAgendaAnnouncement(agenda); + AgendaAnnouncementAdminUpdateReqDto updateReqDto = AgendaAnnouncementAdminUpdateReqDto.builder() + .id(announcement.getId()).isShow(!announcement.getIsShow()) + .title("수정된 공지사항 제목").content("수정된 공지사항 내용").build(); + String request = objectMapper.writeValueAsString(updateReqDto); + + // when + mockMvc.perform(patch("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); + AgendaAnnouncement result = agendaAnnouncementAdminRepository + .findById(announcement.getId()).orElseThrow(); + + // then + assertThat(result.getTitle()).isEqualTo(updateReqDto.getTitle()); + assertThat(result.getContent()).isEqualTo(updateReqDto.getContent()); + assertThat(result.getIsShow()).isEqualTo(updateReqDto.getIsShow()); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 수정 실패 - 존재하지 않는 공지사항") + void updateAgendaAnnouncementAdminFailedWithNoAnnouncement() throws Exception { + // given + AgendaAnnouncementAdminUpdateReqDto updateReqDto = AgendaAnnouncementAdminUpdateReqDto.builder() + .id(1L).isShow(true).title("수정된 공지사항 제목").content("수정된 공지사항 내용").build(); + String request = objectMapper.writeValueAsString(updateReqDto); + + // expected + mockMvc.perform(patch("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 수정 실패 - Update Dto에 id가 없는 경우") + void updateAgendaAnnouncementAdminFailedWithNoReqId() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaAnnouncement announcement = agendaAnnouncementFixture.createAgendaAnnouncement(agenda); + AgendaAnnouncementAdminUpdateReqDto updateReqDto = AgendaAnnouncementAdminUpdateReqDto.builder() + .isShow(!announcement.getIsShow()).title("수정된 공지사항 제목").content("수정된 공지사항 내용").build(); + String request = objectMapper.writeValueAsString(updateReqDto); + + // expected + mockMvc.perform(patch("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 수정 실패 - title이 너무 긴 경우") + void updateAgendaAnnouncementAdminFailedWithTooLongTitle() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaAnnouncement announcement = agendaAnnouncementFixture.createAgendaAnnouncement(agenda); + AgendaAnnouncementAdminUpdateReqDto updateReqDto = AgendaAnnouncementAdminUpdateReqDto.builder() + .id(announcement.getId()).isShow(!announcement.getIsShow()) + .title("수정된 공지사항 제목".repeat(10)).content("수정된 공지사항 내용").build(); + String request = objectMapper.writeValueAsString(updateReqDto); + + // expected + mockMvc.perform(patch("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 수정 실패 - content이 너무 긴 경우") + void updateAgendaAnnouncementAdminFailedWithTooLongContent() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaAnnouncement announcement = agendaAnnouncementFixture.createAgendaAnnouncement(agenda); + AgendaAnnouncementAdminUpdateReqDto updateReqDto = AgendaAnnouncementAdminUpdateReqDto.builder() + .id(announcement.getId()).isShow(!announcement.getIsShow()) + .title("수정된 공지사항 제목").content("수정된 공지사항 내용".repeat(100)).build(); + String request = objectMapper.writeValueAsString(updateReqDto); + + // expected + mockMvc.perform(patch("/admin/agenda/announcement") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java index 4c8ea2d94..e934f3a59 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminServiceTest.java @@ -20,6 +20,7 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaAnnouncementAdminRepository; +import gg.agenda.api.admin.agendaannouncement.controller.request.AgendaAnnouncementAdminUpdateReqDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.utils.annotation.UnitTest; @@ -96,4 +97,31 @@ void getAgendaAnnouncementAdminFailedWithNoAgenda() { () -> agendaAnnouncementAdminService.getAgendaAnnouncementList(UUID.randomUUID(), pageable)); } } + + @Nested + @DisplayName("Admin AgendaAnnouncement 수정 및 삭제") + class UpdateAgendaAnnouncementAdmin { + @Test + @DisplayName("Admin AgendaAnnouncement 수정 성공") + void updateAgendaAnnouncementAdminSuccess() { + // given + AgendaAnnouncement announcement = AgendaAnnouncement.builder().build(); + when(agendaAnnouncementAdminRepository.findById(any(Long.class))).thenReturn(Optional.of(announcement)); + + // expected + assertDoesNotThrow(() -> agendaAnnouncementAdminService.updateAgendaAnnouncement( + AgendaAnnouncementAdminUpdateReqDto.builder().id(1L).build())); + } + + @Test + @DisplayName("Admin AgendaAnnouncement 수정 실패 - AgendaAnnouncement가 없는 경우") + void updateAgendaAnnouncementAdminFailed() { + // given + when(agendaAnnouncementAdminRepository.findById(any(Long.class))).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, () -> agendaAnnouncementAdminService.updateAgendaAnnouncement( + AgendaAnnouncementAdminUpdateReqDto.builder().id(1L).build())); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java index 97a7d28dc..bdf5bf5dd 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaAnnouncement.java @@ -47,4 +47,10 @@ public AgendaAnnouncement(Long id, String title, String content, Boolean isShow, this.isShow = isShow; this.agenda = agenda; } + + public void updateByAdmin(String title, String content, Boolean isShow) { + this.title = title; + this.content = content; + this.isShow = isShow; + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 08ff980e4..eb0663a7b 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -197,6 +197,7 @@ public enum ErrorCode { AGENDA_TEAM_ALREADY_CONFIRM(409, "AG", "이미 확정된 팀입니다."), AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), NOT_ENOUGH_TEAM_MEMBER(400, "AG", "팀원이 부족합니다."), + AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG", "공지사항이 존재하지 않습니다."), AGENDA_TEAM_FULL(400, "AG", "팀이 꽉 찼습니다."), AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), From 820a81dd3b145a1a44ca694c33e02b43614b3156 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:37:47 +0900 Subject: [PATCH 046/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8C=80=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20API?= =?UTF-8?q?=20#850=20(#906)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 12 + .../controller/request/TeamUpdateReqDto.java | 35 +++ .../agendateam/service/AgendaTeamService.java | 58 ++-- .../agendateam/AgendaTeamControllerTest.java | 276 +++++++++++++++++- .../src/main/java/gg/data/agenda/Agenda.java | 10 +- .../main/java/gg/data/agenda/AgendaTeam.java | 36 +++ .../agenda/AgendaTeamProfileRepository.java | 2 + .../java/gg/utils/exception/ErrorCode.java | 4 +- .../fixture/agenda/AgendaTeamFixture.java | 33 +++ .../agenda/AgendaTeamProfileFixture.java | 10 + 10 files changed, 450 insertions(+), 26 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 19f92b5c0..c068fbf15 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -21,6 +21,7 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamUpdateReqDto; import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; @@ -139,4 +140,15 @@ public ResponseEntity attendTeamModify(@Parameter(hidden = true) @Login Us agendaTeamService.modifyAttendTeam(user, teamKeyReqDto, agendaKey); return ResponseEntity.status(HttpStatus.CREATED).build(); } + + /** + * 아젠다 팀 수정하기 + * @param user 사용자 정보, teamUpdateReqDto 팀 update 요청 정보, agendaId 아젠다 아이디 + */ + @PatchMapping + public ResponseEntity agendaTeamModify(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid TeamUpdateReqDto teamUpdateReqDto, @RequestParam("agenda_key") UUID agendaKey) { + agendaTeamService.modifyAgendaTeam(user, teamUpdateReqDto, agendaKey); + return ResponseEntity.noContent().build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java new file mode 100644 index 000000000..7610c9b70 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java @@ -0,0 +1,35 @@ +package gg.agenda.api.user.agendateam.controller.request; + +import java.util.UUID; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TeamUpdateReqDto { + @NotNull + private UUID teamKey; + @NotBlank + private String teamName; + @NotBlank + private String teamContent; + @NotNull + private Boolean teamIsPrivate; + @NotBlank + private String teamLocation; + + @Builder + public TeamUpdateReqDto(UUID teamKey, String teamName, String teamContent, Boolean teamIsPrivate, + String teamLocation) { + this.teamKey = teamKey; + this.teamName = teamName; + this.teamContent = teamContent; + this.teamIsPrivate = teamIsPrivate; + this.teamLocation = teamLocation; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 7898b6412..3e7730926 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -11,11 +11,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamUpdateReqDto; import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; @@ -120,7 +120,7 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - agenda.addTeam(Location.valueOf(teamCreateReqDto.getTeamLocation()), LocalDateTime.now()); + agenda.addTeam(Location.valueOfLocation(teamCreateReqDto.getTeamLocation()), LocalDateTime.now()); if (agenda.getHostIntraId().equals(user.getIntraId())) { throw new ForbiddenException(HOST_FORBIDDEN); @@ -194,39 +194,33 @@ public void agendaTeamLeave(UserDto user, UUID agendaKey, UUID teamKey) { agenda.cancelTeam(LocalDateTime.now()); List profiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); - List changedProfiles = agendaTeamProfileLeave(user, agendaTeam, profiles); - ticketService.refundTickets(changedProfiles, agendaKey); - } - /** - * 아젠다 팀 나가기 - * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 - * @Annotation 트랜잭션의 원자성을 보장하기 위해 부모 트랜잭션이 없을경우 예외를 발생시키는 Propagation.MANDATORY로 설정 - */ - @Transactional(propagation = Propagation.MANDATORY) - public List agendaTeamProfileLeave(UserDto user, AgendaTeam agendaTeam, - List profiles) { + List changedProfiles; + if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { - List changedProfiles = profiles + changedProfiles = profiles .stream() .peek(AgendaTeamProfile::leaveTeam) .map(AgendaTeamProfile::getProfile) .collect(Collectors.toList()); agendaTeam.leaveTeamLeader(); - return changedProfiles; + ticketService.refundTickets(changedProfiles, agendaKey); + return; } - AgendaTeamProfile teamMate = profiles.stream() + AgendaTeamProfile teamMateProfile = profiles.stream() .filter(profile -> profile.getProfile().getUserId().equals(user.getId())) .findFirst().orElseThrow(() -> new ForbiddenException(NOT_TEAM_MATE)); - teamMate.leaveTeam(); + teamMateProfile.leaveTeam(); agendaTeam.leaveTeamMate(); - return List.of(teamMate.getProfile()); + changedProfiles = List.of(teamMateProfile.getProfile()); + ticketService.refundTickets(changedProfiles, agendaKey); } /** * 아젠다 팀 공개 모집인 팀 목록 조회 * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ + @Transactional(readOnly = true) public List listOpenTeam(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); @@ -242,6 +236,7 @@ public List listOpenTeam(UUID agendaKey, Pageable pageable) { * 아젠다 팀 확정된 팀 목록 조회 * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ + @Transactional(readOnly = true) public List listConfirmTeam(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); @@ -263,6 +258,7 @@ public List listConfirmTeam(UUID agendaKey, Pageable pageable * 아젠다 팀 참여하기 * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 */ + @Transactional public void modifyAttendTeam(UserDto user, TeamKeyReqDto teamKeyReqDto, UUID agendaKey) { AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); @@ -287,4 +283,30 @@ public void modifyAttendTeam(UserDto user, TeamKeyReqDto teamKeyReqDto, UUID age ticket.useTicket(agenda.getAgendaKey()); agendaTeamProfileRepository.save(new AgendaTeamProfile(agendaTeam, agenda, agendaProfile)); } + + /** + * 아젠다 팀 수정하기 + * @param user 사용자 정보, teamUpdateReqDto 팀 수정 요청 정보, agendaId 아젠다 아이디 + */ + @Transactional + public void modifyAgendaTeam(UserDto user, TeamUpdateReqDto teamUpdateReqDto, UUID agendaKey) { + Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + + AgendaTeam agendaTeam = agendaTeamRepository + .findByAgendaAndTeamKeyAndStatus(agenda, teamUpdateReqDto.getTeamKey(), OPEN, CONFIRM) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + + if (!agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + throw new ForbiddenException(TEAM_LEADER_FORBIDDEN); + } + + List profiles = agendaTeamProfileRepository.findAllByAgendaTeam(agendaTeam); + + agenda.updateTeam(Location.valueOfLocation(teamUpdateReqDto.getTeamLocation()), LocalDateTime.now()); + agendaTeam.updateTeam(teamUpdateReqDto.getTeamName(), teamUpdateReqDto.getTeamContent(), + teamUpdateReqDto.getTeamIsPrivate(), Location.valueOfLocation(teamUpdateReqDto.getTeamLocation()), + profiles); + agendaTeamRepository.save(agendaTeam); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index ac59b43c2..e8f9c1572 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -27,6 +27,7 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; +import gg.agenda.api.user.agendateam.controller.request.TeamUpdateReqDto; import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; @@ -763,7 +764,7 @@ public void notValidTeamStatusFail() throws Exception { } @Test - @DisplayName("409 이미 CONFIRM된 팀으로 인한 실패") + @DisplayName("400 이미 CONFIRM된 팀으로 인한 실패") public void alreadyConfirmTeamFail() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); @@ -778,7 +779,7 @@ public void alreadyConfirmTeamFail() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()); + .andExpect(status().isBadRequest()); } @Test @@ -961,6 +962,45 @@ public void noTeamFail() throws Exception { .andExpect(status().isNotFound()); } + @Test + @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀장의 나가기 실패") + public void notValidAgendaTeamStatusWhenTeamLeader() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀원의 나가기 실패") + public void notValidAgendaTeamStatusWhenTeamMate() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + @Test @DisplayName("400 탈퇴 불가능한 Agenda 시간으로 인한 실패") public void notValidAgendaDeadline() throws Exception { @@ -1408,7 +1448,7 @@ public void notValidTeamStatus() throws Exception { } @Test - @DisplayName("409 참가 불가능한 Team Status Confirm으로 인한 실패") + @DisplayName("400 참가 불가능한 Team Status Confirm으로 인한 실패") public void notValidTeamStatusConfirm() throws Exception { //given Agenda agenda = agendaFixture.createAgenda(SEOUL); @@ -1423,7 +1463,235 @@ public void notValidTeamStatusConfirm() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()); + .andExpect(status().isBadRequest()); + } + } + + @Nested + @DisplayName("팀 수정 테스트") + class UpdateTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("204 팀장 팀 수정 성공") + public void updateTeamSuccess() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, SEOUL); + AgendaTeamProfile atp = agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; + assertThat(updatedTeam.getName()).isEqualTo("newName"); + assertThat(updatedTeam.getContent()).isEqualTo("newDesc"); + assertThat(updatedTeam.getIsPrivate()).isTrue(); + assertThat(updatedTeam.getLocation()).isEqualTo(MIX); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + TeamUpdateReqDto req = new TeamUpdateReqDto(UUID.randomUUID(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("404 team 없음으로 인한 실패") + public void noTeamFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + TeamUpdateReqDto req = new TeamUpdateReqDto(UUID.randomUUID(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("403 팀원 팀 수정 실패") + public void notTeamLeaderFail() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + agendaTeamProfileFixture.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "SEOUL"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("400 수정 불가능한 지역으로 인한 실패") + public void notValidAgendaLocation() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "GYEONGSAN"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 수정 불가능한 인원으로 인한 실패") + public void notValidAgendaTeam() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda.getMaxPeople(), agenda, seoulUser, MIX); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + agendaTeamProfileFixture.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "SEOUL"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 수정 불가능한 Agenda 시간으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(LocalDateTime.now().minusHours(50)); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 수정 불가능한 Agenda status 으로 인한 실패") + public void notValidAgendaDeadline() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(CONFIRM); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("404 수정 불가능한 Team Status Cancel로 인한 실패") + public void notValidTeamStatus() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("400 수정 불가능한 Team Status Confirm으로 인한 실패") + public void notValidTeamStatusConfirm() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 수정 불가능한 Team Status Cancel로 인한 실패") + public void notValidTeamStatusCancel() throws Exception { + //given + Agenda agenda = agendaFixture.createAgenda(MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); } } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index e438aa55e..d41d493d5 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -123,7 +123,6 @@ public void addTeam(Location location, LocalDateTime now) { mustStatusOnGoing(); mustBeforeDeadline(now); mustHaveCapacity(); - this.currentTeam++; } public void confirmTeam(Location location, LocalDateTime now) { @@ -131,6 +130,7 @@ public void confirmTeam(Location location, LocalDateTime now) { mustStatusOnGoing(); mustBeforeDeadline(now); mustHaveCapacity(); + this.currentTeam++; } public void attendTeam(Location location, LocalDateTime now) { @@ -139,6 +139,12 @@ public void attendTeam(Location location, LocalDateTime now) { mustBeforeDeadline(now); } + public void updateTeam(Location location, LocalDateTime now) { + mustBeWithinLocation(location); + mustStatusOnGoing(); + mustBeforeDeadline(now); + } + public void cancelTeam(LocalDateTime now) { mustStatusOnGoing(); mustBeforeDeadline(now); @@ -205,7 +211,7 @@ public void updateLocation(Location location, List teams) { .map(AgendaTeam::getLocation) .anyMatch(teamLocation -> !Location.isUnderLocation(location, teamLocation)); if (conflictAgendaLocation) { - throw new InvalidParameterException(AGENDA_UPDATE_LOCATION_CONFLICT); + throw new InvalidParameterException(UPDATE_LOCATION_NOT_VALID); } this.location = location; } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 5bab5a877..93484b0c5 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -3,6 +3,8 @@ import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.utils.exception.ErrorCode.*; +import java.util.List; +import java.util.Objects; import java.util.UUID; import javax.persistence.Column; @@ -19,6 +21,7 @@ import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.utils.exception.custom.BusinessException; +import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -105,6 +108,9 @@ public void leaveTeamLeader() { if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); } + if (this.status == CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } this.status = CANCEL; this.mateCount = 0; } @@ -113,6 +119,9 @@ public void leaveTeamMate() { if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); } + if (this.status == CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } this.mateCount--; } @@ -128,4 +137,31 @@ public void attendTeam(Agenda agenda) { } this.mateCount++; } + + public void updateTeam(String name, String content, Boolean isPrivate, Location location, + List profiles) { + if (this.status == CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + if (this.status == CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } + this.name = name; + this.content = content; + this.isPrivate = isPrivate; + updateLocation(location, profiles); + } + + public void updateLocation(Location location, List profiles) { + if (Objects.isNull(location)) { + return; + } + boolean conflictAgendaLocation = profiles.stream() + .map(AgendaTeamProfile::getProfile) + .anyMatch(profile -> !Location.isUnderLocation(location, profile.getLocation())); + if (conflictAgendaLocation) { + throw new InvalidParameterException(UPDATE_LOCATION_NOT_VALID); + } + this.location = location; + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 1894e4b35..412cf95e7 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -20,4 +20,6 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); Optional findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + + List findAllByAgendaTeam(AgendaTeam agendaTeam); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index eb0663a7b..c41943c6e 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -194,7 +194,7 @@ public enum ErrorCode { // agenda AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), - AGENDA_TEAM_ALREADY_CONFIRM(409, "AG", "이미 확정된 팀입니다."), + AGENDA_TEAM_ALREADY_CONFIRM(400, "AG", "이미 확정된 팀입니다."), AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), NOT_ENOUGH_TEAM_MEMBER(400, "AG", "팀원이 부족합니다."), AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG", "공지사항이 존재하지 않습니다."), @@ -202,7 +202,7 @@ public enum ErrorCode { AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), - AGENDA_UPDATE_LOCATION_CONFLICT(409, "AG", "지역을 변경할 수 없습니다."), + UPDATE_LOCATION_NOT_VALID(400, "AG", "지역을 변경할 수 없습니다."), AGENDA_CAPACITY_CONFLICT(409, "AG", "팀 제한을 변경할 수 없습니다."), AGENDA_TEAM_CAPACITY_CONFLICT(409, "AG", "팀 인원 제한을 변경할 수 없습니다."), HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index 95e5483b8..cb64b988e 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -2,6 +2,7 @@ import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Location.*; +import static java.util.UUID.*; import java.util.UUID; @@ -83,4 +84,36 @@ public AgendaTeam createAgendaTeam(Agenda agenda, Location location, AgendaTeamS .build(); return agendaTeamRepository.save(agendaTeam); } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(location) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, AgendaTeamStatus status) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(status) + .location(location) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java index 0577fb385..6a0268cff 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java @@ -24,4 +24,14 @@ public AgendaTeamProfile createAgendaTeamProfile(Agenda agenda, AgendaTeam agend .build(); return agendaTeamProfileRepository.save(agendaTeamProfile); } + + public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam team, AgendaProfile seoulUserAgendaProfile) { + AgendaTeamProfile agendaTeamProfile = AgendaTeamProfile.builder() + .agenda(team.getAgenda()) + .agendaTeam(team) + .profile(seoulUserAgendaProfile) + .isExist(true) + .build(); + return agendaTeamProfileRepository.save(agendaTeamProfile); + } } From 405512488e7635efe2aba28a733b8237be80a51d Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:10:21 +0900 Subject: [PATCH 047/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20?= =?UTF-8?q?=ED=99=95=EC=A0=95=EA=B3=BC=20=EC=A2=85=EB=A3=8C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#905=20(#907)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 26 +- ...irmReqDto.java => AgendaAwardsReqDto.java} | 6 +- .../request/AgendaCreateReqDto.java | 2 +- ...TeamAwardDto.java => AgendaTeamAward.java} | 4 +- .../user/agenda/service/AgendaService.java | 59 ++- .../java/gg/agenda/api/AgendaMockData.java | 109 ++++-- .../controller/AgendaAdminControllerTest.java | 161 +++++--- .../service/AgendaAdminServiceTest.java | 4 +- .../controller/AgendaControllerTest.java | 349 ++++++++++++------ .../agenda/service/AgendaServiceTest.java | 198 ++++++---- .../agendateam/AgendaTeamControllerTest.java | 14 +- .../src/main/java/gg/data/agenda/Agenda.java | 25 +- .../main/java/gg/data/agenda/AgendaTeam.java | 7 + .../gg/data/agenda/type/AgendaStatus.java | 5 +- .../gg/repo/agenda/AgendaTeamRepository.java | 4 + .../java/gg/utils/exception/ErrorCode.java | 4 +- .../java/gg/utils/AgendaTestDataUtils.java | 11 + .../utils/fixture/agenda/AgendaFixture.java | 28 +- .../fixture/agenda/AgendaTeamFixture.java | 23 ++ 19 files changed, 716 insertions(+), 323 deletions(-) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/{AgendaConfirmReqDto.java => AgendaAwardsReqDto.java} (75%) rename gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/{AgendaTeamAwardDto.java => AgendaTeamAward.java} (84%) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 4201c15d8..ce92e509f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.agenda.api.user.agenda.controller.response.AgendaKeyResDto; import gg.agenda.api.user.agenda.controller.response.AgendaResDto; @@ -98,21 +98,23 @@ public ResponseEntity> agendaListHistory( return ResponseEntity.ok(agendaSimpleResDtoList); } - @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Agenda 참가 신청 성공"), - @ApiResponse(responseCode = "400", description = "Agenda 참가 신청 요청이 잘못됨"), - @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음"), - @ApiResponse(responseCode = "409", description = "Agenda 참가 신청이 이미 완료됨") - }) - @PatchMapping("/confirm") - public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user, - @RequestBody(required = false) @Valid AgendaConfirmReqDto agendaConfirmReqDto) { + @PatchMapping("/finish") + public ResponseEntity agendaEndWithAwards(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user, + @RequestBody(required = false) @Valid AgendaAwardsReqDto agendaAwardsReqDto) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); - if (agenda.getIsRanking() && agendaConfirmReqDto == null) { + if (agenda.getIsRanking() && agendaAwardsReqDto == null) { throw new InvalidParameterException(AGENDA_INVALID_PARAM); } - agendaService.confirmAgenda(agendaConfirmReqDto, agenda); + agendaService.finishAgendaWithAwards(agendaAwardsReqDto, agenda); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + @PatchMapping("/confirm") + public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + agenda.mustModifiedByHost(user.getIntraId()); + agendaService.confirmAgenda(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java similarity index 75% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java index e96bf1e08..f32cb4efd 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaConfirmReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java @@ -13,15 +13,15 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaConfirmReqDto { +public class AgendaAwardsReqDto { @Valid @NotNull @NotEmpty - private List awards; + private List awards; @Builder - public AgendaConfirmReqDto(List awards) { + public AgendaAwardsReqDto(List awards) { this.awards = awards; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index 1915f482b..c6b4956e2 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -112,7 +112,7 @@ public interface MapStruct { @Mapping(target = "posterUri", source = "dto.agendaPoster") @Mapping(target = "hostIntraId", source = "user.intraId") @Mapping(target = "location", source = "dto.agendaLocation") - @Mapping(target = "status", constant = "ON_GOING") + @Mapping(target = "status", constant = "OPEN") @Mapping(target = "isOfficial", source = "dto.agendaIsOfficial") @Mapping(target = "isRanking", source = "dto.agendaIsRanking") Agenda toEntity(AgendaCreateReqDto dto, UserDto user); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java similarity index 84% rename from gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java index ae7a28873..a2dae184e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAwardDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java @@ -12,7 +12,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaTeamAwardDto { +public class AgendaTeamAward { @NotNull @NotEmpty @@ -27,7 +27,7 @@ public class AgendaTeamAwardDto { private int awardPriority; @Builder - public AgendaTeamAwardDto(String teamName, String awardName, int awardPriority) { + public AgendaTeamAward(String teamName, String awardName, int awardPriority) { this.teamName = teamName; this.awardName = awardName; this.awardPriority = awardPriority; diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 6287515ee..9e51ce286 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -2,10 +2,10 @@ import static gg.utils.exception.ErrorCode.*; -import java.time.LocalDateTime; import java.util.Comparator; +import java.util.HashMap; import java.util.List; -import java.util.Optional; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -13,16 +13,20 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; + +import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; -import gg.repo.agenda.AgendaAnnouncementRepository; import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @@ -35,6 +39,10 @@ public class AgendaService { private final AgendaTeamRepository agendaTeamRepository; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; + + private final TicketService ticketService; + @Transactional(readOnly = true) public Agenda findAgendaByAgendaKey(UUID agendaKey) { return agendaRepository.findByAgendaKey(agendaKey) @@ -43,7 +51,7 @@ public Agenda findAgendaByAgendaKey(UUID agendaKey) { @Transactional(readOnly = true) public List findCurrentAgendaList() { - return agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING).stream() + return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN).stream() .sorted(Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) .thenComparing(Agenda::getDeadline, Comparator.reverseOrder())) .collect(Collectors.toList()); @@ -57,19 +65,38 @@ public Agenda addAgenda(AgendaCreateReqDto agendaCreateReqDto, UserDto user) { @Transactional(readOnly = true) public List findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.CONFIRM).getContent(); + return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.FINISH).getContent(); + } + + @Transactional + public void finishAgendaWithAwards(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { + if (!agenda.getIsRanking()) { + agenda.finish(); + return; + } + Map teams = new HashMap<>(); + agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.CONFIRM) + .forEach(team -> teams.put(team.getName(), team)); + for (AgendaTeamAward agendaTeamAward : agendaAwardsReqDto.getAwards()) { + if (!teams.containsKey(agendaTeamAward.getTeamName())) { + throw new NotExistException(TEAM_NOT_FOUND); + } + teams.get(agendaTeamAward.getTeamName()) + .acceptAward(agendaTeamAward.getAwardName(), agendaTeamAward.getAwardPriority()); + } + agenda.finish(); } @Transactional - public void confirmAgenda(AgendaConfirmReqDto agendaConfirmReqDto, Agenda agenda) { - if (agenda.getIsRanking()) { - agendaConfirmReqDto.getAwards().forEach(award -> { - AgendaTeam agendaTeam = agendaTeamRepository - .findByAgendaAndNameAndStatus(agenda, award.getTeamName(), AgendaTeamStatus.CONFIRM) - .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); - agendaTeam.acceptAward(award.getAwardName(), award.getAwardPriority()); - }); + public void confirmAgenda(Agenda agenda) { + List openTeams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); + for (AgendaTeam openTeam : openTeams) { + openTeam.cancelTeam(); + List participants = agendaTeamProfileRepository.findAllByAgendaTeam(openTeam).stream() + .map(AgendaTeamProfile::getProfile) + .collect(Collectors.toList()); + ticketService.refundTickets(participants, agenda.getAgendaKey()); } - agenda.confirm(LocalDateTime.now()); + agenda.confirm(); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 1884f5a3a..7f382ec7b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -1,8 +1,6 @@ package gg.agenda.api; import static gg.data.agenda.type.AgendaStatus.*; -import static gg.data.agenda.type.AgendaStatus.CONFIRM; -import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.data.agenda.type.Coalition.*; import static gg.data.agenda.type.Location.*; import static java.util.UUID.*; @@ -62,7 +60,7 @@ public Agenda createOfficialAgenda() { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -84,7 +82,7 @@ public Agenda createNonOfficialAgenda() { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -193,7 +191,7 @@ public List createAgendaHistory(int size) { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(CONFIRM) + .status(FINISH) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -220,7 +218,7 @@ public Agenda createAgenda() { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -242,7 +240,7 @@ public Agenda createAgenda(String intraId) { .posterUri("posterUri") .hostIntraId(intraId) .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -264,7 +262,29 @@ public Agenda createAgenda(String intraId, LocalDateTime startTime, boolean rank .posterUri("posterUri") .hostIntraId(intraId) .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) + .isOfficial(true) + .isRanking(rank) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgenda(String intraId, LocalDateTime startTime, boolean rank, AgendaStatus status) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(startTime.minusDays(1)) + .startTime(startTime) + .endTime(startTime.plusDays(1)) + .minTeam(1) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId(intraId) + .location(SEOUL) + .status(status) .isOfficial(true) .isRanking(rank) .build(); @@ -308,7 +328,7 @@ public Agenda createAgenda(LocalDateTime deadline) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -330,7 +350,29 @@ public Agenda createAgendaWithAgendaCapacity(int min, int max) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + + public Agenda createAgendaWithAgendaCapacityAndStatus(int min, int max, AgendaStatus status) { + Agenda agenda = Agenda.builder() + .title("title") + .content("content") + .deadline(LocalDateTime.now().plusDays(1)) + .startTime(LocalDateTime.now().plusDays(2)) + .endTime(LocalDateTime.now().plusDays(3)) + .minTeam(min) + .maxTeam(max) + .currentTeam(0) + .minPeople(1) + .maxPeople(3) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(SEOUL) + .status(status) .isOfficial(true) .isRanking(true) .build(); @@ -352,7 +394,7 @@ public Agenda createAgendaWithAgendaTeamCapacity(int min, int max) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -374,7 +416,7 @@ public Agenda createAgenda(Location location) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(location) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -396,7 +438,7 @@ public Agenda createAgenda(int curruentTeam) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -418,7 +460,7 @@ public Agenda createNeedMorePeopleAgenda(int curruentTeam) { .posterUri("posterUri") .hostIntraId("hostIntraId") .location(SEOUL) - .status(ON_GOING) + .status(AgendaStatus.OPEN) .isOfficial(true) .isRanking(true) .build(); @@ -479,7 +521,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda) { .name("name") .content("content") .leaderIntraId("leaderIntraId") - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(SEOUL) .mateCount(3) .awardPriority(1) @@ -513,7 +555,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda, String teamName) { .name(teamName) .content("content") .leaderIntraId("leaderIntraId") - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(SEOUL) .mateCount(3) .awardPriority(1) @@ -545,7 +587,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user) { .name("name") .content("content") .leaderIntraId(user.getIntraId()) - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(SEOUL) .mateCount(3) .awardPriority(1) @@ -561,7 +603,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, int mateCount) { .name("name") .content("content") .leaderIntraId(user.getIntraId()) - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(SEOUL) .mateCount(mateCount) .awardPriority(1) @@ -577,7 +619,7 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location) .name("name") .content("content") .leaderIntraId(user.getIntraId()) - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(location) .mateCount(3) .awardPriority(1) @@ -609,7 +651,7 @@ public AgendaTeam createAgendaTeam(int currentTeam, Agenda agenda, User user, Lo .name("name") .content("content") .leaderIntraId(user.getIntraId()) - .status(OPEN) + .status(AgendaTeamStatus.OPEN) .location(location) .mateCount(currentTeam) .awardPriority(1) @@ -689,16 +731,16 @@ public Agenda createAgendaWithTeamAndAgendaCapacity(int teamCount, int min, int return agenda; } - public Agenda createAgendaWithTeamAndAgendaCapacityAndConfirm(int teamCount, int min, int max) { + public Agenda createAgendaWithTeamAndAgendaCapacityAndFinish(int teamCount, int min, int max) { Agenda agenda = createAgendaWithAgendaCapacity(min, max); for (int i = 0; i < teamCount; i++) { AgendaTeam agendaTeam = createAgendaTeam(agenda); agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); } agenda.updateSchedule(LocalDateTime.now().minusDays(2), - LocalDateTime.now().minusDays(1), - LocalDateTime.now().plusDays(1)); - agenda.confirm(LocalDateTime.now()); + LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); + agenda.confirm(); + agenda.finish(); em.persist(agenda); em.flush(); em.clear(); @@ -715,8 +757,23 @@ public Agenda createAgendaWithTeamAndAgendaTeamCapacity(int teamCount, int min, return agenda; } - public Agenda createAgendaWithTeamAndAgendaTeamCapacityAndConfirm(int teamCount, int min, int max) { - Agenda agenda = createAgendaWithAgendaTeamCapacity(min, max); + public Agenda createAgendaWithTeamAndAgendaTeamCapacityAndFinish(int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaCapacityAndStatus(min, max, CONFIRM); + for (int i = 0; i < teamCount; i++) { + User user = testDataUtils.createNewUser(); + AgendaTeam agendaTeam = createAgendaTeam(agenda, user, 3); + agendaTeam.confirm(); + agenda.addTeam(agendaTeam.getLocation(), LocalDateTime.now()); + em.persist(agendaTeam); + em.flush(); + em.clear(); + } + return agenda; + } + + public Agenda createAgendaWithStatusAndTeamWithAgendaTeamCapacity(AgendaStatus status, + int teamCount, int min, int max) { + Agenda agenda = createAgendaWithAgendaCapacityAndStatus(min, max, status); for (int i = 0; i < teamCount; i++) { User user = testDataUtils.createNewUser(); AgendaTeam agendaTeam = createAgendaTeam(agenda, user, 3); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index d65f85aee..31c7b95e9 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -86,11 +86,11 @@ void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { // given int size = 10; List agendas = new ArrayList<>(); - agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.ON_GOING)); - agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.CONFIRM)); + agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.OPEN)); + agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.FINISH)); agendas.addAll(agendaMockData.createOfficialAgendaList(5, AgendaStatus.CANCEL)); - agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.ON_GOING)); - agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.CONFIRM)); + agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.OPEN)); + agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.FINISH)); agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.CANCEL)); PageRequestDto pageRequestDto = new PageRequestDto(page, size); String request = objectMapper.writeValueAsString(pageRequestDto); @@ -143,14 +143,17 @@ void updateAgendaAdminSuccessWithInformation() throws Exception { Agenda agenda = agendaMockData.createAgendaWithTeam(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaTitle("updated title").agendaContents("updated content") - .agendaPoster("updated poster").agendaStatus(CONFIRM).isOfficial(!agenda.getIsOfficial()) + .agendaPoster("updated poster").agendaStatus(FINISH).isOfficial(!agenda.getIsOfficial()) .isRanking(!agenda.getIsRanking()).build(); String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -175,9 +178,12 @@ void updateAgendaAdminSuccessWithSchedule() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -196,9 +202,12 @@ void updateAgendaAdminSuccessWithLocationSeoulToMix() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -215,9 +224,12 @@ void updateAgendaAdminSuccessWithLocationSeoulToGyeongsan() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -253,9 +265,12 @@ void updateAgendaAdminSuccessWithLocationGyeongsanToMix() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -273,9 +288,12 @@ void updateAgendaAdminSuccessWithAgendaCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -295,9 +313,12 @@ void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -314,9 +335,12 @@ void updateAgendaAdminFailedWithNoAgenda() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", UUID.randomUUID().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNotFound()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", UUID.randomUUID().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); } @Test @@ -328,9 +352,12 @@ void updateAgendaAdminFailedWithLocationSeoulToGyeongSan() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @@ -342,9 +369,12 @@ void updateAgendaAdminFailedWithLocationGyeongSanToSeoul() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @ParameterizedTest @@ -357,9 +387,12 @@ void updateAgendaAdminFailedWithLocationMixToSeoul() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @@ -372,9 +405,12 @@ void updateAgendaAdminFailedWithAgendaInvalidCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @@ -387,24 +423,30 @@ void updateAgendaAdminFailedWithMaxTeam() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test - @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 확정된 대회에 minTeam 이하의 팀이 참여한 경우") + @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 종료한 대회에 minTeam 이하의 팀이 참여한 경우") void updateAgendaAdminFailedWithMinTeam() throws Exception { // given - Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacityAndConfirm(5, 5, 10); + Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacityAndFinish(5, 5, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam() + 2) .agendaMaxTeam((agenda.getMaxTeam())).build(); String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @@ -417,9 +459,12 @@ void updateAgendaAdminFailedWithAgendaTeamInvalidCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @@ -432,16 +477,20 @@ void updateAgendaAdminFailedWithMaxPeople() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(patch("/admin/agenda/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 확정된 팀에 minPeople 이하의 인원이 참여한 경우") void updateAgendaAdminFailedWithMinPeople() throws Exception { // given - Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacityAndConfirm(10, 3, 10); + Agenda agenda = agendaMockData.createAgendaWithStatusAndTeamWithAgendaTeamCapacity(OPEN, + 10, 3, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(5).agendaMaxPeople(agenda.getMaxPeople()).build(); String request = objectMapper.writeValueAsString(agendaDto); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java index e6b4349c6..23bc47250 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -96,7 +96,7 @@ void updateAgendaSuccessWithInformation() { teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); } Agenda agenda = Agenda.builder().title("title").content("content").posterUri("posterUri").isOfficial(false) - .isRanking(true).status(AgendaStatus.CONFIRM).build(); + .isRanking(true).status(AgendaStatus.FINISH).build(); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaTitle("Updated title").agendaContents("Updated content") .agendaPoster("Updated posterUri").isOfficial(true).isRanking(true) @@ -262,7 +262,7 @@ void updateAgendaFailAdminWithCannotChangeMinTeam() { teams.add(AgendaTeam.builder().location(Location.SEOUL).mateCount(3).build()); } // 10개 팀 Agenda agenda = - Agenda.builder().currentTeam(teams.size()).minTeam(5).maxTeam(10).status(AgendaStatus.CONFIRM).build(); + Agenda.builder().currentTeam(teams.size()).minTeam(5).maxTeam(10).status(AgendaStatus.FINISH).build(); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(10).agendaMaxTeam(20).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 934ec314d..d730d369f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -28,9 +29,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; -import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; -import gg.agenda.api.user.agenda.controller.request.AgendaTeamAwardDto; +import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; import gg.agenda.api.user.agenda.controller.response.AgendaKeyResDto; import gg.agenda.api.user.agenda.controller.response.AgendaResDto; import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; @@ -42,9 +43,13 @@ import gg.data.agenda.type.Location; import gg.data.user.User; import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamRepository; +import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaTeamFixture; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -65,12 +70,24 @@ public class AgendaControllerTest { @Autowired private AgendaMockData agendaMockData; + @Autowired + private AgendaFixture agendaFixture; + + @Autowired + private AgendaTeamFixture agendaTeamFixture; + + @Autowired + private AgendaTestDataUtils agendaTestDataUtils; + @Autowired EntityManager em; @Autowired AgendaRepository agendaRepository; + @Autowired + AgendaTeamRepository agendaTeamRepository; + private User user; private String accessToken; @@ -184,9 +201,9 @@ class GetAgendaListCurrent { @DisplayName("Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") void getAgendaListSuccess() throws Exception { // given - List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.ON_GOING); + List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.OPEN); List nonOfficialAgendaList = agendaMockData - .createNonOfficialAgendaList(6, AgendaStatus.ON_GOING); + .createNonOfficialAgendaList(6, AgendaStatus.OPEN); // when String response = mockMvc.perform(get("/agenda/list") @@ -210,7 +227,7 @@ void getAgendaListSuccess() throws Exception { @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") void getAgendaListSuccessWithNoAgenda() throws Exception { // given - agendaMockData.createOfficialAgendaList(3, AgendaStatus.CONFIRM); + agendaMockData.createOfficialAgendaList(3, AgendaStatus.FINISH); agendaMockData.createNonOfficialAgendaList(6, AgendaStatus.CANCEL); // when @@ -566,28 +583,29 @@ void getAgendaListHistoryWithoutSize() throws Exception { } @Nested - @DisplayName("Agenda 시상 및 확정") - class ConfirmAgenda { + @DisplayName("Agenda 종료 및 시상하기") + class FinishAgenda { @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상 대회인 경우") - void confirmAgendaSuccess() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 성공 - 시상 대회인 경우") + void finishAgendaSuccess() throws Exception { // given int teamSize = 10; int awardSize = 3; - Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -597,7 +615,7 @@ void confirmAgendaSuccess() throws Exception { .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); // then - assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(result.getStatus()).isEqualTo(AgendaStatus.FINISH); awards.forEach(award -> { AgendaTeam agendaTeam = em.createQuery( "select at from AgendaTeam at where at.agenda = :agenda and at.name = :teamName", @@ -611,16 +629,40 @@ void confirmAgendaSuccess() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회인 경우") - void confirmAgendaSuccessWithNoRanking() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 시상 대회에 시상 내역이 없는 경우") + void finishAgendaFailedWithNoAwards() throws Exception { + // given + int teamSize = 10; + int awardSize = 3; + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); + List agendaTeams = IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); + + // expected + mockMvc.perform(patch("/agenda/finish") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 종료 및 시상하기 성공 - 시상하지 않는 대회인 경우") + void finishAgendaSuccessWithNoRanking() throws Exception { // given int teamSize = 10; - Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false); + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); IntStream.range(0, teamSize) .forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); @@ -628,28 +670,29 @@ void confirmAgendaSuccessWithNoRanking() throws Exception { .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); // then - assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(result.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") - void confirmAgendaSuccessWithNoRankAndAwards() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") + void finishAgendaSuccessWithNoRankAndAwards() throws Exception { // given int teamSize = 10; int awardSize = 3; - Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false); + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -659,7 +702,7 @@ void confirmAgendaSuccessWithNoRankAndAwards() throws Exception { .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); // then - assertThat(result.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(result.getStatus()).isEqualTo(AgendaStatus.FINISH); awards.forEach(award -> { AgendaTeam agendaTeam = em.createQuery( "select at from AgendaTeam at where at.agenda = :agenda and at.name = :teamName", @@ -673,26 +716,27 @@ void confirmAgendaSuccessWithNoRankAndAwards() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 존재하지 않는 팀에 대한 시상인 경우") - void confirmAgendaFailedWithInvalidTeam() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 존재하지 않는 팀에 대한 시상인 경우") + void finishAgendaFailedWithInvalidTeam() throws Exception { // given int teamSize = 10; int awardSize = 3; - Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); + Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), + LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - awards.add(AgendaTeamAwardDto.builder() + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + awards.add(AgendaTeamAward.builder() .teamName("invalid_team").awardName("prize").awardPriority(1).build()); // invalid team - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -701,8 +745,8 @@ void confirmAgendaFailedWithInvalidTeam() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - Agenda가 없는 경우") - void confirmAgendaFailedWithNoAgenda() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - Agenda가 없는 경우") + void finishAgendaFailedWithNoAgenda() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -710,17 +754,17 @@ void confirmAgendaFailedWithNoAgenda() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); UUID invalidAgendaKey = UUID.randomUUID(); // invalid agenda key // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", invalidAgendaKey.toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -729,18 +773,18 @@ void confirmAgendaFailedWithNoAgenda() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 없는 경우") - void confirmAgendaFailedWithoutAwards() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 시상 내역이 없는 경우") + void finishAgendaFailedWithoutAwards() throws Exception { // given int teamSize = 10; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); IntStream.range(0, teamSize).forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().build(); // null - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().build(); // null + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -749,20 +793,20 @@ void confirmAgendaFailedWithoutAwards() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 빈 리스트인 경우") - void confirmAgendaFailedWithEmptyAwards() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 시상 내역이 빈 리스트인 경우") + void finishAgendaFailedWithEmptyAwards() throws Exception { // given int teamSize = 10; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); IntStream.range(0, teamSize).forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder() + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder() .awards(List.of()) // empty .build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -771,8 +815,8 @@ void confirmAgendaFailedWithEmptyAwards() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 개최자가 아닌 경우") - void confirmAgendaFailedNotHost() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 개최자가 아닌 경우") + void finishAgendaFailedNotHost() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -781,15 +825,15 @@ void confirmAgendaFailedNotHost() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // when - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -798,25 +842,25 @@ void confirmAgendaFailedNotHost() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 이미 확정된 경우") - void confirmAgendaFailedAlreadyConfirm() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 이미 확정된 경우") + void finishAgendaFailedAlreadyConfirm() throws Exception { // given int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), - LocalDateTime.now().minusDays(10), AgendaStatus.CONFIRM); + LocalDateTime.now().minusDays(10), AgendaStatus.FINISH); List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -825,8 +869,8 @@ void confirmAgendaFailedAlreadyConfirm() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 이미 취소된 경우") - void confirmAgendaFailedAlreadyCancel() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 이미 취소된 경우") + void finishAgendaFailedAlreadyCancel() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -835,15 +879,15 @@ void confirmAgendaFailedAlreadyCancel() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -852,25 +896,25 @@ void confirmAgendaFailedAlreadyCancel() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - 아직 시작하지 않은 경우") - void confirmAgendaFailedBeforeStartTime() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - 아직 시작하지 않은 경우") + void finishAgendaFailedBeforeStartTime() throws Exception { // given int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), - LocalDateTime.now().plusDays(1), AgendaStatus.ON_GOING); + LocalDateTime.now().plusDays(1), AgendaStatus.OPEN); List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -879,8 +923,8 @@ void confirmAgendaFailedBeforeStartTime() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - empty awardName") - void confirmAgendaFailedWithEmptyAwardName() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - empty awardName") + void finishAgendaFailedWithEmptyAwardName() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -888,17 +932,17 @@ void confirmAgendaFailedWithEmptyAwardName() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - awards.add(AgendaTeamAwardDto.builder().teamName(agendaTeams.get(awardSize).getName()) + awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardName("").awardPriority(awardSize).build()); // empty award name - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -907,8 +951,8 @@ void confirmAgendaFailedWithEmptyAwardName() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - null awardName") - void confirmAgendaFailedWithNullAwardName() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - null awardName") + void finishAgendaFailedWithNullAwardName() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -916,17 +960,17 @@ void confirmAgendaFailedWithNullAwardName() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - awards.add(AgendaTeamAwardDto.builder().teamName(agendaTeams.get(awardSize).getName()) + awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardPriority(awardSize).build()); // null award name - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -935,8 +979,8 @@ void confirmAgendaFailedWithNullAwardName() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - empty teamName") - void confirmAgendaFailedWithEmptyTeamName() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - empty teamName") + void finishAgendaFailedWithEmptyTeamName() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -944,17 +988,17 @@ void confirmAgendaFailedWithEmptyTeamName() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - awards.add(AgendaTeamAwardDto.builder().awardName("prize" + awardSize) + awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .teamName("").awardPriority(awardSize).build()); // empty award name - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -963,8 +1007,8 @@ void confirmAgendaFailedWithEmptyTeamName() throws Exception { } @Test - @DisplayName("Agenda 시상 및 확정 실패 - null teamName") - void confirmAgendaFailedWithNullTeamName() throws Exception { + @DisplayName("Agenda 종료 및 시상하기 실패 - null teamName") + void finishAgendaFailedWithNullTeamName() throws Exception { // given int teamSize = 10; int awardSize = 3; @@ -972,17 +1016,17 @@ void confirmAgendaFailedWithNullTeamName() throws Exception { List agendaTeams = IntStream.range(0, teamSize) .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAwardDto.builder().teamName(agendaTeams.get(i).getName()) + List awards = IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - awards.add(AgendaTeamAwardDto.builder().awardName("prize" + awardSize) + awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .awardPriority(awardSize).build()); // null award name - AgendaConfirmReqDto agendaConfirmReqDto = AgendaConfirmReqDto.builder().awards(awards).build(); - String response = objectMapper.writeValueAsString(agendaConfirmReqDto); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); + String response = objectMapper.writeValueAsString(agendaAwardsReqDto); // expected - mockMvc.perform(patch("/agenda/confirm") + mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -990,4 +1034,73 @@ void confirmAgendaFailedWithNullTeamName() throws Exception { .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("Agenda 확정하기") + class ConfirmAgenda { + + @Test + @DisplayName("Agenda 확정하기 성공") + void confirmAgendaSuccess() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams(user.getIntraId(), 10, AgendaStatus.OPEN); + List openTeams = agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.OPEN, 3); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); + Optional confirmedAgenda = agendaRepository.findById(agenda.getId()); + List openedTeams = agendaTeamRepository + .findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); + List canceledTeams = agendaTeamRepository + .findAllByAgendaAndStatus(agenda, AgendaTeamStatus.CANCEL); + + // then + assert (confirmedAgenda.isPresent()); + assertThat(confirmedAgenda.get().getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(openedTeams.size()).isEqualTo(0); + assertThat(canceledTeams.size()).isEqualTo(openTeams.size()); + } + + @Test + @DisplayName("Agenda 확정하기 실패 - 존재하지 않는 Agenda인 경우") + void confirmAgendaFailedWithNoAgenda() throws Exception { + // given + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", UUID.randomUUID().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Agenda 확정하기 실패 - 개최자가 아닌 경우") + void confirmAgendaFailedWithNotHost() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams("not host", 10, AgendaStatus.OPEN); + + // when + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isForbidden()); + } + + @ParameterizedTest + @EnumSource(value = AgendaStatus.class, names = {"CANCEL", "CONFIRM", "FINISH"}) + @DisplayName("Agenda 확정하기 실패 - 대회의 상태가 OPEN이 아닌 경우") + void confirmAgendaFailedWithNotOpenAgenda(AgendaStatus status) throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams(user.getIntraId(), 10, status); + agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.OPEN, 3); + + // expected + mockMvc.perform(patch("/agenda/confirm") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 50717c176..542b1962b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.data.domain.Page; @@ -23,17 +25,22 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import gg.agenda.api.user.agenda.controller.request.AgendaConfirmReqDto; +import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; -import gg.agenda.api.user.agenda.controller.request.AgendaTeamAwardDto; +import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; -import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.data.agenda.type.AgendaTeamStatus; import gg.repo.agenda.AgendaRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.utils.annotation.UnitTest; +import gg.utils.exception.custom.InvalidParameterException; import gg.utils.exception.custom.NotExistException; import lombok.extern.slf4j.Slf4j; @@ -47,6 +54,12 @@ class AgendaServiceTest { @Mock AgendaTeamRepository agendaTeamRepository; + @Mock + AgendaTeamProfileRepository agendaTeamProfileRepository; + + @Mock + TicketService ticketService; + @InjectMocks AgendaService agendaService; @@ -97,7 +110,7 @@ void getAgendaListSuccess() { .deadline(LocalDateTime.now().plusDays(i + 3)).build())); IntStream.range(0, nonOfficialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(false) .deadline(LocalDateTime.now().plusDays(i + 3)).build())); - when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); + when(agendaRepository.findAllByStatusIs(AgendaStatus.OPEN)).thenReturn(agendas); // when List result = agendaService.findCurrentAgendaList(); @@ -118,7 +131,7 @@ void getAgendaListSuccess() { void getAgendaListWithNoContent() { // given List agendas = new ArrayList<>(); - when(agendaRepository.findAllByStatusIs(AgendaStatus.ON_GOING)).thenReturn(agendas); + when(agendaRepository.findAllByStatusIs(AgendaStatus.OPEN)).thenReturn(agendas); // when agendaService.findCurrentAgendaList(); @@ -173,7 +186,7 @@ void getAgendaListHistorySuccess() { .build() )); Page agendaPage = new PageImpl<>(agendas.subList(0, 10), pageable, size); - when(agendaRepository.findAllByStatusIs(any(Pageable.class), eq(AgendaStatus.CONFIRM))) + when(agendaRepository.findAllByStatusIs(any(Pageable.class), eq(AgendaStatus.FINISH))) .thenReturn(agendaPage); // when @@ -181,7 +194,7 @@ void getAgendaListHistorySuccess() { // then verify(agendaRepository, times(1)) - .findAllByStatusIs(pageable, AgendaStatus.CONFIRM); + .findAllByStatusIs(pageable, AgendaStatus.FINISH); assertThat(result.size()).isEqualTo(size); for (int i = 1; i < result.size(); i++) { assertThat(result.get(i).getStartTime()) @@ -192,7 +205,7 @@ void getAgendaListHistorySuccess() { @Nested @DisplayName("Agenda 시상 및 확정") - class ConfirmAgenda { + class FinishAgenda { int seq; @@ -203,161 +216,210 @@ void setUp() { @Test @DisplayName("Agenda 시상 및 확정 성공") - void confirmAgendaSuccess() { + void finishAgendaSuccess() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(true).build(); + .status(AgendaStatus.CONFIRM).isRanking(true).build(); List agendaTeams = new ArrayList<>(); IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); - AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + AgendaTeamAward awardDto = AgendaTeamAward.builder() .teamName("team1").awardName("award").awardPriority(1).build(); - UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); - UUID agendaKey = agenda.getAgendaKey(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() .awards(List.of(awardDto)).build(); - when(agendaTeamRepository.findByAgendaAndNameAndStatus(any(), any(), any())) - .thenReturn(Optional.of(agendaTeams.get(seq++))); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any())) + .thenReturn(agendaTeams); // when - agendaService.confirmAgenda(confirmDto, agenda); + agendaService.finishAgendaWithAwards(confirmDto, agenda); // then - verify(agendaTeamRepository, times(1)).findByAgendaAndNameAndStatus(any(), any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회인 경우") - void confirmAgendaSuccessWithNoRank() { + void finishAgendaSuccessWithNoRank() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(false).build(); - UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); - UUID agendaKey = agenda.getAgendaKey(); + .status(AgendaStatus.CONFIRM).isRanking(false).build(); // when - agendaService.confirmAgenda(null, agenda); + agendaService.finishAgendaWithAwards(null, agenda); // then - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") - void confirmAgendaSuccessWithNoRankAndAwards() { + void finishAgendaSuccessWithNoRankAndAwards() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(false).build(); + .status(AgendaStatus.CONFIRM).isRanking(false).build(); List agendaTeams = new ArrayList<>(); IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); - AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + AgendaTeamAward awardDto = AgendaTeamAward.builder() .teamName("team1").awardName("award").awardPriority(1).build(); - UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); - UUID agendaKey = agenda.getAgendaKey(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() .awards(List.of(awardDto)).build(); // when - agendaService.confirmAgenda(confirmDto, agenda); + agendaService.finishAgendaWithAwards(confirmDto, agenda); // then - verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 빈 리스트로 들어온 경우") - void confirmAgendaSuccessWithNoRankAndEmtpyAwards() { + void finishAgendaSuccessWithNoRankAndEmptyAwards() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(false).build(); - List agendaTeams = new ArrayList<>(); - IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); - UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); - UUID agendaKey = agenda.getAgendaKey(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + .status(AgendaStatus.CONFIRM).isRanking(false).build(); + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() .awards(List.of()).build(); // when - agendaService.confirmAgenda(confirmDto, agenda); + agendaService.finishAgendaWithAwards(confirmDto, agenda); // then - verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 null로 들어온 경우") - void confirmAgendaSuccessWithNoRankAndNullAwards() { + void finishAgendaSuccessWithNoRankAndNullAwards() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(false).build(); - List agendaTeams = new ArrayList<>(); - IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); - UserDto user = UserDto.builder().intraId(agenda.getHostIntraId()).build(); - UUID agendaKey = agenda.getAgendaKey(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder().build(); + .status(AgendaStatus.CONFIRM).isRanking(false).build(); + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder().build(); // when - agendaService.confirmAgenda(confirmDto, agenda); + agendaService.finishAgendaWithAwards(confirmDto, agenda); // then - verify(agendaTeamRepository, never()).findByAgendaAndNameAndStatus(any(), any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); } @Test @DisplayName("Agenda 시상 및 확정 실패 - 시상 내역이 null인 경우") - void confirmAgendaFailedWithoutAwards() { + void finishAgendaFailedWithoutAwards() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(true).build(); + .status(AgendaStatus.CONFIRM).isRanking(true).build(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder().build(); + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder().build(); // expected assertThrows(NullPointerException.class, - () -> agendaService.confirmAgenda(confirmDto, agenda)); + () -> agendaService.finishAgendaWithAwards(confirmDto, agenda)); } @Test @DisplayName("Agenda 시상 및 확정 실패 - 매개변수가 null인 경우") - void confirmAgendaFailedWithNullDto() { + void finishAgendaFailedWithNullDto() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(true).build(); + .status(AgendaStatus.CONFIRM).isRanking(true).build(); // expected assertThrows(NullPointerException.class, - () -> agendaService.confirmAgenda(null, agenda)); + () -> agendaService.finishAgendaWithAwards(null, agenda)); } @Test @DisplayName("Agenda 시상 및 확정 실패 - 존재하지 않는 팀에 대한 시상인 경우") - void confirmAgendaFailedWithInvalidTeam() { + void finishAgendaFailedWithInvalidTeam() { // given Agenda agenda = Agenda.builder() .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.ON_GOING).isRanking(true).build(); - AgendaTeamAwardDto awardDto = AgendaTeamAwardDto.builder() + .status(AgendaStatus.CONFIRM).isRanking(true).build(); + AgendaTeamAward awardDto = AgendaTeamAward.builder() .teamName("invalidTeam").awardName("award").awardPriority(1).build(); - AgendaConfirmReqDto confirmDto = AgendaConfirmReqDto.builder() + AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() .awards(List.of(awardDto)).build(); - when(agendaTeamRepository.findByAgendaAndNameAndStatus(any(), any(), any())) - .thenReturn(Optional.empty()); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any())) + .thenReturn(List.of()); // expected assertThrows(NotExistException.class, - () -> agendaService.confirmAgenda(confirmDto, agenda)); + () -> agendaService.finishAgendaWithAwards(confirmDto, agenda)); + } + } + + @Nested + @DisplayName("Agenda 확정하기") + class ConfirmAgenda { + + @Test + @DisplayName("Agenda 확정하기 성공") + void confirmAgendaSuccess() { + // given + Agenda agenda = Agenda.builder().hostIntraId("intraId").status(AgendaStatus.OPEN).build(); + AgendaTeam agendaTeam = AgendaTeam.builder().status(AgendaTeamStatus.OPEN).build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .profile(AgendaProfile.builder().build()).build(); + when(agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN)) + .thenReturn(List.of(agendaTeam)); + when(agendaTeamProfileRepository.findAllByAgendaTeam(agendaTeam)).thenReturn(List.of(participant)); + doNothing().when(ticketService).refundTickets(any(), any()); + + + + // when + agendaService.confirmAgenda(agenda); + + // then + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); + verify(agendaTeamProfileRepository, times(1)).findAllByAgendaTeam(agendaTeam); + verify(ticketService, times(1)).refundTickets(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + assertThat(agendaTeam.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); + } + + @Test + @DisplayName("Agenda 확정하기 실패 - AgendaTeam이 없는 경우") + void confirmAgendaFailedWithNoAgenda() { + // given + Agenda agenda = Agenda.builder().hostIntraId("intraId").status(AgendaStatus.OPEN).build(); + when(agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN)) + .thenReturn(List.of()); + + // when + agendaService.confirmAgenda(agenda); + + // then + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); + } + + @ParameterizedTest + @EnumSource(value = AgendaStatus.class, names = {"CONFIRM", "FINISH", "CANCEL"}) + @DisplayName("Agenda 확정하기 실패 - 대회의 상태가 OPEN이 아닌 경우") + void confirmAgendaFailedWithAlreadyConfirm(AgendaStatus status) { + // given + Agenda agenda = Agenda.builder().hostIntraId("intraId").status(status).build(); + AgendaTeam agendaTeam = AgendaTeam.builder().status(AgendaTeamStatus.OPEN).build(); + when(agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN)) + .thenReturn(List.of(agendaTeam)); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaService.confirmAgenda(agenda)); + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index e8f9c1572..27f59a440 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -308,7 +308,7 @@ public void notValidAgendaLocation() throws Exception { @DisplayName("400 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(CONFIRM); + Agenda agenda = agendaMockData.createAgenda(FINISH); agendaMockData.createTicket(seoulUserAgendaProfile); TeamCreateReqDto req = new TeamCreateReqDto("teamName", true, "SEOUL", "teamContent"); @@ -524,7 +524,7 @@ public void teamDetailsGetFailByNoTeam() throws Exception { @DisplayName("403 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByConfirmTeam() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(CONFIRM); + Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); @@ -542,7 +542,7 @@ public void teamDetailsGetFailByConfirmTeam() throws Exception { @DisplayName("404 조회 불가능한 team으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByCancelTeam() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(CONFIRM); + Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -786,7 +786,7 @@ public void alreadyConfirmTeamFail() throws Exception { @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(CONFIRM); + Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -1024,7 +1024,7 @@ public void notValidAgendaDeadline() throws Exception { @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given - Agenda agenda = agendaMockData.createAgenda(CONFIRM); + Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -1375,7 +1375,7 @@ public void notValidAgendaStatus() throws Exception { @DisplayName("400 참가 불가능한 Agenda status 으로 인한 실패") public void notValidAgendaDeadline() throws Exception { //given - Agenda agenda = agendaFixture.createAgenda(CONFIRM); + Agenda agenda = agendaFixture.createAgenda(FINISH); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); ticketFixture.createTicket(seoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); @@ -1622,7 +1622,7 @@ public void notValidAgendaStatus() throws Exception { @DisplayName("400 수정 불가능한 Agenda status 으로 인한 실패") public void notValidAgendaDeadline() throws Exception { //given - Agenda agenda = agendaFixture.createAgenda(CONFIRM); + Agenda agenda = agendaFixture.createAgenda(FINISH); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUser, MIX); agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); TeamUpdateReqDto req = new TeamUpdateReqDto(team.getTeamKey(), "newName", "newDesc", true, "MIX"); diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index d41d493d5..fb4e2af7b 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -150,17 +150,30 @@ public void cancelTeam(LocalDateTime now) { mustBeforeDeadline(now); } - public void confirm(LocalDateTime confirmTime) { + public void confirm() { + if (this.status == AgendaStatus.FINISH) { + throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); + } + if (this.status == AgendaStatus.CANCEL) { + throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); + } if (this.status == AgendaStatus.CONFIRM) { throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); } + this.status = AgendaStatus.CONFIRM; + } + + public void finish() { + if (this.status == AgendaStatus.OPEN) { + throw new InvalidParameterException(AGENDA_DOES_NOT_CONFIRM); + } if (this.status == AgendaStatus.CANCEL) { throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); } - if (this.startTime.isAfter(confirmTime)) { - throw new InvalidParameterException(AGENDA_INVALID_PARAM); + if (this.status == AgendaStatus.FINISH) { + throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); } - this.status = AgendaStatus.CONFIRM; + this.status = AgendaStatus.FINISH; } public void updateInformation(String title, String content, String posterUri) { @@ -223,7 +236,7 @@ public void updateAgendaCapacity(int minTeam, int maxTeam, List team if (minTeam > maxTeam || teams.size() > maxTeam) { throw new InvalidParameterException(AGENDA_CAPACITY_CONFLICT); } - if (this.status == AgendaStatus.CONFIRM && teams.size() < minTeam) { + if (this.status == AgendaStatus.FINISH && teams.size() < minTeam) { throw new InvalidParameterException(AGENDA_CAPACITY_CONFLICT); } this.minTeam = minTeam; @@ -254,7 +267,7 @@ private void mustBeWithinLocation(Location location) { } private void mustStatusOnGoing() { - if (this.status != AgendaStatus.ON_GOING) { + if (this.status != AgendaStatus.OPEN) { throw new InvalidParameterException(AGENDA_NOT_OPEN); } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 93484b0c5..cc9adb423 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -164,4 +164,11 @@ public void updateLocation(Location location, List profiles) } this.location = location; } + + public void cancelTeam() { + if (this.status == CONFIRM) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); + } + this.status = CANCEL; + } } diff --git a/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java b/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java index f5b8de94e..c29e207e9 100644 --- a/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java +++ b/gg-data/src/main/java/gg/data/agenda/type/AgendaStatus.java @@ -7,8 +7,9 @@ @AllArgsConstructor public enum AgendaStatus { CANCEL("CANCEL"), - ON_GOING("ON_GOING"), - CONFIRM("CONFIRM"); + OPEN("OPEN"), + CONFIRM("CONFIRM"), + FINISH("FINISH"); private final String status; } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 5b14c161d..6d1f37018 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -1,5 +1,6 @@ package gg.repo.agenda; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -29,6 +30,9 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :name AND a.status = :status") Optional findByAgendaAndNameAndStatus(Agenda agenda, String name, AgendaTeamStatus status); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status") + List findAllByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index c41943c6e..2392c4010 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -216,8 +216,10 @@ public enum ErrorCode { TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), - AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 종료된 일정입니다."), + AGENDA_ALREADY_FINISHED(409, "AG", "이미 종료된 일정입니다."), + AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 확정된 일정입니다."), AGENDA_ALREADY_CANCELED(409, "AG", "이미 취소된 일정입니다."), + AGENDA_DOES_NOT_CONFIRM(409, "AG", "확정되지 않은 일정입니다."), AGENDA_AWARD_EMPTY(400, "AG", "시상 정보가 없습니다."), AGENDA_AWARD_PRIORITY_DUPLICATE(400, "AG", "시상 우선순위가 중복됩니다."); diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java index 93348accf..a82c54c42 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -1,8 +1,11 @@ package gg.utils; import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.AgendaTeamStatus; import gg.utils.fixture.agenda.AgendaAnnouncementFixture; import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaTeamFixture; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -14,10 +17,18 @@ public class AgendaTestDataUtils { private final AgendaAnnouncementFixture agendaAnnouncementFixture; + private final AgendaTeamFixture agendaTeamFixture; + public Agenda createAgendaAndAnnouncements(int size) { Agenda agenda = agendaFixture.createAgenda(); agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, size / 2, true); agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, size - size / 2, false); return agenda; } + + public Agenda createAgendaAndAgendaTeams(String intraId, int i, AgendaStatus status) { + Agenda agenda = agendaFixture.createAgenda(intraId, status); + agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, i); + return agenda; + } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java index 7c206cb75..c0c634bfe 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -31,7 +31,7 @@ public Agenda createAgenda() { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(ON_GOING) + .status(OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) @@ -41,6 +41,28 @@ public Agenda createAgenda() { return agendaRepository.save(agenda); } + public Agenda createAgenda(String intraId, AgendaStatus status) { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(2) + .maxTeam(5) + .currentTeam(0) + .minPeople(1) + .maxPeople(5) + .status(status) + .posterUri("posterUri") + .hostIntraId(intraId) + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + public Agenda createAgenda(Location location) { Agenda agenda = Agenda.builder() .title("title " + UUID.randomUUID()) @@ -53,7 +75,7 @@ public Agenda createAgenda(Location location) { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(ON_GOING) + .status(OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(location) @@ -75,7 +97,7 @@ public Agenda createAgenda(LocalDateTime localDateTime) { .currentTeam(0) .minPeople(1) .maxPeople(5) - .status(ON_GOING) + .status(OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") .location(Location.MIX) diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index cb64b988e..982dc95e4 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -4,6 +4,9 @@ import static gg.data.agenda.type.Location.*; import static java.util.UUID.*; +import gg.data.agenda.type.AgendaStatus; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import org.springframework.stereotype.Component; @@ -116,4 +119,24 @@ public AgendaTeam createAgendaTeam(Agenda agenda, User user, Location location, .build(); return agendaTeamRepository.save(agendaTeam); } + + public List createAgendaTeamList(Agenda agenda, AgendaTeamStatus status, int size) { + List teams = new ArrayList<>(); + for (int i = 0; i < size; i++) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(randomUUID()) + .name("name") + .content("content") + .leaderIntraId("intraId" + i) + .status(status) + .location(SEOUL) + .mateCount(3) + .awardPriority(1) + .isPrivate(false) + .build(); + teams.add(agendaTeam); + } + return agendaTeamRepository.saveAll(teams); + } } From f9e7a010339ae5a8a06b905e63de8332258d299f Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:38:24 +0900 Subject: [PATCH 048/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Fix=20TestFixtur?= =?UTF-8?q?e=20checkstyle=20#914=20(#915)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hotfix --- .../java/gg/utils/AgendaTestDataUtils.java | 7 ++++--- .../fixture/agenda/AgendaAnnouncementFixture.java | 11 +++++++---- .../gg/utils/fixture/agenda/AgendaTeamFixture.java | 1 - 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java index a82c54c42..7532cece9 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -1,5 +1,7 @@ package gg.utils; +import org.springframework.stereotype.Component; + import gg.data.agenda.Agenda; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; @@ -7,7 +9,6 @@ import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor @@ -26,9 +27,9 @@ public Agenda createAgendaAndAnnouncements(int size) { return agenda; } - public Agenda createAgendaAndAgendaTeams(String intraId, int i, AgendaStatus status) { + public Agenda createAgendaAndAgendaTeams(String intraId, int size, AgendaStatus status) { Agenda agenda = agendaFixture.createAgenda(intraId, status); - agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, i); + agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, size); return agenda; } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java index 07ee694b3..f6ce93d8f 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaAnnouncementFixture.java @@ -1,15 +1,18 @@ package gg.utils.fixture.agenda; -import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; -import gg.repo.agenda.AgendaAnnouncementRepository; import java.util.ArrayList; import java.util.List; import java.util.UUID; + import javax.persistence.EntityManager; -import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Component; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.repo.agenda.AgendaAnnouncementRepository; +import lombok.RequiredArgsConstructor; + @Component @RequiredArgsConstructor public class AgendaAnnouncementFixture { diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index 982dc95e4..d64b48137 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -4,7 +4,6 @@ import static gg.data.agenda.type.Location.*; import static java.util.UUID.*; -import gg.data.agenda.type.AgendaStatus; import java.util.ArrayList; import java.util.List; import java.util.UUID; From 7252f835ee61218d2bb309e6d2ac5d047b808767 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Sat, 27 Jul 2024 21:57:44 +0900 Subject: [PATCH 049/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20#912=20(#913)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 37 +++++++++++++++- .../response/AgendaProfileDetailsResDto.java | 5 +-- .../AgendaProfileInfoDetailsResDto.java | 16 +++++++ .../service/AgendaProfileFindService.java | 43 ++++++++++++++++--- .../AgendaProfileControllerTest.java | 30 +++++++++++++ .../repo/agenda/AgendaProfileRepository.java | 2 + 6 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileInfoDetailsResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 624fe5daf..d92a3afe5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -14,10 +14,13 @@ import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.user.type.RoleType; +import gg.repo.user.UserRepository; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -27,6 +30,7 @@ public class AgendaProfileController { private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileService agendaProfileService; + private final UserRepository userRepository; private static final Logger log = LoggerFactory.getLogger(AgendaProfileController.class); /** @@ -37,7 +41,8 @@ public class AgendaProfileController { @GetMapping public ResponseEntity myAgendaProfileDetails( @Login @Parameter(hidden = true) UserDto user) { - AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.detailsAgendaProfile(user.getId()); + AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.detailsAgendaProfile( + user.getIntraId()); return ResponseEntity.status(HttpStatus.OK).body(agendaProfileDetails); } @@ -53,5 +58,35 @@ public ResponseEntity agendaProfileModify(@Login @Parameter(hidden = true) agendaProfileService.modifyAgendaProfile(user.getId(), reqDto); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + /** + * AgendaProfile 상세 조회 API + * @param user 로그인한 사용자 정보 + * @return AgendaProfileDetailsResDto 객체와 HTTP 상태 코드를 포함한 ResponseEntity + */ + @GetMapping("/info") + public ResponseEntity myAgendaProfileInfoDetails( + @Login @Parameter(hidden = true) UserDto user) { + String intraId = user.getIntraId(); + Boolean isAdmin = user.getRoleType() == RoleType.ADMIN; + + AgendaProfileInfoDetailsResDto agendaProfileInfoDetails = new AgendaProfileInfoDetailsResDto(intraId, isAdmin); + + return ResponseEntity.ok(agendaProfileInfoDetails); + } + + // /** + // * 현재 참여중인 Agenda 목록 조회하는 메서드 + // * @param user 로그인한 유저의 id + // * @return List 객체 + // */ + // @GetMapping("/current/list") + // public ResponseEntity> getCurrentAttendAgendaList( + // @Login @Parameter(hidden = true) UserDto user) { + // + // List currentAttendAgendaList = agendaProfileFindService.findCurrentAttendAgenda( + // user.getId()); + // return ResponseEntity.ok(currentAttendAgendaList); + // } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java index 18f8ce03a..024c41fa2 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -3,7 +3,6 @@ import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; -import gg.data.user.User; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,8 +17,8 @@ public class AgendaProfileDetailsResDto { private Location userLocation; private int ticketCount; - public AgendaProfileDetailsResDto(User user, AgendaProfile entity, int ticketCount) { - this.userIntraId = user.getIntraId(); + public AgendaProfileDetailsResDto(String intraId, AgendaProfile entity, int ticketCount) { + this.userIntraId = intraId; this.userContent = entity.getContent(); this.userGithub = entity.getGithubUrl(); this.userCoalition = entity.getCoalition(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileInfoDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileInfoDetailsResDto.java new file mode 100644 index 000000000..67f7e403c --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileInfoDetailsResDto.java @@ -0,0 +1,16 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AgendaProfileInfoDetailsResDto { + private String intraId; + private Boolean isAdmin; + + public AgendaProfileInfoDetailsResDto(String intraId, Boolean isAdmin) { + this.intraId = intraId; + this.isAdmin = isAdmin; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 37ab75d3c..73af1340f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -7,8 +7,9 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.data.agenda.AgendaProfile; -import gg.data.user.User; import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.repo.agenda.AgendaTeamRepository; import gg.repo.agenda.TicketRepository; import gg.repo.user.UserRepository; import gg.utils.exception.custom.NotExistException; @@ -21,22 +22,50 @@ public class AgendaProfileFindService { private final UserRepository userRepository; private final AgendaProfileRepository agendaProfileRepository; private final TicketRepository ticketRepository; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; + private final AgendaTeamRepository agendaTeamRepository; /** * AgendaProfile 상세 정보를 조회하는 메서드 - * @param userId 로그인한 유저의 id + * @param intraId 로그인한 유저의 id * @return AgendaProfileDetailsResDto 객체 */ @Transactional(readOnly = true) - public AgendaProfileDetailsResDto detailsAgendaProfile(Long userId) { - User loginUser = userRepository.getById(userId); - - AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) + public AgendaProfileDetailsResDto detailsAgendaProfile(String intraId) { + AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(agendaProfile.getId()) .size(); - return new AgendaProfileDetailsResDto(loginUser, agendaProfile, ticketCount); + return new AgendaProfileDetailsResDto(intraId, agendaProfile, ticketCount); } + + // /** + // * 자기가 참여중인 Agenda 목록 조회하는 메서드 + // * @param userId 로그인한 유저의 id + // * @return AgendaProfileDetailsResDto 객체 + // */ + // @Transactional(readOnly = true) + // public List findCurrentAttendAgenda(Long userId) { + // User loginUser = userRepository.getById(userId); + // + // AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) + // .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + // + // List agendaTeamProfiles = agendaTeamProfileRepository.findByUserId(loginUser.getId()); + // + // if (agendaTeamProfiles.isEmpty()) { + // throw new NotExistException(AGENDA_PROFILE_NOT_FOUND); + // } + // + // return agendaTeamProfiles.stream() + // .filter(agendaTeamProfile -> agendaTeamProfile.getAgenda().getStatus() == AgendaStatus.ON_GOING) + // .map(agendaTeamProfile -> { + // AgendaTeam agendaTeam = agendaTeamRepository.findById(agendaTeamProfile.getId()) + // .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + // return new CurrentAttendAgendaListResDto(agendaTeamProfile, agendaTeam); + // }) + // .collect(Collectors.toList()); + // } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 133c53e42..b4dc9931b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -21,8 +21,10 @@ import gg.agenda.api.AgendaMockData; import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.data.agenda.AgendaProfile; import gg.data.user.User; +import gg.data.user.type.RoleType; import gg.repo.agenda.AgendaProfileRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; @@ -200,5 +202,33 @@ void testAgendaProfileNotFound() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("개인 프로필 admin 여부 조회") + class GetAgendaProfileInfo { + + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("로그인된 유저에 해당하는 Admin 여부를 조회합니다.") + void test() throws Exception { + //given + // when + String response = mockMvc.perform(get("/agenda/profile/info") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaProfileInfoDetailsResDto result = objectMapper.readValue(response, + AgendaProfileInfoDetailsResDto.class); + // then + Boolean isAdmin = user.getRoleType() == RoleType.ADMIN; + assertThat(result.getIntraId()).isEqualTo(user.getIntraId()); + assertThat(result.getIsAdmin()).isEqualTo(isAdmin); + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java index ae365a233..5d110342d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaProfileRepository.java @@ -8,4 +8,6 @@ public interface AgendaProfileRepository extends JpaRepository { Optional findByUserId(Long userId); + + Optional findByIntraId(String intraId); } From e6e3ba4e0d67413988e199ccba5547c88c044d2e Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:46:08 +0900 Subject: [PATCH 050/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8B=B0?= =?UTF-8?q?=EC=BC=93=20=EB=B0=9C=EA=B8=89=20=EC=8B=9C=EC=9E=91=20API=20#90?= =?UTF-8?q?8=20(#916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ticket/controller/TicketController.java | 30 +++++ .../user/ticket/service/TicketService.java | 24 +++- .../api/user/ticket/TicketControllerTest.java | 124 ++++++++++++++++++ .../src/main/java/gg/data/agenda/Ticket.java | 14 +- .../java/gg/repo/agenda/TicketRepository.java | 4 + .../java/gg/utils/exception/ErrorCode.java | 1 + .../fixture/agenda/AgendaProfileFixture.java | 29 ++++ .../utils/fixture/agenda/TicketFixture.java | 13 ++ 8 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java new file mode 100644 index 000000000..80141e8fc --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -0,0 +1,30 @@ +package gg.agenda.api.user.ticket.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.ticket.service.TicketService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/ticket") +public class TicketController { + private final TicketService ticketService; + + /** + * 티켓 설정 추가 + * @param user 사용자 정보 + */ + @PostMapping + public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login UserDto user) { + ticketService.addTicketSetup(user); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index c638ebfee..17e194b3a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -1,22 +1,30 @@ package gg.agenda.api.user.ticket.service; +import static gg.utils.exception.ErrorCode.*; + import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import gg.auth.UserDto; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; +import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.TicketRepository; +import gg.utils.exception.custom.DuplicationException; +import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class TicketService { private final TicketRepository ticketRepository; + private final AgendaProfileRepository agendaProfileRepository; /** * 티켓 환불 @@ -29,9 +37,23 @@ public void refundTickets(List changedProfiles, UUID agendaKey) { List tickets = new ArrayList<>(); for ( AgendaProfile profile : changedProfiles) { - Ticket ticket = Ticket.refundTicket(profile, agendaKey); + Ticket ticket = Ticket.createRefundedTicket(profile, agendaKey); tickets.add(ticket); } ticketRepository.saveAll(tickets); } + + /** + * 티켓 설정 추가 + * @param user 사용자 정보 + */ + public void addTicketSetup(UserDto user) { + AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + Optional optionalTicket = ticketRepository.findByAgendaProfileAndIsApprovedFalse(profile); + if (optionalTicket.isPresent()) { + throw new DuplicationException(ALREADY_TICKET_SETUP); + } + ticketRepository.save(Ticket.createNotApporveTicket(profile)); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java new file mode 100644 index 000000000..f3891b929 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -0,0 +1,124 @@ +package gg.agenda.api.user.ticket; + +import static gg.data.agenda.type.Location.*; +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.data.user.User; +import gg.repo.agenda.AgendaTeamRepository; +import gg.repo.agenda.TicketRepository; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaProfileFixture; +import gg.utils.fixture.agenda.AgendaTeamFixture; +import gg.utils.fixture.agenda.AgendaTeamProfileFixture; +import gg.utils.fixture.agenda.TicketFixture; + +@IntegrationTest +@AutoConfigureMockMvc +@Transactional +public class TicketControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private TestDataUtils testDataUtils; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private AgendaTeamRepository agendaTeamRepository; + @Autowired + private AgendaFixture agendaFixture; + @Autowired + private AgendaTeamFixture agendaTeamFixture; + @Autowired + private AgendaProfileFixture agendaProfileFixture; + @Autowired + private AgendaTeamProfileFixture agendaTeamProfileFixture; + @Autowired + private TicketFixture ticketFixture; + User seoulUser; + User gyeongsanUser; + String seoulUserAccessToken; + String gyeongsanUserAccessToken; + AgendaProfile seoulUserAgendaProfile; + AgendaProfile gyeongsanUserAgendaProfile; + + @Nested + @DisplayName("Apporve되어 있지 않은 티켓 생성 테스트") + class AddTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaProfileFixture.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("티켓 생성 성공") + void addTicketSetupSuccess() throws Exception { + //given && when + mockMvc.perform( + post("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + // then + Ticket createdTicket = ticketRepository.findByAgendaProfileId(seoulUserAgendaProfile.getId()) + .orElseThrow(); + assertThat(createdTicket.getAgendaProfile().getId()).isEqualTo(seoulUserAgendaProfile.getId()); + assertThat(createdTicket.getIsApproved()).isFalse(); + assertThat(createdTicket.getIsUsed()).isFalse(); + } + + @Test + @DisplayName("404 티켓 생성 실패 - 프로필이 존재하지 않는 경우") + void addTicketSetupFailToNotFoundProfile() throws Exception { + //given + User notExistUser = testDataUtils.createNewUser(); + String notExistUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notExistUser); + //when + mockMvc.perform( + post("/agenda/ticket") + .header("Authorization", "Bearer " + notExistUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("409 티켓 생성 실패 - 이미 티켓이 존재하는 경우") + void addTicketSetupFailToAnotherTicketSet() throws Exception { + //given + ticketFixture.createNotApporveTicket(seoulUserAgendaProfile); + //when + mockMvc.perform( + post("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); + } + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index 3faee57ad..8e71ec2e9 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -63,7 +63,7 @@ public Ticket(AgendaProfile agendaProfile, UUID issuedFrom, UUID usedTo, Boolean this.usedAt = usedAt; } - public static Ticket refundTicket(AgendaProfile agendaProfile, UUID issuedFrom) { + public static Ticket createRefundedTicket(AgendaProfile agendaProfile, UUID issuedFrom) { return Ticket.builder() .agendaProfile(agendaProfile) .issuedFrom(issuedFrom) @@ -75,6 +75,18 @@ public static Ticket refundTicket(AgendaProfile agendaProfile, UUID issuedFrom) .build(); } + public static Ticket createNotApporveTicket(AgendaProfile agendaProfile) { + return Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(null) + .usedTo(null) + .isApproved(false) + .approvedAt(null) + .isUsed(false) + .usedAt(null) + .build(); + } + public void useTicket(UUID usedTo) { this.usedTo = usedTo; this.usedAt = LocalDateTime.now(); diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index d25688b46..2e3496666 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -12,4 +12,8 @@ public interface TicketRepository extends JpaRepository { Optional findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(AgendaProfile agendaProfile); List findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(Long agendaProfileId); + + Optional findByAgendaProfileAndIsApprovedFalse(AgendaProfile agendaProfile); + + Optional findByAgendaProfileId(Long agendaProfileId); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 2392c4010..2234daf38 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -216,6 +216,7 @@ public enum ErrorCode { TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), + ALREADY_TICKET_SETUP(409, "AG", "이미 티켓 신청이 되어있습니다."), AGENDA_ALREADY_FINISHED(409, "AG", "이미 종료된 일정입니다."), AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 확정된 일정입니다."), AGENDA_ALREADY_CANCELED(409, "AG", "이미 취소된 일정입니다."), diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java new file mode 100644 index 000000000..54eccfb70 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java @@ -0,0 +1,29 @@ +package gg.utils.fixture.agenda; + +import static gg.data.agenda.type.Coalition.*; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import gg.repo.agenda.AgendaProfileRepository; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaProfileFixture { + private final AgendaProfileRepository agendaProfileRepository; + + public AgendaProfile createAgendaProfile(User user, Location location) { + AgendaProfile agendaProfile = AgendaProfile.builder() + .content("content") + .githubUrl("githubUrl") + .coalition(LEE) + .location(location) + .intraId(user.getIntraId()) + .userId(user.getId()) + .build(); + return agendaProfileRepository.save(agendaProfile); + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java index 4a4c1c787..f942fb470 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java @@ -26,4 +26,17 @@ public Ticket createTicket(AgendaProfile agendaProfile) { .build(); return ticketRepository.save(ticket); } + + public void createNotApporveTicket(AgendaProfile seoulUserAgendaProfile) { + Ticket ticket = Ticket.builder() + .agendaProfile(seoulUserAgendaProfile) + .issuedFrom(null) + .usedTo(null) + .isApproved(false) + .approvedAt(null) + .isUsed(false) + .usedAt(null) + .build(); + ticketRepository.save(ticket); + } } From 1bd1019f3051cb3026c41b01a63f57438a3761c6 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:24:20 +0900 Subject: [PATCH 051/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EA=B0=80?= =?UTF-8?q?=EC=A7=84=20=ED=8B=B0=EC=BC=93=20=ED=99=95=EC=9D=B8=20API=20#91?= =?UTF-8?q?0=20(#917)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ticket/controller/TicketController.java | 12 ++++ .../response/TicketCountResDto.java | 14 +++++ .../user/ticket/service/TicketService.java | 12 ++++ .../api/user/ticket/TicketControllerTest.java | 63 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 80141e8fc..43434c132 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -2,10 +2,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; @@ -27,4 +29,14 @@ public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login User ticketService.addTicketSetup(user); return ResponseEntity.status(HttpStatus.CREATED).build(); } + + /** + * 티켓 수 조회 + * @param user 사용자 정보 + */ + @GetMapping + public ResponseEntity ticketCountFind(@Parameter(hidden = true) @Login UserDto user) { + int ticketCount = ticketService.findTicketCount(user); + return ResponseEntity.ok().body(new TicketCountResDto(ticketCount)); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java new file mode 100644 index 000000000..876eca768 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java @@ -0,0 +1,14 @@ +package gg.agenda.api.user.ticket.controller.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TicketCountResDto { + private int ticketCount; + + public TicketCountResDto(int ticketCount) { + this.ticketCount = ticketCount; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 17e194b3a..74d42cf1e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -56,4 +56,16 @@ public void addTicketSetup(UserDto user) { } ticketRepository.save(Ticket.createNotApporveTicket(profile)); } + + /** + * 티켓 수 조회 + * @param user 사용자 정보 + * @return 티켓 수 + */ + public int findTicketCount(UserDto user) { + AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + List tickets = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(profile.getId()); + return tickets.size(); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index f3891b929..61fe55555 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.data.user.User; @@ -121,4 +122,66 @@ void addTicketSetupFailToAnotherTicketSet() throws Exception { .andExpect(status().isConflict()); } } + + @Nested + @DisplayName("티켓 개수 확인 테스트") + class FindTicketCountTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaProfileFixture.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 티켓 개수 확인 성공") + void findTicketCountSuccess() throws Exception { + //given + ticketFixture.createTicket(seoulUserAgendaProfile); + ticketFixture.createTicket(seoulUserAgendaProfile); + //when + String res = mockMvc.perform( + get("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketCountResDto result = objectMapper.readValue(res, TicketCountResDto.class); + //then + assertThat(result.getTicketCount()).isEqualTo(2); + } + + @Test + @DisplayName("200 티켓 개수 확인 성공 - 티켓이 없는 경우") + void findTicketCountSuccessToEmptyTicket() throws Exception { + //when + String res = mockMvc.perform( + get("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketCountResDto result = objectMapper.readValue(res, TicketCountResDto.class); + //then + assertThat(result.getTicketCount()).isEqualTo(0); + } + + @Test + @DisplayName("404 티켓 개수 확인 실패 - 프로필이 존재하지 않는 경우") + void findTicketCountFailToNotFoundProfile() throws Exception { + //given + User notExistUser = testDataUtils.createNewUser(); + String notExistUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notExistUser); + //when + mockMvc.perform( + get("/agenda/ticket") + .header("Authorization", "Bearer " + notExistUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + } } From 696875cea80df2fefe345bccbd96398dca50c155 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:08:49 +0900 Subject: [PATCH 052/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20AgendaT?= =?UTF-8?q?eam=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0?= =?UTF-8?q?=20#898=20(#918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/AgendaTeamAdminRepository.java | 5 + .../controller/AgendaTeamAdminController.java | 44 ++++++ .../controller/response/AgendaTeamResDto.java | 51 +++++++ .../service/AgendaTeamAdminService.java | 31 ++++ .../AgendaTeamAdminControllerTest.java | 134 ++++++++++++++++++ .../service/AgendaTeamAdminServiceTest.java | 78 ++++++++++ 6 files changed, 343 insertions(+) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java index 7b299bfea..9bf0003e0 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java @@ -2,6 +2,8 @@ import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -14,4 +16,7 @@ public interface AgendaTeamAdminRepository extends JpaRepository findAllByAgenda(Agenda agenda); + + @Query("SELECT at FROM AgendaTeam at WHERE at.agenda = :agenda") + Page findAllByAgenda(Agenda agenda, Pageable pageable); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java new file mode 100644 index 000000000..f47ee47dc --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -0,0 +1,44 @@ +package gg.agenda.api.admin.agendateam.controller; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; +import gg.agenda.api.admin.agendateam.service.AgendaTeamAdminService; +import gg.data.agenda.AgendaTeam; +import gg.utils.dto.PageRequestDto; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/admin/agenda/team") +@RequiredArgsConstructor +public class AgendaTeamAdminController { + + private final AgendaTeamAdminService agendaTeamAdminService; + + @GetMapping("/list") + public ResponseEntity> getAgendaTeamList(@RequestParam("agenda_key") UUID agendaKey, + @RequestBody @Valid PageRequestDto pageRequestDto) { + int page = pageRequestDto.getPage(); + int size = pageRequestDto.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List agendaTeamList = agendaTeamAdminService.getAgendaTeamList(agendaKey, pageable); + List agendaTeamResDtoList = agendaTeamList.stream() + .map(AgendaTeamResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(agendaTeamResDtoList); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java new file mode 100644 index 000000000..34ca6369e --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java @@ -0,0 +1,51 @@ +package gg.agenda.api.admin.agendateam.controller.response; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.AgendaTeam; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamResDto { + + private String teamName; + + private String teamStatus; + + private int teamScore; + + private boolean teamIsPrivate; + + private String teamLeaderIntraId; + + private int teamMateCount; + + private UUID teamKey; + + @Builder + public AgendaTeamResDto(String teamName, String teamStatus, int teamScore, boolean teamIsPrivate, + String teamLeaderIntraId, int teamMateCount, UUID teamKey) { + this.teamName = teamName; + this.teamStatus = teamStatus; + this.teamScore = teamScore; + this.teamIsPrivate = teamIsPrivate; + this.teamLeaderIntraId = teamLeaderIntraId; + this.teamMateCount = teamMateCount; + this.teamKey = teamKey; + } + + @Mapper + public interface MapStruct { + + AgendaTeamResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamResDto.MapStruct.class); + + AgendaTeamResDto toDto(AgendaTeam agendaTeam); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java new file mode 100644 index 000000000..ab46c3f96 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -0,0 +1,31 @@ +package gg.agenda.api.admin.agendateam.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaTeamAdminService { + + private final AgendaAdminRepository agendaAdminRepository; + + private final AgendaTeamAdminRepository agendaTeamAdminRepository; + + public List getAgendaTeamList(UUID agendaKey, Pageable pageable) { + Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) + .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); + return agendaTeamAdminRepository.findAllByAgenda(agenda, pageable).getContent(); + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java new file mode 100644 index 000000000..37b04b747 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -0,0 +1,134 @@ +package gg.agenda.api.admin.agendateam.controller; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; +import java.util.UUID; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.user.User; +import gg.utils.AgendaTestDataUtils; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; +import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaTeamFixture; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaTeamAdminControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaFixture agendaFixture; + + @Autowired + private AgendaTeamFixture agendaTeamFixture; + + @Autowired + private AgendaTestDataUtils agendaTestDataUtils; + + @Autowired + EntityManager em; + + @Autowired + AgendaAdminRepository agendaAdminRepository; + + @Autowired + AgendaTeamAdminRepository agendaTeamAdminRepository; + + private User user; + + private String accessToken; + + @BeforeEach + void setUp() { + user = testDataUtils.createAdminUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("Admin AgendaTeam 전체 조회") + class GetAgencyTeamListAdmin { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("Admin AgendaTeam 전체 조회 성공") + void getAgendaTeamListAdminSuccess(int page) throws Exception { + // given + int size = 10; + int total = 37; + Agenda agenda = agendaFixture.createAgenda(); + List teams = agendaTeamFixture + .createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, total); + PageRequestDto pageRequestDto = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequestDto); + + // when + String response = mockMvc.perform(get("/admin/agenda/team/list") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaTeamResDto[] result = objectMapper.readValue(response, AgendaTeamResDto[].class); + + // then + assertThat(result).isNotNull(); + assertThat(result).hasSize(((page - 1) * size) < teams.size() + ? Math.min(size, teams.size() - (page - 1) * size) : 0); + teams.sort((a, b) -> b.getId().compareTo(a.getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getTeamKey()).isEqualTo(teams.get(i + (page - 1) * size).getTeamKey()); + } + } + + @Test + @DisplayName("Admin AgendaTeam 전체 조회 실패 - Agenda 없음") + void getAgendaTeamListAdminFailedWithNoAgenda() throws Exception { + // given + PageRequestDto pageRequestDto = new PageRequestDto(1, 10); + String request = objectMapper.writeValueAsString(pageRequestDto); + + // expected + mockMvc.perform(get("/admin/agenda/team/list") + .header("Authorization", "Bearer " + accessToken) + .param("agenda_key", UUID.randomUUID().toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java new file mode 100644 index 000000000..55226c349 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java @@ -0,0 +1,78 @@ +package gg.agenda.api.admin.agendateam.service; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; +import gg.utils.annotation.UnitTest; +import gg.utils.exception.custom.NotExistException; + +@UnitTest +public class AgendaTeamAdminServiceTest { + + @Mock + private AgendaAdminRepository agendaAdminRepository; + + @Mock + private AgendaTeamAdminRepository agendaTeamAdminRepository; + + @InjectMocks + private AgendaTeamAdminService agendaTeamAdminService; + + @Nested + @DisplayName("Admin AgendaTeam 전체 조회") + class GetAgendaTeamListAdmin { + + @Test + @DisplayName("Admin AgendaTeam 전체 조회 성공") + void getAgendaTeamListAdminSuccess() { + // given + Agenda agenda = Agenda.builder().build(); + List announcements = new ArrayList<>(); + Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending()); + when(agendaAdminRepository.findByAgendaKey(any(UUID.class))).thenReturn(Optional.of(agenda)); + when(agendaTeamAdminRepository.findAllByAgenda(any(Agenda.class), any(Pageable.class))) + .thenReturn(new PageImpl<>(announcements)); + + // when + List result = agendaTeamAdminService.getAgendaTeamList(agenda.getAgendaKey(), pageable); + + // then + verify(agendaAdminRepository, times(1)).findByAgendaKey(any(UUID.class)); + verify(agendaTeamAdminRepository, times(1)) + .findAllByAgenda(any(Agenda.class), any(Pageable.class)); + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Admin AgendaTeam 전체 조회 실패 - Agenda 없음") + void getAgendaTeamListAdminFailedWithNoAgenda() { + // given + Pageable pageable = mock(Pageable.class); + when(agendaAdminRepository.findByAgendaKey(any(UUID.class))).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaTeamAdminService.getAgendaTeamList(UUID.randomUUID(), pageable)); + } + } +} From 9b56f7a091d8fb43c983b33c437c46e05b2f6721 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:38:07 +0900 Subject: [PATCH 053/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20AgendaT?= =?UTF-8?q?eam=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0?= =?UTF-8?q?=20#901=20(#919)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/AgendaTeamAdminRepository.java | 5 + .../AgendaTeamProfileAdminRepository.java | 15 +++ .../controller/AgendaTeamAdminController.java | 13 +++ .../request/AgendaTeamKeyReqDto.java | 23 +++++ .../response/AgendaProfileResDto.java | 37 ++++++++ .../response/AgendaTeamDetailResDto.java | 69 ++++++++++++++ .../service/AgendaTeamAdminService.java | 20 ++++ .../AgendaTeamAdminControllerTest.java | 93 +++++++++++++++++++ .../service/AgendaTeamAdminServiceTest.java | 88 ++++++++++++++++++ .../fixture/agenda/AgendaProfileFixture.java | 23 +++++ 10 files changed, 386 insertions(+) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java index 9bf0003e0..cd543ca74 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamAdminRepository.java @@ -1,6 +1,8 @@ package gg.admin.repo.agenda; import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -19,4 +21,7 @@ public interface AgendaTeamAdminRepository extends JpaRepository findAllByAgenda(Agenda agenda, Pageable pageable); + + @Query("SELECT at FROM AgendaTeam at WHERE at.teamKey = :teamKey") + Optional findByTeamKey(UUID teamKey); } diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java new file mode 100644 index 000000000..33b0336b6 --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java @@ -0,0 +1,15 @@ +package gg.admin.repo.agenda; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; + +@Repository +public interface AgendaTeamProfileAdminRepository extends JpaRepository { + + List findAllByAgendaTeamAndIsExistIsTrue(AgendaTeam agendaTeam); +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index f47ee47dc..cfc1eeef3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -16,8 +16,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; +import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; import gg.agenda.api.admin.agendateam.service.AgendaTeamAdminService; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.utils.dto.PageRequestDto; import lombok.RequiredArgsConstructor; @@ -41,4 +44,14 @@ public ResponseEntity> getAgendaTeamList(@RequestParam("a .collect(Collectors.toList()); return ResponseEntity.ok(agendaTeamResDtoList); } + + @GetMapping + public ResponseEntity getAgendaTeamDetail( + @RequestBody @Valid AgendaTeamKeyReqDto agendaTeamKeyReqDto) { + AgendaTeam agendaTeam = agendaTeamAdminService.getAgendaTeamByTeamKey(agendaTeamKeyReqDto.getTeamKey()); + List participants = agendaTeamAdminService.getAgendaProfileListByAgendaTeam(agendaTeam); + AgendaTeamDetailResDto agendaTeamDetailResDto = AgendaTeamDetailResDto.MapStruct.INSTANCE + .toDto(agendaTeam, participants); + return ResponseEntity.ok(agendaTeamDetailResDto); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java new file mode 100644 index 000000000..937611b77 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java @@ -0,0 +1,23 @@ +package gg.agenda.api.admin.agendateam.controller.request; + +import java.util.UUID; + +import javax.validation.constraints.NotNull; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamKeyReqDto { + + @NotNull + private UUID teamKey; + + @Builder + public AgendaTeamKeyReqDto(UUID teamKey) { + this.teamKey = teamKey; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java new file mode 100644 index 000000000..e0d7ade2f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java @@ -0,0 +1,37 @@ +package gg.agenda.api.admin.agendateam.controller.response; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Coalition; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaProfileResDto { + + private String intraId; + + private Coalition coalition; + + @Builder + public AgendaProfileResDto(String intraId, Coalition coalition) { + this.intraId = intraId; + this.coalition = coalition; + } + + @Mapper + public interface MapStruct { + + AgendaProfileResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaProfileResDto.MapStruct.class); + + @Mapping(target = "intraId", source = "intraId") + @Mapping(target = "coalition", source = "coalition") + AgendaProfileResDto toDto(AgendaProfile profile); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java new file mode 100644 index 000000000..655cfc2da --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java @@ -0,0 +1,69 @@ +package gg.agenda.api.admin.agendateam.controller.response; + +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamDetailResDto { + + private String teamName; + + private String teamLeaderIntraId; + + private String teamStatus; + + private String teamAward; + + private int teamAwardPriority; + + private boolean teamIsPrivate; + + private List teamMates; + + @Builder + public AgendaTeamDetailResDto(String teamName, String teamLeaderIntraId, String teamStatus, String teamAward, + int teamAwardPriority, boolean teamIsPrivate, List teamMates) { + this.teamName = teamName; + this.teamLeaderIntraId = teamLeaderIntraId; + this.teamStatus = teamStatus; + this.teamAward = teamAward; + this.teamAwardPriority = teamAwardPriority; + this.teamIsPrivate = teamIsPrivate; + this.teamMates = teamMates; + } + + @Mapper + public interface MapStruct { + + AgendaTeamDetailResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamDetailResDto.MapStruct.class); + + @Mapping(target = "teamName", source = "team.name") + @Mapping(target = "teamLeaderIntraId", source = "team.leaderIntraId") + @Mapping(target = "teamStatus", source = "team.status") + @Mapping(target = "teamAward", source = "team.award") + @Mapping(target = "teamAwardPriority", source = "team.awardPriority") + @Mapping(target = "teamIsPrivate", source = "team.isPrivate") + @Mapping(target = "teamMates", source = "teamMates", qualifiedByName = "toAgendaProfileResDtoList") + AgendaTeamDetailResDto toDto(AgendaTeam team, List teamMates); + + @Named("toAgendaProfileResDtoList") + default List toAgendaProfileResDtoList(List teamMates) { + return teamMates.stream() + .map(AgendaProfileResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + } + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index ab46c3f96..ba9630f0e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -4,14 +4,19 @@ import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @@ -23,9 +28,24 @@ public class AgendaTeamAdminService { private final AgendaTeamAdminRepository agendaTeamAdminRepository; + private final AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; + + @Transactional(readOnly = true) public List getAgendaTeamList(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); return agendaTeamAdminRepository.findAllByAgenda(agenda, pageable).getContent(); } + + @Transactional(readOnly = true) + public AgendaTeam getAgendaTeamByTeamKey(UUID teamKey) { + return agendaTeamAdminRepository.findByTeamKey(teamKey) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + } + + @Transactional(readOnly = true) + public List getAgendaProfileListByAgendaTeam(AgendaTeam agendaTeam) { + return agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(agendaTeam).stream() + .map(AgendaTeamProfile::getProfile).collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index 37b04b747..1a43a8272 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -25,8 +25,12 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; +import gg.agenda.api.admin.agendateam.controller.response.AgendaProfileResDto; +import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.user.User; @@ -35,7 +39,9 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; +import gg.utils.fixture.agenda.AgendaTeamProfileFixture; @IntegrationTest @Transactional @@ -57,6 +63,12 @@ public class AgendaTeamAdminControllerTest { @Autowired private AgendaTeamFixture agendaTeamFixture; + @Autowired + private AgendaProfileFixture agendaProfileFixture; + + @Autowired + private AgendaTeamProfileFixture agendaTeamProfileFixture; + @Autowired private AgendaTestDataUtils agendaTestDataUtils; @@ -131,4 +143,85 @@ void getAgendaTeamListAdminFailedWithNoAgenda() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("Admin AgendaTeam 상세 조회") + class GetAgendaTeamDetailAdmin { + @Test + @DisplayName("Admin AgendaTeam 상세 조회 성공") + void getAgendaTeamDetailAdminSuccess() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(10); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); + + // when + String response = mockMvc.perform(get("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaTeamDetailResDto result = objectMapper.readValue(response, AgendaTeamDetailResDto.class); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTeamName()).isEqualTo(team.getName()); + assertThat(result.getTeamMates()).isNotNull(); + assertThat(result.getTeamMates().size()).isEqualTo(profiles.size()); + for (int i = 0; i < profiles.size(); i++) { + AgendaProfileResDto profile = result.getTeamMates().get(i); + assertThat(profile.getIntraId()).isEqualTo(profiles.get(i).getIntraId()); + } + } + + @Test + @DisplayName("Admin AgendaTeam 상세 조회 실패 - Team Key 없음") + void getAgendaTeamDetailAdminFailedWithNoTeamKey() throws Exception { + // given + // expected + mockMvc.perform(get("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin AgendaTeam 상세 조회 실패 - 존재하지 않는 Team") + void getAgendaTeamDetailAdminFailedWithNotFoundTeam() throws Exception { + // given + String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(UUID.randomUUID())); + + // expected + mockMvc.perform(get("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Admin AgendaTeam 상세 조회 성공 - teamMate 없는 경우 빈 리스트") + void getAgendaTeamDetailAdminSuccessWithNoTeamMates() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); + + // when + String response = mockMvc.perform(get("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + AgendaTeamDetailResDto result = objectMapper.readValue(response, AgendaTeamDetailResDto.class); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTeamName()).isEqualTo(team.getName()); + assertThat(result.getTeamMates()).isNotNull(); + assertThat(result.getTeamMates().size()).isEqualTo(0); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java index 55226c349..1c1d7c081 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java @@ -21,8 +21,11 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.NotExistException; @@ -35,6 +38,9 @@ public class AgendaTeamAdminServiceTest { @Mock private AgendaTeamAdminRepository agendaTeamAdminRepository; + @Mock + private AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; + @InjectMocks private AgendaTeamAdminService agendaTeamAdminService; @@ -75,4 +81,86 @@ void getAgendaTeamListAdminFailedWithNoAgenda() { () -> agendaTeamAdminService.getAgendaTeamList(UUID.randomUUID(), pageable)); } } + + @Nested + @DisplayName("Admin AgendaTeam Team Key로 조회") + class GetAgendaTeamByTeamKey { + + @Test + @DisplayName("Admin AgendaTeam Team Key로 조회 성공") + void getAgendaTeamByTeamKeySuccess() { + // given + UUID teamKey = UUID.randomUUID(); + AgendaTeam agendaTeam = mock(AgendaTeam.class); + when(agendaTeamAdminRepository.findByTeamKey(teamKey)).thenReturn(Optional.of(agendaTeam)); + + // when + AgendaTeam agendaTeamByTeamKey = agendaTeamAdminService.getAgendaTeamByTeamKey(teamKey); + + // then + verify(agendaTeamAdminRepository, times(1)).findByTeamKey(teamKey); + assertThat(agendaTeamByTeamKey).isNotNull(); + } + + @Test + @DisplayName("Admin AgendaTeam 상세 조회 실패 - Team Key 없음") + void getAgendaTeamByTeamKeyFailedWithNoTeam() { + // given + UUID teamKey = UUID.randomUUID(); + when(agendaTeamAdminRepository.findByTeamKey(teamKey)).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaTeamAdminService.getAgendaTeamByTeamKey(teamKey)); + } + } + + @Nested + @DisplayName("Admin AgendaTeam teamMates 조회") + class GetAgendaProfileListByAgendaTeam { + + @Test + @DisplayName("Admin AgendaTeam teamMates 조회 성공") + void getAgendaProfileListByAgendaTeamSuccess() { + // given + AgendaTeam agendaTeam = mock(AgendaTeam.class); + List participants = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + AgendaProfile profile = AgendaProfile.builder().build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .profile(profile).build(); + participants.add(participant); + } + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(agendaTeam)) + .thenReturn(participants); + + // when + List result = agendaTeamAdminService.getAgendaProfileListByAgendaTeam(agendaTeam); + + // then + verify(agendaTeamProfileAdminRepository, times(1)) + .findAllByAgendaTeamAndIsExistIsTrue(agendaTeam); + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Admin AgendaTeam teamMates 조회 성공 - teamMates 없는 경우 빈 리스트 반환") + void getAgendaProfileListByAgendaTeamFailedWithNoTeam() { + // given + AgendaTeam agendaTeam = mock(AgendaTeam.class); + List participants = new ArrayList<>(); + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(agendaTeam)) + .thenReturn(participants); + + // when + List result = agendaTeamAdminService.getAgendaProfileListByAgendaTeam(agendaTeam); + + // then + verify(agendaTeamProfileAdminRepository, times(1)) + .findAllByAgendaTeamAndIsExistIsTrue(agendaTeam); + assertThat(result).isNotNull(); + assertThat(result).isEmpty(); + + } + } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java index 54eccfb70..42a15ed8c 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java @@ -2,6 +2,9 @@ import static gg.data.agenda.type.Coalition.*; +import gg.utils.TestDataUtils; +import java.util.ArrayList; +import java.util.List; import org.springframework.stereotype.Component; import gg.data.agenda.AgendaProfile; @@ -13,8 +16,11 @@ @Component @RequiredArgsConstructor public class AgendaProfileFixture { + private final AgendaProfileRepository agendaProfileRepository; + private final TestDataUtils testDataUtils; + public AgendaProfile createAgendaProfile(User user, Location location) { AgendaProfile agendaProfile = AgendaProfile.builder() .content("content") @@ -26,4 +32,21 @@ public AgendaProfile createAgendaProfile(User user, Location location) { .build(); return agendaProfileRepository.save(agendaProfile); } + + public List createAgendaProfileList(int size) { + List agendaProfileList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + User user = testDataUtils.createNewUser(); + AgendaProfile agendaProfile = AgendaProfile.builder() + .content("content") + .githubUrl("githubUrl") + .coalition(LEE) + .location(Location.SEOUL) + .intraId(user.getIntraId()) + .userId(user.getId()) + .build(); + agendaProfileList.add(agendaProfile); + } + return agendaProfileRepository.saveAll(agendaProfileList); + } } From ff757c391613164f532c6152645966b931d540ca Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:32:33 +0900 Subject: [PATCH 054/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8B=B0?= =?UTF-8?q?=EC=BC=93=20=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20#911=20(#920)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ticket/controller/TicketController.java | 20 ++ .../response/TicketHistoryResDto.java | 59 ++++++ .../user/ticket/service/TicketService.java | 39 ++++ .../api/user/ticket/TicketControllerTest.java | 178 ++++++++++++++++++ .../java/gg/repo/agenda/AgendaRepository.java | 3 +- .../java/gg/repo/agenda/TicketRepository.java | 4 + .../utils/fixture/agenda/TicketFixture.java | 16 ++ 7 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketHistoryResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 43434c132..66d89071e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -1,16 +1,26 @@ package gg.agenda.api.user.ticket.controller; +import java.util.List; + +import javax.validation.Valid; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; +import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.utils.dto.PageRequestDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -39,4 +49,14 @@ public ResponseEntity ticketCountFind(@Parameter(hidden = tru int ticketCount = ticketService.findTicketCount(user); return ResponseEntity.ok().body(new TicketCountResDto(ticketCount)); } + + @GetMapping("/history") + public ResponseEntity> ticketHistoryList(@Parameter(hidden = true) @Login UserDto user, + @RequestBody @Valid PageRequestDto pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List tickets = ticketService.listTicketHistory(user, pageable); + return ResponseEntity.ok().body(tickets); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketHistoryResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketHistoryResDto.java new file mode 100644 index 000000000..32617aa59 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketHistoryResDto.java @@ -0,0 +1,59 @@ +package gg.agenda.api.user.ticket.controller.response; + +import java.time.LocalDateTime; +import java.util.UUID; + +import gg.data.agenda.Agenda; +import gg.data.agenda.Ticket; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TicketHistoryResDto { + private LocalDateTime createdAt; + private String issuedFrom; + private UUID issuedFromKey; + private String usedTo; + private UUID usedToKey; + private boolean isApproved; + private LocalDateTime approvedAt; + private boolean isUsed; + private LocalDateTime usedAt; + + public TicketHistoryResDto(Ticket ticket) { + this.createdAt = ticket.getCreatedAt(); + this.issuedFrom = "42Intra"; + this.issuedFromKey = ticket.getIssuedFrom(); + if (ticket.getIsApproved()) { + this.usedTo = "NotUsed"; + } else { + this.usedTo = "NotApproved"; + } + this.usedToKey = ticket.getUsedTo(); + this.isApproved = ticket.getIsApproved(); + this.approvedAt = ticket.getApprovedAt(); + this.isUsed = ticket.getIsUsed(); + this.usedAt = ticket.getUsedAt(); + } + + public void changeIssuedFrom(Agenda agenda) { + if (agenda == null) { + this.issuedFrom = "42Intra"; + return; + } + this.issuedFrom = agenda.getTitle(); + } + + public void changeUsedTo(Agenda agenda) { + if (agenda == null && !this.isApproved) { + this.usedTo = "NotApproved"; + return; + } + if (agenda == null) { + this.usedTo = "NotUsed"; + return; + } + this.usedTo = agenda.getTitle(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 74d42cf1e..38ae0a987 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -6,15 +6,21 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.auth.UserDto; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.TicketRepository; import gg.utils.exception.custom.DuplicationException; import gg.utils.exception.custom.NotExistException; @@ -24,6 +30,7 @@ @RequiredArgsConstructor public class TicketService { private final TicketRepository ticketRepository; + private final AgendaRepository agendaRepository; private final AgendaProfileRepository agendaProfileRepository; /** @@ -47,6 +54,7 @@ public void refundTickets(List changedProfiles, UUID agendaKey) { * 티켓 설정 추가 * @param user 사용자 정보 */ + @Transactional public void addTicketSetup(UserDto user) { AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); @@ -62,10 +70,41 @@ public void addTicketSetup(UserDto user) { * @param user 사용자 정보 * @return 티켓 수 */ + @Transactional(readOnly = true) public int findTicketCount(UserDto user) { AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); List tickets = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(profile.getId()); return tickets.size(); } + + /** + * 티켓 이력 조회 + * @param user 사용자 정보 + * @param pageable 페이지 정보 + * @return 티켓 이력 목록 + */ + @Transactional(readOnly = true) + public List listTicketHistory(UserDto user, Pageable pageable) { + AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + Page tickets = ticketRepository.findByAgendaProfileId(profile.getId(), pageable); + + List ticketHistoryResDtos = tickets.getContent().stream() + .map(TicketHistoryResDto::new) + .collect(Collectors.toList()); + + for (TicketHistoryResDto dto : ticketHistoryResDtos) { + if (dto.getIssuedFromKey() != null) { + Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getIssuedFromKey()).orElse(null); + dto.changeIssuedFrom(agenda); + } + if (dto.getUsedToKey() != null) { + Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getUsedToKey()).orElse(null); + dto.changeUsedTo(agenda); + } + } + return ticketHistoryResDtos; + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index 61fe55555..9e3cc5a79 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -9,6 +9,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; @@ -18,6 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; +import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.data.user.User; @@ -25,6 +29,7 @@ import gg.repo.agenda.TicketRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; @@ -184,4 +189,177 @@ void findTicketCountFailToNotFoundProfile() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("티켓 히스토리 조회 테스트") + class FindTicketHistoryTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaProfileFixture.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("200 티켓 히스토리 조회 성공") + void findTicketHistorySuccess(int page) throws Exception { + //given + for (int i = 0; i < 23; i++) { + ticketFixture.createTicket(seoulUserAgendaProfile); + } + PageRequestDto req = new PageRequestDto(page, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("page", String.valueOf(page)) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).hasSize(((page - 1) * 5) < 23 + ? Math.min(5, 23 - (page - 1) * 5) : 0); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있지 않은 경우") + void findTicketHistorySuccessToNotApprove() throws Exception { + //given + ticketFixture.createTicket(seoulUserAgendaProfile, false, false, null, null); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).hasSize(1); + assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result[0].getUsedTo()).isEqualTo("NotApproved"); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있고 used 되어있는 경우") + void findTicketHistorySuccessToUsed() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, true, null, + seoulAgenda.getAgendaKey()); + ticketRepository.save(ticket); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).hasSize(1); + assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result[0].getUsedTo()).isEqualTo(seoulAgenda.getTitle()); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있고 used 되어있지 않은 경우") + void findTicketHistorySuccessToNotUsed() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, false, null, + null); + ticketRepository.save(ticket); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).hasSize(1); + assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result[0].getUsedTo()).isEqualTo("NotUsed"); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - refund 되어있고 used 되어있지 않은 경우") + void findTicketHistorySuccessToRefund() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, false, seoulAgenda.getAgendaKey(), + null); + ticketRepository.save(ticket); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).hasSize(1); + assertThat(result[0].getIssuedFrom()).isEqualTo(seoulAgenda.getTitle()); + assertThat(result[0].getUsedTo()).isEqualTo("NotUsed"); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - 티켓이 없는 경우") + void findTicketHistorySuccessToEmptyTicket() throws Exception { + //given + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + //then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("404 티켓 히스토리 조회 실패 - 프로필이 존재하지 않는 경우") + void findTicketHistoryFailToNotFoundProfile() throws Exception { + //given + User notExistUser = testDataUtils.createNewUser(); + String notExistUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notExistUser); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + mockMvc.perform( + get("/agenda/ticket/history") + .header("Authorization", "Bearer " + notExistUserAccessToken) + .content(content) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index 7057fdb0d..82f09fe7d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -5,7 +5,6 @@ import java.util.UUID; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -21,4 +20,6 @@ public interface AgendaRepository extends JpaRepository { List findAllByStatusIs(AgendaStatus status); Page findAllByStatusIs(Pageable pageable, AgendaStatus status); + + Optional findAgendaByAgendaKey(UUID usedTo); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 2e3496666..7ee76fbc3 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import gg.data.agenda.AgendaProfile; @@ -16,4 +18,6 @@ public interface TicketRepository extends JpaRepository { Optional findByAgendaProfileAndIsApprovedFalse(AgendaProfile agendaProfile); Optional findByAgendaProfileId(Long agendaProfileId); + + Page findByAgendaProfileId(Long agendaProfileId, Pageable pageable); } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java index f942fb470..91405a4ef 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java @@ -1,6 +1,7 @@ package gg.utils.fixture.agenda; import java.time.LocalDateTime; +import java.util.UUID; import org.springframework.stereotype.Component; @@ -39,4 +40,19 @@ public void createNotApporveTicket(AgendaProfile seoulUserAgendaProfile) { .build(); ticketRepository.save(ticket); } + + public Ticket createTicket(AgendaProfile agendaProfile, boolean isApproved, boolean isUsed, UUID issuedFrom, + UUID usedTo) { + Ticket ticket = Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(issuedFrom) + .usedTo(usedTo) + .isApproved(isApproved) + .approvedAt(LocalDateTime.now().minusDays(1)) + .isUsed(isUsed) + .usedAt(null) + .build(); + ticketRepository.save(ticket); + return ticket; + } } From 81b9d7133cd4f8fb22aa24405e516f44462b6467 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:18:22 +0900 Subject: [PATCH 055/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Checksty?= =?UTF-8?q?le=20validation=20-=20TestFixture=20=EC=B6=94=EA=B0=80=20#922?= =?UTF-8?q?=20(#923)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/checkstyle-validation.yml | 2 ++ .../fixture/agenda/AgendaProfileFixture.java | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/checkstyle-validation.yml b/.github/workflows/checkstyle-validation.yml index 8b8fc328c..caba78380 100644 --- a/.github/workflows/checkstyle-validation.yml +++ b/.github/workflows/checkstyle-validation.yml @@ -19,3 +19,5 @@ jobs: run: ./gradlew --console verbose clean checkstyleMain - name: ️Test checkstyle run: ./gradlew --console verbose clean checkstyleTest + - name: ️TestFixture checkstyle + run: ./gradlew --console verbose clean checkstyleTestFixture diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java index 42a15ed8c..f28925767 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java @@ -2,15 +2,16 @@ import static gg.data.agenda.type.Coalition.*; -import gg.utils.TestDataUtils; import java.util.ArrayList; import java.util.List; + import org.springframework.stereotype.Component; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Location; import gg.data.user.User; import gg.repo.agenda.AgendaProfileRepository; +import gg.utils.TestDataUtils; import lombok.RequiredArgsConstructor; @Component @@ -38,13 +39,13 @@ public List createAgendaProfileList(int size) { for (int i = 0; i < size; i++) { User user = testDataUtils.createNewUser(); AgendaProfile agendaProfile = AgendaProfile.builder() - .content("content") - .githubUrl("githubUrl") - .coalition(LEE) - .location(Location.SEOUL) - .intraId(user.getIntraId()) - .userId(user.getId()) - .build(); + .content("content") + .githubUrl("githubUrl") + .coalition(LEE) + .location(Location.SEOUL) + .intraId(user.getIntraId()) + .userId(user.getId()) + .build(); agendaProfileList.add(agendaProfile); } return agendaProfileRepository.saveAll(agendaProfileList); From 99f006b9d7fc6ee600edc012e1f46ad7c5ed401a Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:40:01 +0900 Subject: [PATCH 056/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EB=82=B4?= =?UTF-8?q?=EA=B0=80=20=EC=B0=B8=EC=97=AC=20=EC=A4=91=EC=9D=B8=20=EB=8C=80?= =?UTF-8?q?=ED=9A=8C=20=EB=B3=B4=EA=B8=B0=20API=20#854=20(#924)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 30 ++--- .../CurrentAttendAgendaListResDto.java | 27 +++++ .../service/AgendaProfileFindService.java | 54 ++++----- .../AgendaProfileControllerTest.java | 103 ++++++++++++++++++ .../agenda/AgendaTeamProfileRepository.java | 2 + 5 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index d92a3afe5..7b81f2bb6 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -1,5 +1,7 @@ package gg.agenda.api.user.agendaprofile.controller; +import java.util.List; + import javax.validation.Valid; import org.slf4j.Logger; @@ -15,6 +17,7 @@ import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.auth.UserDto; @@ -75,18 +78,19 @@ public ResponseEntity myAgendaProfileInfoDetails return ResponseEntity.ok(agendaProfileInfoDetails); } - // /** - // * 현재 참여중인 Agenda 목록 조회하는 메서드 - // * @param user 로그인한 유저의 id - // * @return List 객체 - // */ - // @GetMapping("/current/list") - // public ResponseEntity> getCurrentAttendAgendaList( - // @Login @Parameter(hidden = true) UserDto user) { - // - // List currentAttendAgendaList = agendaProfileFindService.findCurrentAttendAgenda( - // user.getId()); - // return ResponseEntity.ok(currentAttendAgendaList); - // } + /** + * 현재 참여중인 Agenda 목록 조회하는 메서드 + * @param user 로그인한 유저의 id + * @return List 객체 + */ + @GetMapping("/current/list") + public ResponseEntity> getCurrentAttendAgendaList( + @Login @Parameter(hidden = true) UserDto user) { + String intraId = user.getIntraId(); + + List currentAttendAgendaList = agendaProfileFindService.findCurrentAttendAgenda( + intraId); + return ResponseEntity.ok(currentAttendAgendaList); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java new file mode 100644 index 000000000..9e410a6c1 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java @@ -0,0 +1,27 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import java.util.UUID; + +import gg.data.agenda.AgendaTeamProfile; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class CurrentAttendAgendaListResDto { + private String agendaId; + private String agendaTitle; + private String agendaLocation; + private UUID teamKey; + private Boolean isOfficial; + private String teamName; + + public CurrentAttendAgendaListResDto(AgendaTeamProfile agendaTeamProfile) { + this.agendaId = agendaTeamProfile.getAgenda().getId().toString(); + this.agendaTitle = agendaTeamProfile.getAgenda().getTitle(); + this.agendaLocation = agendaTeamProfile.getAgenda().getLocation().toString(); + this.teamKey = agendaTeamProfile.getAgendaTeam().getTeamKey(); + this.isOfficial = agendaTeamProfile.getAgenda().getIsOfficial(); + this.teamName = agendaTeamProfile.getAgendaTeam().getName(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 73af1340f..df78c24d2 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -2,11 +2,17 @@ import static gg.utils.exception.ErrorCode.*; +import java.util.List; +import java.util.stream.Collectors; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeamProfile; +import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; @@ -41,31 +47,25 @@ public AgendaProfileDetailsResDto detailsAgendaProfile(String intraId) { return new AgendaProfileDetailsResDto(intraId, agendaProfile, ticketCount); } - // /** - // * 자기가 참여중인 Agenda 목록 조회하는 메서드 - // * @param userId 로그인한 유저의 id - // * @return AgendaProfileDetailsResDto 객체 - // */ - // @Transactional(readOnly = true) - // public List findCurrentAttendAgenda(Long userId) { - // User loginUser = userRepository.getById(userId); - // - // AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(loginUser.getId()) - // .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - // - // List agendaTeamProfiles = agendaTeamProfileRepository.findByUserId(loginUser.getId()); - // - // if (agendaTeamProfiles.isEmpty()) { - // throw new NotExistException(AGENDA_PROFILE_NOT_FOUND); - // } - // - // return agendaTeamProfiles.stream() - // .filter(agendaTeamProfile -> agendaTeamProfile.getAgenda().getStatus() == AgendaStatus.ON_GOING) - // .map(agendaTeamProfile -> { - // AgendaTeam agendaTeam = agendaTeamRepository.findById(agendaTeamProfile.getId()) - // .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); - // return new CurrentAttendAgendaListResDto(agendaTeamProfile, agendaTeam); - // }) - // .collect(Collectors.toList()); - // } + /** + * 자기가 참여중인 Agenda 목록 조회하는 메서드 + * @param intraId 로그인한 유저의 id + * @return AgendaProfileDetailsResDto 객체 + */ + @Transactional(readOnly = true) + public List findCurrentAttendAgenda(String intraId) { + AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + List agendaTeamProfiles = agendaTeamProfileRepository.findByProfile( + agendaProfile); + + return agendaTeamProfiles.stream() + .filter(agendaTeamProfile -> { + AgendaStatus status = agendaTeamProfile.getAgenda().getStatus(); + return status == AgendaStatus.OPEN || status == AgendaStatus.CONFIRM; + }) + .map(CurrentAttendAgendaListResDto::new) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index b4dc9931b..3c73d4fdf 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -5,6 +5,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + import javax.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; @@ -22,7 +26,12 @@ import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; import gg.data.user.User; import gg.data.user.type.RoleType; import gg.repo.agenda.AgendaProfileRepository; @@ -45,6 +54,7 @@ public class AgendaProfileControllerTest { private AgendaProfileRepository agendaProfileRepository; User user; String accessToken; + AgendaProfile agendaProfile; @Nested @DisplayName("agenda profile 상세 조회") @@ -230,5 +240,98 @@ void test() throws Exception { assertThat(result.getIsAdmin()).isEqualTo(isAdmin); } } + + @Nested + @DisplayName("내가 참여 중인 대회 보기") + class GetCurrentAttendAgenda { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("200 내가 참여 중인 대회 조회 성공") + public void getCurrentAttendAgendaListSuccess() throws Exception { + //given + agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + List agendaTeamList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + User agendaCreateUser = testDataUtils.createNewUser(); + User otherUser = testDataUtils.createNewUser(); + LocalDateTime startTime = LocalDateTime.now().plusDays(i); + Agenda agenda = agendaMockData.createAgenda(agendaCreateUser.getIntraId(), startTime, + i % 2 == 0 ? AgendaStatus.OPEN : AgendaStatus.CONFIRM); + AgendaTeam agendaTeam = agendaMockData.createAgendaTeam(agenda, otherUser, Location.SEOUL); + agendaMockData.createAgendaTeamProfile(agendaTeam, agendaProfile); + agendaTeamList.add(agendaTeam); + } + + // when + String res = mockMvc.perform( + get("/agenda/profile/current/list") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + CurrentAttendAgendaListResDto[] result = objectMapper.readValue(res, CurrentAttendAgendaListResDto[].class); + + // then + assertThat(result.length).isEqualTo(agendaTeamList.size()); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getAgendaId()).isEqualTo(agendaTeamList.get(i).getAgenda().getId().toString()); + assertThat(result[i].getAgendaTitle()).isEqualTo(agendaTeamList.get(i).getAgenda().getTitle()); + assertThat(result[i].getAgendaLocation()).isEqualTo( + agendaTeamList.get(i).getAgenda().getLocation().toString()); + assertThat(result[i].getTeamKey()).isEqualTo(agendaTeamList.get(i).getTeamKey()); + assertThat(result[i].getIsOfficial()).isEqualTo(agendaTeamList.get(i).getAgenda().getIsOfficial()); + assertThat(result[i].getTeamName()).isEqualTo(agendaTeamList.get(i).getName()); + } + } + + @Test + @DisplayName("200 내가 참여 중인 대회가 없을 때 조회 성공") + public void getCurrentAttendAgendaListSuccessNoAgenda() throws Exception { + //given + agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + // 참여 중인 대회가 없는 상태 + + // when + String res = mockMvc.perform( + get("/agenda/profile/current/list") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + CurrentAttendAgendaListResDto[] result = objectMapper.readValue(res, CurrentAttendAgendaListResDto[].class); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("해당 로그인 유저의 아젠다 프로필이 없을 때") + void testAgendaProfileNotFound() throws Exception { + // given: 특정 유저와 관련된 AgendaProfile이 없음 + + // when & then: 예외가 발생해야 함 + mockMvc.perform(get("/agenda/profile/current/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("해당 로그인 유저의 AgendaTeam이 없을 때") + void testAgendaTeamNotFound() throws Exception { + // given: 특정 유저와 관련된 AgendaTeam이 없음 + + // when & then: 예외가 발생해야 함 + mockMvc.perform(get("/agenda/profile/current/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + } } + + diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 412cf95e7..60df26164 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -22,4 +22,6 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); List findAllByAgendaTeam(AgendaTeam agendaTeam); + + List findByProfile(AgendaProfile agendaProfile); } From 16eeafc76af3dd2e8e11d14d98b28b056228ddad Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Tue, 30 Jul 2024 19:29:06 +0900 Subject: [PATCH 057/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20AgendaT?= =?UTF-8?q?eam=20=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20#902=20(#921)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/AgendaProfileAdminRepository.java | 17 + .../controller/AgendaTeamAdminController.java | 13 +- .../request/AgendaTeamMateReqDto.java | 21 ++ .../request/AgendaTeamUpdateDto.java | 68 ++++ .../response/AgendaTeamDetailResDto.java | 8 +- ...eResDto.java => AgendaTeamMateResDto.java} | 8 +- .../service/AgendaTeamAdminService.java | 42 +++ .../controller/request/AgendaTeamAward.java | 7 +- .../AgendaTeamAdminControllerTest.java | 322 +++++++++++++++++- .../service/AgendaTeamAdminServiceTest.java | 154 +++++++++ .../main/java/gg/data/agenda/AgendaTeam.java | 28 +- .../utils/fixture/agenda/AgendaFixture.java | 4 +- .../fixture/agenda/AgendaProfileFixture.java | 13 + .../fixture/agenda/AgendaTeamFixture.java | 26 +- .../agenda/AgendaTeamProfileFixture.java | 1 + 15 files changed, 712 insertions(+), 20 deletions(-) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamMateReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamUpdateDto.java rename gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/{AgendaProfileResDto.java => AgendaTeamMateResDto.java} (72%) diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java new file mode 100644 index 000000000..f6631c0f5 --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java @@ -0,0 +1,17 @@ +package gg.admin.repo.agenda; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.AgendaProfile; + +@Repository +public interface AgendaProfileAdminRepository extends JpaRepository { + + + @Query("SELECT a FROM AgendaProfile a WHERE a.intraId = :intraId") + Optional findByIntraId(String intraId); +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index cfc1eeef3..6efef42fb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -9,14 +9,17 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; import gg.agenda.api.admin.agendateam.service.AgendaTeamAdminService; @@ -33,7 +36,7 @@ public class AgendaTeamAdminController { private final AgendaTeamAdminService agendaTeamAdminService; @GetMapping("/list") - public ResponseEntity> getAgendaTeamList(@RequestParam("agenda_key") UUID agendaKey, + public ResponseEntity> agendaTeamList(@RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid PageRequestDto pageRequestDto) { int page = pageRequestDto.getPage(); int size = pageRequestDto.getSize(); @@ -46,7 +49,7 @@ public ResponseEntity> getAgendaTeamList(@RequestParam("a } @GetMapping - public ResponseEntity getAgendaTeamDetail( + public ResponseEntity agendaTeamDetail( @RequestBody @Valid AgendaTeamKeyReqDto agendaTeamKeyReqDto) { AgendaTeam agendaTeam = agendaTeamAdminService.getAgendaTeamByTeamKey(agendaTeamKeyReqDto.getTeamKey()); List participants = agendaTeamAdminService.getAgendaProfileListByAgendaTeam(agendaTeam); @@ -54,4 +57,10 @@ public ResponseEntity getAgendaTeamDetail( .toDto(agendaTeam, participants); return ResponseEntity.ok(agendaTeamDetailResDto); } + + @PatchMapping + public ResponseEntity agendaTeamUpdate(@RequestBody @Valid AgendaTeamUpdateDto agendaTeamUpdateDto) { + agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamMateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamMateReqDto.java new file mode 100644 index 000000000..f994c5e15 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamMateReqDto.java @@ -0,0 +1,21 @@ +package gg.agenda.api.admin.agendateam.controller.request; + +import javax.validation.constraints.NotNull; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamMateReqDto { + + @NotNull + private String intraId; + + @Builder + public AgendaTeamMateReqDto(String intraId) { + this.intraId = intraId; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamUpdateDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamUpdateDto.java new file mode 100644 index 000000000..be8d494c4 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamUpdateDto.java @@ -0,0 +1,68 @@ +package gg.agenda.api.admin.agendateam.controller.request; + +import java.util.List; +import java.util.UUID; + +import javax.validation.Valid; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaTeamUpdateDto { + + @NotNull + private UUID teamKey; + + @NotBlank + @Size(max = 30) + private String teamName; + + @NotBlank + private String teamContent; + + @NotNull + private AgendaTeamStatus teamStatus; + + @NotNull + private Boolean teamIsPrivate; + + @NotNull + private Location teamLocation; + + @NotNull + private String teamAward; + + @Min(1) + @Max(1000) + private Integer teamAwardPriority; + + @Valid + @NotNull + private List teamMates; + + @Builder + public AgendaTeamUpdateDto(UUID teamKey, String teamName, String teamContent, AgendaTeamStatus teamStatus, + Location teamLocation, String teamAward, Integer teamAwardPriority, Boolean teamIsPrivate, + List teamMates) { + this.teamKey = teamKey; + this.teamName = teamName; + this.teamContent = teamContent; + this.teamStatus = teamStatus; + this.teamAward = teamAward; + this.teamAwardPriority = teamAwardPriority; + this.teamIsPrivate = teamIsPrivate; + this.teamLocation = teamLocation; + this.teamMates = teamMates; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java index 655cfc2da..6862b87d0 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java @@ -31,11 +31,11 @@ public class AgendaTeamDetailResDto { private boolean teamIsPrivate; - private List teamMates; + private List teamMates; @Builder public AgendaTeamDetailResDto(String teamName, String teamLeaderIntraId, String teamStatus, String teamAward, - int teamAwardPriority, boolean teamIsPrivate, List teamMates) { + int teamAwardPriority, boolean teamIsPrivate, List teamMates) { this.teamName = teamName; this.teamLeaderIntraId = teamLeaderIntraId; this.teamStatus = teamStatus; @@ -60,9 +60,9 @@ public interface MapStruct { AgendaTeamDetailResDto toDto(AgendaTeam team, List teamMates); @Named("toAgendaProfileResDtoList") - default List toAgendaProfileResDtoList(List teamMates) { + default List toAgendaProfileResDtoList(List teamMates) { return teamMates.stream() - .map(AgendaProfileResDto.MapStruct.INSTANCE::toDto) + .map(AgendaTeamMateResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamMateResDto.java similarity index 72% rename from gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java rename to gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamMateResDto.java index e0d7ade2f..fbf4d320f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaProfileResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamMateResDto.java @@ -13,14 +13,14 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaProfileResDto { +public class AgendaTeamMateResDto { private String intraId; private Coalition coalition; @Builder - public AgendaProfileResDto(String intraId, Coalition coalition) { + public AgendaTeamMateResDto(String intraId, Coalition coalition) { this.intraId = intraId; this.coalition = coalition; } @@ -28,10 +28,10 @@ public AgendaProfileResDto(String intraId, Coalition coalition) { @Mapper public interface MapStruct { - AgendaProfileResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaProfileResDto.MapStruct.class); + AgendaTeamMateResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamMateResDto.MapStruct.class); @Mapping(target = "intraId", source = "intraId") @Mapping(target = "coalition", source = "coalition") - AgendaProfileResDto toDto(AgendaProfile profile); + AgendaTeamMateResDto toDto(AgendaProfile profile); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index ba9630f0e..1eac37949 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -11,8 +11,11 @@ import org.springframework.transaction.annotation.Transactional; import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaProfileAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamMateReqDto; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; @@ -28,6 +31,8 @@ public class AgendaTeamAdminService { private final AgendaTeamAdminRepository agendaTeamAdminRepository; + private final AgendaProfileAdminRepository agendaProfileAdminRepository; + private final AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; @Transactional(readOnly = true) @@ -48,4 +53,41 @@ public List getAgendaProfileListByAgendaTeam(AgendaTeam agendaTea return agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(agendaTeam).stream() .map(AgendaTeamProfile::getProfile).collect(Collectors.toList()); } + + @Transactional + public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { + AgendaTeam team = agendaTeamAdminRepository.findByTeamKey(agendaTeamUpdateDto.getTeamKey()) + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + List profiles = agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(team); + List updatedTeamMates = agendaTeamUpdateDto.getTeamMates().stream() + .map(AgendaTeamMateReqDto::getIntraId) + .collect(Collectors.toList()); + List currentTeamMates = profiles.stream() + .map(profile -> profile.getProfile().getIntraId()) + .collect(Collectors.toList()); + + // AgendaTeam 정보 변경 + team.updateTeamAdmin(agendaTeamUpdateDto.getTeamName(), agendaTeamUpdateDto.getTeamContent(), + agendaTeamUpdateDto.getTeamIsPrivate(), agendaTeamUpdateDto.getTeamStatus()); + team.updateLocation(agendaTeamUpdateDto.getTeamLocation(), profiles); + team.acceptAward(agendaTeamUpdateDto.getTeamAward(), agendaTeamUpdateDto.getTeamAwardPriority()); + + // AgendaTeam 팀원 내보내기 + profiles.stream().filter(profile -> !updatedTeamMates.contains(profile.getProfile().getIntraId())) + .forEach(agentTeamProfile -> { + String intraId = agentTeamProfile.getProfile().getIntraId(); + agentTeamProfile.getAgendaTeam().leaveTeamMateAdmin(intraId); + agentTeamProfile.leaveTeam(); + }); + + // AgendaTeam 팀원 추가하기 + updatedTeamMates.stream().filter(intraId -> !currentTeamMates.contains(intraId)) + .forEach(intraId -> { + AgendaProfile profile = agendaProfileAdminRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + team.attendTeamAdmin(team.getAgenda()); + AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(team, team.getAgenda(), profile); + agendaTeamProfileAdminRepository.save(agendaTeamProfile); + }); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java index a2dae184e..897ffe39f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaTeamAward.java @@ -2,9 +2,12 @@ import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.Length; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -18,8 +21,8 @@ public class AgendaTeamAward { @NotEmpty private String teamName; - @NotNull - @NotEmpty + @NotBlank + @Length(max = 30) private String awardName; @Min(1) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index 1a43a8272..6772aeb98 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import javax.persistence.EntityManager; import javax.transaction.Transactional; @@ -25,14 +26,19 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; +import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; -import gg.agenda.api.admin.agendateam.controller.response.AgendaProfileResDto; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamMateReqDto; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; +import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamMateResDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; import gg.data.user.User; import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; @@ -81,6 +87,9 @@ public class AgendaTeamAdminControllerTest { @Autowired AgendaTeamAdminRepository agendaTeamAdminRepository; + @Autowired + AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; + private User user; private String accessToken; @@ -153,7 +162,7 @@ void getAgendaTeamDetailAdminSuccess() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); - List profiles = agendaProfileFixture.createAgendaProfileList(10); + List profiles = agendaProfileFixture.createAgendaProfileList(5); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); @@ -172,7 +181,7 @@ void getAgendaTeamDetailAdminSuccess() throws Exception { assertThat(result.getTeamMates()).isNotNull(); assertThat(result.getTeamMates().size()).isEqualTo(profiles.size()); for (int i = 0; i < profiles.size(); i++) { - AgendaProfileResDto profile = result.getTeamMates().get(i); + AgendaTeamMateResDto profile = result.getTeamMates().get(i); assertThat(profile.getIntraId()).isEqualTo(profiles.get(i).getIntraId()); } } @@ -224,4 +233,311 @@ void getAgendaTeamDetailAdminSuccessWithNoTeamMates() throws Exception { assertThat(result.getTeamMates().size()).isEqualTo(0); } } + + @Nested + @DisplayName("Admin AgendaTeam 수정") + class UpdateAgendaTeamAdmin { + @Test + @DisplayName("Admin AgendaTeam 수정 성공") + void updateAgendaTeamAdminSuccess() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); + AgendaTeam updatedAgendaTeam = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + + // then + assertThat(updatedAgendaTeam.getName()).isEqualTo(updateDto.getTeamName()); + assertThat(updatedAgendaTeam.getContent()).isEqualTo(updateDto.getTeamContent()); + assertThat(updatedAgendaTeam.getIsPrivate()).isEqualTo(updateDto.getTeamIsPrivate()); + assertThat(updatedAgendaTeam.getStatus()).isEqualTo(updateDto.getTeamStatus()); + assertThat(updatedAgendaTeam.getAward()).isEqualTo(updateDto.getTeamAward()); + assertThat(updatedAgendaTeam.getAwardPriority()).isEqualTo(updateDto.getTeamAwardPriority()); + assertThat(updatedAgendaTeam.getLocation()).isEqualTo(updateDto.getTeamLocation()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 성공 - Location을 변경할 수 없는 경우") + void updateAgendaTeamAdminFailedWithLocation() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(Location.MIX); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.GYEONGSAN) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + AgendaTeam result = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + + // then + assertThat(result.getLocation()).isEqualTo(team.getLocation()); + assertThat(result.getName()).isEqualTo(team.getName()); + assertThat(result.getContent()).isEqualTo(team.getContent()); + assertThat(result.getIsPrivate()).isEqualTo(team.getIsPrivate()); + assertThat(result.getStatus()).isEqualTo(team.getStatus()); + assertThat(result.getAward()).isEqualTo(team.getAward()); + assertThat(result.getAwardPriority()).isEqualTo(team.getAwardPriority()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 추가하기") + void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(3); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + AgendaProfile newProfile = agendaProfileFixture.createAgendaProfile(); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto(newProfile.getIntraId())); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); + AgendaTeam updatedAgendaTeam = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + List participants = agendaTeamProfileAdminRepository + .findAllByAgendaTeamAndIsExistIsTrue(updatedAgendaTeam); + + // then + assertThat(updatedAgendaTeam.getName()).isEqualTo(updateDto.getTeamName()); + assertThat(updatedAgendaTeam.getContent()).isEqualTo(updateDto.getTeamContent()); + assertThat(updatedAgendaTeam.getIsPrivate()).isEqualTo(updateDto.getTeamIsPrivate()); + assertThat(updatedAgendaTeam.getStatus()).isEqualTo(updateDto.getTeamStatus()); + assertThat(updatedAgendaTeam.getAward()).isEqualTo(updateDto.getTeamAward()); + assertThat(updatedAgendaTeam.getAwardPriority()).isEqualTo(updateDto.getTeamAwardPriority()); + assertThat(updatedAgendaTeam.getLocation()).isEqualTo(updateDto.getTeamLocation()); + assertThat(participants.size()).isEqualTo(updateTeamMates.size()); + // Check new participant + assertThat(participants.stream() + .anyMatch(participant -> participant.getProfile().getIntraId().equals(newProfile.getIntraId()))) + .isTrue(); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 이미 꽉 찬 팀에 팀원 추가하기") + void updateAgendaTeamAdminFailedWithMaxPeople() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + AgendaProfile newProfile = agendaProfileFixture.createAgendaProfile(); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto(newProfile.getIntraId())); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 삭제하기") + void updateAgendaTeamAdminSuccessWithRemoveTeammate() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(3); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + AgendaProfile wrongProfile = agendaProfileFixture.createAgendaProfile(); + agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, wrongProfile); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNoContent()); + AgendaTeam updatedAgendaTeam = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + List participants = agendaTeamProfileAdminRepository + .findAllByAgendaTeamAndIsExistIsTrue(updatedAgendaTeam); + + // then + assertThat(updatedAgendaTeam.getName()).isEqualTo(updateDto.getTeamName()); + assertThat(updatedAgendaTeam.getContent()).isEqualTo(updateDto.getTeamContent()); + assertThat(updatedAgendaTeam.getIsPrivate()).isEqualTo(updateDto.getTeamIsPrivate()); + assertThat(updatedAgendaTeam.getStatus()).isEqualTo(updateDto.getTeamStatus()); + assertThat(updatedAgendaTeam.getAward()).isEqualTo(updateDto.getTeamAward()); + assertThat(updatedAgendaTeam.getAwardPriority()).isEqualTo(updateDto.getTeamAwardPriority()); + assertThat(updatedAgendaTeam.getLocation()).isEqualTo(updateDto.getTeamLocation()); + assertThat(participants.size()).isEqualTo(updateTeamMates.size()); + // Check wrong participant + assertThat(participants.stream() + .noneMatch(participant -> participant.getProfile().getIntraId().equals(wrongProfile.getIntraId()))) + .isTrue(); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 리더를 삭제하는 경우") + void updateAgendaTeamAdminFailedWithRemoveLeader() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + User user = testDataUtils.createNewUser(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, user); + AgendaProfile leaderProfile = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); + agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, leaderProfile); + List profiles = agendaProfileFixture.createAgendaProfileList(3); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 존재하지 않는 Team Key") + void updateAgendaTeamAdminFailedWithInvalidTeamKey() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(UUID.randomUUID()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 존재하지 않는 Intra ID") + void updateAgendaTeamAdminFailedWithInvalidIntraId() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto("invalid")); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/admin/agenda/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + AgendaTeam result = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + + // then + assertThat(result.getLocation()).isEqualTo(team.getLocation()); + assertThat(result.getName()).isEqualTo(team.getName()); + assertThat(result.getContent()).isEqualTo(team.getContent()); + assertThat(result.getIsPrivate()).isEqualTo(team.getIsPrivate()); + assertThat(result.getStatus()).isEqualTo(team.getStatus()); + assertThat(result.getAward()).isEqualTo(team.getAward()); + assertThat(result.getAwardPriority()).isEqualTo(team.getAwardPriority()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java index 1c1d7c081..75c6ac353 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java @@ -20,15 +20,20 @@ import org.springframework.data.domain.Sort; import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaProfileAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamMateReqDto; +import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.NotExistException; +import lombok.extern.slf4j.Slf4j; +@Slf4j @UnitTest public class AgendaTeamAdminServiceTest { @@ -38,6 +43,9 @@ public class AgendaTeamAdminServiceTest { @Mock private AgendaTeamAdminRepository agendaTeamAdminRepository; + @Mock + private AgendaProfileAdminRepository agendaProfileAdminRepository; + @Mock private AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; @@ -163,4 +171,150 @@ void getAgendaProfileListByAgendaTeamFailedWithNoTeam() { } } + + @Nested + @DisplayName("Admin AgendaTeam 수정") + class UpdateAgendaTeamAdmin { + + @Test + @DisplayName("Admin AgendaTeam 수정 성공") + void updateAgendaTeamAdminSuccess() { + // given + Agenda agenda = Agenda.builder().build(); + AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); + AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .agendaTeam(team).agenda(agenda).profile(profile).build(); + AgendaTeamMateReqDto agendaTeamMateReqDto = AgendaTeamMateReqDto.builder() + .intraId("intra").build(); + AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(List.of(agendaTeamMateReqDto)).build(); + when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) + .thenReturn(List.of(participant)); + + // when + agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); + + // then + verify(agendaTeamAdminRepository, times(1)) + .findByTeamKey(any(UUID.class)); + verify(agendaTeamProfileAdminRepository, times(1)) + .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 추가하기") + void updateAgendaTeamAdminSuccessWithAddTeammate() { + // given + Agenda agenda = Agenda.builder().maxPeople(10).build(); + AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); + AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .agendaTeam(team).agenda(agenda).profile(profile).build(); + + AgendaProfile newProfile = AgendaProfile.builder().intraId("newIntra").build(); + List updateTeamMates = new ArrayList<>(); + updateTeamMates.add(AgendaTeamMateReqDto.builder() + .intraId(profile.getIntraId()).build()); + updateTeamMates.add(AgendaTeamMateReqDto.builder() + .intraId(newProfile.getIntraId()).build()); + + AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates).build(); + + when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) + .thenReturn(List.of(participant)); + when(agendaProfileAdminRepository.findByIntraId("newIntra")) + .thenReturn(Optional.of(newProfile)); + when(agendaTeamProfileAdminRepository.save(any(AgendaTeamProfile.class))) + .thenReturn(mock(AgendaTeamProfile.class)); + + // when + agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); + + // then + verify(agendaTeamAdminRepository, times(1)) + .findByTeamKey(any(UUID.class)); + verify(agendaTeamProfileAdminRepository, times(1)) + .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); + verify(agendaProfileAdminRepository, times(1)) + .findByIntraId("newIntra"); + verify(agendaTeamProfileAdminRepository, times(1)) + .save(any(AgendaTeamProfile.class)); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 삭제하기") + void updateAgendaTeamAdminSuccessWithRemoveTeammate() { + // given + Agenda agenda = Agenda.builder().build(); + AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); + AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .agendaTeam(team).agenda(agenda).profile(profile).build(); + + AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(List.of()).build(); + + when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) + .thenReturn(List.of(participant)); + + // when + agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); + + // then + verify(agendaTeamAdminRepository, times(1)) + .findByTeamKey(any(UUID.class)); + verify(agendaTeamProfileAdminRepository, times(1)) + .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); + assertThat(participant.getIsExist()).isFalse(); // leaveTeam() 호출 확인 + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 존재하지 않는 Team Key") + void updateAgendaTeamAdminFailedWithInvalidTeamKey() { + // given + AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() + .teamKey(UUID.randomUUID()).teamMates(List.of()).build(); + when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto)); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 존재하지 않는 Intra ID") + void updateAgendaTeamAdminFailedWithInvalidIntraId() { + // given + Agenda agenda = Agenda.builder().build(); + AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); + AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); + AgendaTeamProfile participant = AgendaTeamProfile.builder() + .agendaTeam(team).agenda(agenda).profile(profile).build(); + + AgendaProfile newProfile = AgendaProfile.builder().intraId("newIntra").build(); + List updateTeamMates = new ArrayList<>(); + updateTeamMates.add(AgendaTeamMateReqDto.builder() + .intraId(profile.getIntraId()).build()); + updateTeamMates.add(AgendaTeamMateReqDto.builder() + .intraId(newProfile.getIntraId()).build()); + + AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates).build(); + + when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); + when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) + .thenReturn(List.of(participant)); + when(agendaProfileAdminRepository.findByIntraId("newIntra")) + .thenReturn(Optional.empty()); + + // expected + assertThrows(NotExistException.class, + () -> agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto)); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index cc9adb423..eed842862 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -21,6 +21,7 @@ import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.utils.exception.custom.BusinessException; +import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; @@ -89,9 +90,11 @@ public AgendaTeam(Agenda agenda, UUID teamKey, String name, String content, Stri this.isPrivate = isPrivate; } - public void acceptAward(String award, int awardPriority) { + public void acceptAward(String award, Integer awardPriority) { this.award = award; - this.awardPriority = awardPriority; + if (Objects.nonNull(awardPriority)) { + this.awardPriority = awardPriority; + } } public void confirm() { @@ -125,6 +128,13 @@ public void leaveTeamMate() { this.mateCount--; } + public void leaveTeamMateAdmin(String intraId) { + if (intraId.equals(this.leaderIntraId)) { + throw new ForbiddenException(NOT_TEAM_MATE); + } + this.mateCount--; + } + public void attendTeam(Agenda agenda) { if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); @@ -138,6 +148,13 @@ public void attendTeam(Agenda agenda) { this.mateCount++; } + public void attendTeamAdmin(Agenda agenda) { + if (this.mateCount >= agenda.getMaxPeople()) { + throw new BusinessException(AGENDA_TEAM_FULL); + } + this.mateCount++; + } + public void updateTeam(String name, String content, Boolean isPrivate, Location location, List profiles) { if (this.status == CANCEL) { @@ -152,6 +169,13 @@ public void updateTeam(String name, String content, Boolean isPrivate, Location updateLocation(location, profiles); } + public void updateTeamAdmin(String name, String content, Boolean isPrivate, AgendaTeamStatus status) { + this.name = name; + this.content = content; + this.isPrivate = isPrivate; + this.status = status; + } + public void updateLocation(Location location, List profiles) { if (Objects.isNull(location)) { return; diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java index c0c634bfe..d7af90700 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -30,7 +30,7 @@ public Agenda createAgenda() { .maxTeam(5) .currentTeam(0) .minPeople(1) - .maxPeople(5) + .maxPeople(6) .status(OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") @@ -74,7 +74,7 @@ public Agenda createAgenda(Location location) { .maxTeam(5) .currentTeam(0) .minPeople(1) - .maxPeople(5) + .maxPeople(6) .status(OPEN) .posterUri("posterUri") .hostIntraId("hostIntraId") diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java index f28925767..cd77a2f28 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java @@ -34,6 +34,19 @@ public AgendaProfile createAgendaProfile(User user, Location location) { return agendaProfileRepository.save(agendaProfile); } + public AgendaProfile createAgendaProfile() { + User user = testDataUtils.createNewUser(); + AgendaProfile agendaProfile = AgendaProfile.builder() + .content("content") + .githubUrl("githubUrl") + .coalition(LEE) + .location(Location.SEOUL) + .intraId(user.getIntraId()) + .userId(user.getId()) + .build(); + return agendaProfileRepository.save(agendaProfile); + } + public List createAgendaProfileList(int size) { List agendaProfileList = new ArrayList<>(); for (int i = 0; i < size; i++) { diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index d64b48137..e05f24020 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -16,11 +16,19 @@ import gg.data.agenda.type.Location; import gg.data.user.User; import gg.repo.agenda.AgendaTeamRepository; +import gg.utils.TestDataUtils; import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor public class AgendaTeamFixture { + + private final TestDataUtils testDataUtils; + + private final AgendaProfileFixture agendaProfileFixture; + + private final AgendaTeamProfileFixture agendaTeamProfileFixture; + private final AgendaTeamRepository agendaTeamRepository; public AgendaTeam createAgendaTeam(Agenda agenda) { @@ -32,7 +40,23 @@ public AgendaTeam createAgendaTeam(Agenda agenda) { .leaderIntraId("leaderIntraId") .status(OPEN) .location(MIX) - .mateCount(3) + .mateCount(1) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, User user) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId(user.getIntraId()) + .status(OPEN) + .location(MIX) + .mateCount(1) .awardPriority(1) .isPrivate(false) .build(); diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java index 6a0268cff..d647554c4 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java @@ -22,6 +22,7 @@ public AgendaTeamProfile createAgendaTeamProfile(Agenda agenda, AgendaTeam agend .profile(agendaProfile) .isExist(true) .build(); + agendaTeam.attendTeam(agenda); return agendaTeamProfileRepository.save(agendaTeamProfile); } From 7c5d7ad42b8b8d22637a589a70390108c7078c83 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:18:25 +0900 Subject: [PATCH 058/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Agenda?= =?UTF-8?q?=20Admin=20URL=20=EB=B3=80=EA=B2=BD=20#926=20(#927)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaAdminController.java | 2 +- .../AgendaAnnouncementAdminController.java | 2 +- .../controller/AgendaTeamAdminController.java | 2 +- .../controller/AgendaTeamController.java | 2 +- .../controller/AgendaAdminControllerTest.java | 40 +++++++++---------- ...AgendaAnnouncementAdminControllerTest.java | 16 ++++---- .../AgendaTeamAdminControllerTest.java | 30 +++++++------- .../agendateam/AgendaTeamControllerTest.java | 6 +-- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index 6c4f69ad7..45b66314d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -27,7 +27,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/admin/agenda") +@RequestMapping("/agenda/admin") @RequiredArgsConstructor public class AgendaAdminController { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java index ce3a00382..518e1132e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java @@ -25,7 +25,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/admin/agenda/announcement") +@RequestMapping("/agenda/admin/announcement") @RequiredArgsConstructor public class AgendaAnnouncementAdminController { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index 6efef42fb..5a97fa225 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -29,7 +29,7 @@ import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/admin/agenda/team") +@RequestMapping("/agenda/admin/team") @RequiredArgsConstructor public class AgendaTeamAdminController { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index c068fbf15..04dc71105 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -120,7 +120,7 @@ public ResponseEntity> openTeamList(@RequestBody @Valid Pag * 아젠다 팀 확정된 팀 목록 조회 * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ - @GetMapping("/confirm") + @GetMapping("/confirm/list") public ResponseEntity> confirmTeamList(@RequestBody @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index 31c7b95e9..4fefa5241 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -96,7 +96,7 @@ void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { String request = objectMapper.writeValueAsString(pageRequestDto); // when - String response = mockMvc.perform(get("/admin/agenda/request/list") + String response = mockMvc.perform(get("/agenda/admin/request/list") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON).content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); @@ -121,7 +121,7 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { String request = objectMapper.writeValueAsString(pageRequestDto); // when - String response = mockMvc.perform(get("/admin/agenda/request/list") + String response = mockMvc.perform(get("/agenda/admin/request/list") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON).content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); @@ -148,7 +148,7 @@ void updateAgendaAdminSuccessWithInformation() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -178,7 +178,7 @@ void updateAgendaAdminSuccessWithSchedule() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -202,7 +202,7 @@ void updateAgendaAdminSuccessWithLocationSeoulToMix() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -224,7 +224,7 @@ void updateAgendaAdminSuccessWithLocationSeoulToGyeongsan() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -246,7 +246,7 @@ void updateAgendaAdminSuccessWithLocationGyeongsanToSeoul() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + mockMvc.perform(patch("/agenda/admin/request").param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) .content(request)).andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -265,7 +265,7 @@ void updateAgendaAdminSuccessWithLocationGyeongsanToMix() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -288,7 +288,7 @@ void updateAgendaAdminSuccessWithAgendaCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -313,7 +313,7 @@ void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // when - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -335,7 +335,7 @@ void updateAgendaAdminFailedWithNoAgenda() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", UUID.randomUUID().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -352,7 +352,7 @@ void updateAgendaAdminFailedWithLocationSeoulToGyeongSan() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -369,7 +369,7 @@ void updateAgendaAdminFailedWithLocationGyeongSanToSeoul() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -387,7 +387,7 @@ void updateAgendaAdminFailedWithLocationMixToSeoul() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -405,7 +405,7 @@ void updateAgendaAdminFailedWithAgendaInvalidCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -423,7 +423,7 @@ void updateAgendaAdminFailedWithMaxTeam() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -441,7 +441,7 @@ void updateAgendaAdminFailedWithMinTeam() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -459,7 +459,7 @@ void updateAgendaAdminFailedWithAgendaTeamInvalidCapacity() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -477,7 +477,7 @@ void updateAgendaAdminFailedWithMaxPeople() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request") + mockMvc.perform(patch("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) @@ -496,7 +496,7 @@ void updateAgendaAdminFailedWithMinPeople() throws Exception { String request = objectMapper.writeValueAsString(agendaDto); // expected - mockMvc.perform(patch("/admin/agenda/request").param("agenda_key", agenda.getAgendaKey().toString()) + mockMvc.perform(patch("/agenda/admin/request").param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) .content(request)).andExpect(status().isBadRequest()); } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java index 02495a2fb..31904d27d 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java @@ -96,7 +96,7 @@ void getAgendaAnnouncementAdminSuccess(int page) throws Exception { String request = objectMapper.writeValueAsString(pageDto); // when - String response = mockMvc.perform(get("/admin/agenda/announcement") + String response = mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -125,7 +125,7 @@ void getAgendaAnnouncementAdminSuccessWithNoContent() throws Exception { String request = objectMapper.writeValueAsString(pageDto); // when - String response = mockMvc.perform(get("/admin/agenda/announcement") + String response = mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -148,7 +148,7 @@ void getAgendaAnnouncementAdminFailedWithNoAgenda() throws Exception { String request = objectMapper.writeValueAsString(pageDto); // expected - mockMvc.perform(get("/admin/agenda/announcement") + mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -172,7 +172,7 @@ void updateAgendaAnnouncementAdminSuccess() throws Exception { String request = objectMapper.writeValueAsString(updateReqDto); // when - mockMvc.perform(patch("/admin/agenda/announcement") + mockMvc.perform(patch("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -195,7 +195,7 @@ void updateAgendaAnnouncementAdminFailedWithNoAnnouncement() throws Exception { String request = objectMapper.writeValueAsString(updateReqDto); // expected - mockMvc.perform(patch("/admin/agenda/announcement") + mockMvc.perform(patch("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -213,7 +213,7 @@ void updateAgendaAnnouncementAdminFailedWithNoReqId() throws Exception { String request = objectMapper.writeValueAsString(updateReqDto); // expected - mockMvc.perform(patch("/admin/agenda/announcement") + mockMvc.perform(patch("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -232,7 +232,7 @@ void updateAgendaAnnouncementAdminFailedWithTooLongTitle() throws Exception { String request = objectMapper.writeValueAsString(updateReqDto); // expected - mockMvc.perform(patch("/admin/agenda/announcement") + mockMvc.perform(patch("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -251,7 +251,7 @@ void updateAgendaAnnouncementAdminFailedWithTooLongContent() throws Exception { String request = objectMapper.writeValueAsString(updateReqDto); // expected - mockMvc.perform(patch("/admin/agenda/announcement") + mockMvc.perform(patch("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index 6772aeb98..c6b44528f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -102,7 +102,7 @@ void setUp() { @Nested @DisplayName("Admin AgendaTeam 전체 조회") - class GetAgencyTeamListAdmin { + class GetAgendaTeamListAdmin { @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 5}) @@ -118,7 +118,7 @@ void getAgendaTeamListAdminSuccess(int page) throws Exception { String request = objectMapper.writeValueAsString(pageRequestDto); // when - String response = mockMvc.perform(get("/admin/agenda/team/list") + String response = mockMvc.perform(get("/agenda/admin/team/list") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -144,7 +144,7 @@ void getAgendaTeamListAdminFailedWithNoAgenda() throws Exception { String request = objectMapper.writeValueAsString(pageRequestDto); // expected - mockMvc.perform(get("/admin/agenda/team/list") + mockMvc.perform(get("/agenda/admin/team/list") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -168,7 +168,7 @@ void getAgendaTeamDetailAdminSuccess() throws Exception { String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); // when - String response = mockMvc.perform(get("/admin/agenda/team") + String response = mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -191,7 +191,7 @@ void getAgendaTeamDetailAdminSuccess() throws Exception { void getAgendaTeamDetailAdminFailedWithNoTeamKey() throws Exception { // given // expected - mockMvc.perform(get("/admin/agenda/team") + mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -203,7 +203,7 @@ void getAgendaTeamDetailAdminFailedWithNotFoundTeam() throws Exception { String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(UUID.randomUUID())); // expected - mockMvc.perform(get("/admin/agenda/team") + mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -219,7 +219,7 @@ void getAgendaTeamDetailAdminSuccessWithNoTeamMates() throws Exception { String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); // when - String response = mockMvc.perform(get("/admin/agenda/team") + String response = mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -258,7 +258,7 @@ void updateAgendaTeamAdminSuccess() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -297,7 +297,7 @@ void updateAgendaTeamAdminFailedWithLocation() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -338,7 +338,7 @@ void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -386,7 +386,7 @@ void updateAgendaTeamAdminFailedWithMaxPeople() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -416,7 +416,7 @@ void updateAgendaTeamAdminSuccessWithRemoveTeammate() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -465,7 +465,7 @@ void updateAgendaTeamAdminFailedWithRemoveLeader() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -493,7 +493,7 @@ void updateAgendaTeamAdminFailedWithInvalidTeamKey() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) @@ -522,7 +522,7 @@ void updateAgendaTeamAdminFailedWithInvalidIntraId() throws Exception { String request = objectMapper.writeValueAsString(updateDto); // when - mockMvc.perform(patch("/admin/agenda/team") + mockMvc.perform(patch("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 27f59a440..2cfa6ab40 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -1163,7 +1163,7 @@ public void confirmTeamGetSuccess(int page) throws Exception { String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( - get("/agenda/team/confirm") + get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .param("page", String.valueOf(page)) @@ -1189,7 +1189,7 @@ public void confirmTeamGetSuccessNoTeam() throws Exception { String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( - get("/agenda/team/confirm") + get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) @@ -1209,7 +1209,7 @@ public void noAgendaFail() throws Exception { String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - get("/agenda/team/confirm") + get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) .content(content) From 0b38a0f5743a08cbf7d8073b3b3aa0f8557205f2 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:44:12 +0900 Subject: [PATCH 059/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Agenda?= =?UTF-8?q?=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20#925=20(#928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 45 +++------- .../request/AgendaAwardsReqDto.java | 1 - .../request/AgendaCreateReqDto.java | 43 ++------- .../validator/AgendaCapacityValid.java | 21 +++++ .../validator/AgendaCapacityValidator.java | 29 ++++++ .../validator/AgendaScheduleValid.java | 21 +++++ .../validator/AgendaScheduleValidator.java | 29 ++++++ .../response/AgendaSimpleResDto.java | 4 +- .../user/agenda/service/AgendaService.java | 48 +++++----- .../service/AgendaAnnouncementService.java | 8 +- .../java/gg/agenda/api/AgendaMockData.java | 4 +- .../controller/AgendaControllerTest.java | 42 +++++---- .../dto/AgendaCreateReqDtoTest.java | 87 ------------------ .../agenda/service/AgendaServiceTest.java | 89 ++++--------------- .../AgendaAnnouncementServiceTest.java | 2 +- .../src/main/java/gg/data/agenda/Agenda.java | 68 +++++++------- .../gg/data/agenda/AgendaTeamProfile.java | 7 +- .../java/gg/repo/agenda/AgendaRepository.java | 2 +- .../agenda/AgendaTeamProfileRepository.java | 6 ++ 19 files changed, 246 insertions(+), 310 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValid.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValid.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index ce92e509f..8f44328af 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -1,9 +1,6 @@ package gg.agenda.api.user.agenda.controller; -import static gg.utils.exception.ErrorCode.*; - import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -32,11 +29,8 @@ import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaAnnouncement; import gg.utils.dto.PageRequestDto; -import gg.utils.exception.custom.InvalidParameterException; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @RestController @@ -49,20 +43,14 @@ public class AgendaController { private final AgendaAnnouncementService agendaAnnouncementService; @GetMapping - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Agenda 상세 조회 성공"), - @ApiResponse(responseCode = "400", description = "Agenda 조회 요청이 잘못됨"), - @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음") - }) public ResponseEntity agendaDetails(@RequestParam("agenda_key") UUID agendaKey) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); - Optional announcement = agendaAnnouncementService.findAgendaWithLatestAnnouncement(agenda); - String announcementTitle = announcement.map(AgendaAnnouncement::getTitle).orElse(""); + String announcementTitle = agendaAnnouncementService + .findLatestAnnounceTitleByAgendaOrDefault(agenda, ""); AgendaResDto agendaResDto = AgendaResDto.MapStruct.INSTANCE.toDto(agenda, announcementTitle); return ResponseEntity.ok(agendaResDto); } - @ApiResponse(responseCode = "200", description = "현재 진행중인 Agenda 목록 조회 성공") @GetMapping("/list") public ResponseEntity> agendaListCurrent() { List agendaList = agendaService.findCurrentAgendaList(); @@ -72,22 +60,16 @@ public ResponseEntity> agendaListCurrent() { return ResponseEntity.ok(agendaSimpleResDtoList); } - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Agenda 생성 성공"), - @ApiResponse(responseCode = "400", description = "Agenda 생성 요청 파라미터가 잘못됨") - }) - @PostMapping("/create") - public ResponseEntity agendaAdd(@Login UserDto user, + @PostMapping("/request") + public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, @RequestBody @Valid AgendaCreateReqDto agendaCreateReqDto) { UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, user).getAgendaKey(); AgendaKeyResDto responseDto = AgendaKeyResDto.builder().agendaKey(agendaKey).build(); return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); } - @ApiResponse(responseCode = "200", description = "지난 Agenda 목록 조회 성공") @GetMapping("/history") - public ResponseEntity> agendaListHistory( - @RequestBody @Valid PageRequestDto pageRequest) { + public ResponseEntity> agendaListHistory(@RequestBody @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); @@ -99,22 +81,23 @@ public ResponseEntity> agendaListHistory( } @PatchMapping("/finish") - public ResponseEntity agendaEndWithAwards(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user, - @RequestBody(required = false) @Valid AgendaAwardsReqDto agendaAwardsReqDto) { + public ResponseEntity agendaEndWithAwards(@RequestParam("agenda_key") UUID agendaKey, + @RequestBody @Valid AgendaAwardsReqDto agendaAwardsReqDto, @Login @Parameter(hidden = true) UserDto user) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); - if (agenda.getIsRanking() && agendaAwardsReqDto == null) { - throw new InvalidParameterException(AGENDA_INVALID_PARAM); + if (agenda.getIsRanking()) { + agendaService.awardAgenda(agendaAwardsReqDto, agenda); } - agendaService.finishAgendaWithAwards(agendaAwardsReqDto, agenda); + agendaService.finishAgenda(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @PatchMapping("/confirm") - public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, @Login UserDto user) { + public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agendaKey, + @Login @Parameter(hidden = true) UserDto user) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); - agendaService.confirmAgenda(agenda); + agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java index f32cb4efd..22f8492de 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaAwardsReqDto.java @@ -17,7 +17,6 @@ public class AgendaAwardsReqDto { @Valid @NotNull - @NotEmpty private List awards; @Builder diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index c6b4956e2..9bf148523 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -1,31 +1,30 @@ package gg.agenda.api.user.agenda.controller.request; -import static gg.utils.exception.ErrorCode.*; - import java.time.LocalDateTime; import javax.validation.constraints.Future; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -import org.mapstruct.BeforeMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import gg.agenda.api.user.agenda.controller.request.validator.AgendaCapacityValid; +import gg.agenda.api.user.agenda.controller.request.validator.AgendaScheduleValid; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.type.Location; -import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter +@AgendaCapacityValid +@AgendaScheduleValid @NoArgsConstructor(access = AccessLevel.PROTECTED) public class AgendaCreateReqDto { @@ -71,14 +70,11 @@ public class AgendaCreateReqDto { @NotNull private Boolean agendaIsRanking; - @NotNull - private Boolean agendaIsOfficial; - @Builder public AgendaCreateReqDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, Location agendaLocation, - Boolean agendaIsRanking, Boolean agendaIsOfficial) { + Boolean agendaIsRanking) { this.agendaTitle = agendaTitle; this.agendaContent = agendaContents; this.agendaDeadLine = agendaDeadLine; @@ -91,11 +87,11 @@ public AgendaCreateReqDto(String agendaTitle, String agendaContents, LocalDateTi this.agendaPoster = agendaPoster; this.agendaLocation = agendaLocation; this.agendaIsRanking = agendaIsRanking; - this.agendaIsOfficial = agendaIsOfficial; } @Mapper public interface MapStruct { + AgendaCreateReqDto.MapStruct INSTANCE = Mappers.getMapper(AgendaCreateReqDto.MapStruct.class); @Mapping(target = "id", ignore = true) @@ -112,32 +108,9 @@ public interface MapStruct { @Mapping(target = "posterUri", source = "dto.agendaPoster") @Mapping(target = "hostIntraId", source = "user.intraId") @Mapping(target = "location", source = "dto.agendaLocation") - @Mapping(target = "status", constant = "OPEN") - @Mapping(target = "isOfficial", source = "dto.agendaIsOfficial") @Mapping(target = "isRanking", source = "dto.agendaIsRanking") + @Mapping(target = "status", constant = "OPEN") + @Mapping(target = "isOfficial", constant = "false") Agenda toEntity(AgendaCreateReqDto dto, UserDto user); - - @BeforeMapping - default void mustHaveValidAgendaSchedule(AgendaCreateReqDto dto, UserDto user) { - if (!dto.getAgendaDeadLine().isBefore(dto.getAgendaStartTime())) { - throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); - } - if (!dto.getAgendaDeadLine().isBefore(dto.getAgendaEndTime())) { - throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); - } - if (!dto.getAgendaStartTime().isBefore(dto.getAgendaEndTime())) { - throw new InvalidParameterException(AGENDA_INVALID_SCHEDULE); - } - } - - @BeforeMapping - default void mustHaveValidParam(AgendaCreateReqDto dto, UserDto user) { - if (dto.getAgendaMinTeam() > dto.getAgendaMaxTeam()) { - throw new InvalidParameterException(AGENDA_INVALID_PARAM); - } - if (dto.getAgendaMinPeople() > dto.getAgendaMaxPeople()) { - throw new InvalidParameterException(AGENDA_INVALID_PARAM); - } - } } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValid.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValid.java new file mode 100644 index 000000000..4afb00eed --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValid.java @@ -0,0 +1,21 @@ +package gg.agenda.api.user.agenda.controller.request.validator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Constraint(validatedBy = AgendaCapacityValidator.class) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface AgendaCapacityValid { + + String message() default "올바르지 않은 대회 정보입니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java new file mode 100644 index 000000000..1f330e629 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agenda.controller.request.validator; + +import java.util.Objects; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; + +public class AgendaCapacityValidator implements ConstraintValidator { + + @Override + public void initialize(AgendaCapacityValid constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(AgendaCreateReqDto value, ConstraintValidatorContext context) { + if (Objects.isNull(value)) { + return true; + } + return mustHaveValidTeam(value); + } + + private boolean mustHaveValidTeam(AgendaCreateReqDto value) { + return value.getAgendaMinTeam() < value.getAgendaMaxTeam() + && value.getAgendaMinPeople() < value.getAgendaMaxPeople(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValid.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValid.java new file mode 100644 index 000000000..1bff895d6 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValid.java @@ -0,0 +1,21 @@ +package gg.agenda.api.user.agenda.controller.request.validator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Constraint(validatedBy = AgendaScheduleValidator.class) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface AgendaScheduleValid { + + String message() default "올바르지 않은 대회 일정입니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java new file mode 100644 index 000000000..79df23975 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agenda.controller.request.validator; + +import java.util.Objects; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; + +public class AgendaScheduleValidator implements ConstraintValidator { + + @Override + public void initialize(AgendaScheduleValid constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(AgendaCreateReqDto value, ConstraintValidatorContext context) { + if (Objects.isNull(value)) { + return true; + } + return mustHaveValidSchedule(value); + } + + private boolean mustHaveValidSchedule(AgendaCreateReqDto value) { + return value.getAgendaDeadLine().isBefore(value.getAgendaStartTime()) + && value.getAgendaStartTime().isBefore(value.getAgendaEndTime()); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java index a384da73c..09da2ec9b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java @@ -69,8 +69,8 @@ public interface MapStruct { @Mapping(target = "agendaEndTime", source = "endTime") @Mapping(target = "agendaCurrentTeam", source = "currentTeam") @Mapping(target = "agendaMaxTeam", source = "maxTeam") - @Mapping(target = "agendaMinPeople", source = "minTeam") - @Mapping(target = "agendaMaxPeople", source = "maxTeam") + @Mapping(target = "agendaMinPeople", source = "minPeople") + @Mapping(target = "agendaMaxPeople", source = "maxPeople") @Mapping(target = "agendaLocation", source = "location") @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "isOfficial", source = "isOfficial") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 9e51ce286..1d8695c54 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -3,9 +3,7 @@ import static gg.utils.exception.ErrorCode.*; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -52,11 +50,15 @@ public Agenda findAgendaByAgendaKey(UUID agendaKey) { @Transactional(readOnly = true) public List findCurrentAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN).stream() - .sorted(Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) - .thenComparing(Agenda::getDeadline, Comparator.reverseOrder())) + .sorted(agendaComparatorWithIsOfficialThenDeadline()) .collect(Collectors.toList()); } + private Comparator agendaComparatorWithIsOfficialThenDeadline() { + return Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) + .thenComparing(Agenda::getDeadline, Comparator.reverseOrder()); + } + @Transactional public Agenda addAgenda(AgendaCreateReqDto agendaCreateReqDto, UserDto user) { Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(agendaCreateReqDto, user); @@ -65,38 +67,38 @@ public Agenda addAgenda(AgendaCreateReqDto agendaCreateReqDto, UserDto user) { @Transactional(readOnly = true) public List findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(pageable, AgendaStatus.FINISH).getContent(); + return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, pageable).getContent(); } @Transactional - public void finishAgendaWithAwards(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { - if (!agenda.getIsRanking()) { - agenda.finish(); - return; - } - Map teams = new HashMap<>(); - agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.CONFIRM) - .forEach(team -> teams.put(team.getName(), team)); + public void finishAgenda(Agenda agenda) { + agenda.finishAgenda(); + } + + @Transactional + public void awardAgenda(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { + List teams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.CONFIRM); for (AgendaTeamAward agendaTeamAward : agendaAwardsReqDto.getAwards()) { - if (!teams.containsKey(agendaTeamAward.getTeamName())) { - throw new NotExistException(TEAM_NOT_FOUND); - } - teams.get(agendaTeamAward.getTeamName()) - .acceptAward(agendaTeamAward.getAwardName(), agendaTeamAward.getAwardPriority()); + AgendaTeam matchedTeam = teams.stream() + .filter(team -> team.getName().equals(agendaTeamAward.getTeamName())) + .findFirst() + .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + matchedTeam.acceptAward(agendaTeamAward.getAwardName(), agendaTeamAward.getAwardPriority()); } - agenda.finish(); } @Transactional - public void confirmAgenda(Agenda agenda) { + public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { List openTeams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); for (AgendaTeam openTeam : openTeams) { - openTeam.cancelTeam(); - List participants = agendaTeamProfileRepository.findAllByAgendaTeam(openTeam).stream() + // TODO: AgendaTeamService의 cancelTeam 메서드를 호출하는 것이 더 좋을 수도 있음 + List participants = agendaTeamProfileRepository + .findAllByAgendaTeamWithFetchProfile(openTeam).stream() .map(AgendaTeamProfile::getProfile) .collect(Collectors.toList()); ticketService.refundTickets(participants, agenda.getAgendaKey()); + openTeam.cancelTeam(); } - agenda.confirm(); + agenda.confirmAgenda(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java index 02057d1f9..c1d44087d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java @@ -32,7 +32,11 @@ public List findAnnouncementListByAgenda(Pageable pageable, } @Transactional(readOnly = true) - public Optional findAgendaWithLatestAnnouncement(Agenda agenda) { - return agendaAnnouncementRepository.findLatestByAgenda(agenda); + public String findLatestAnnounceTitleByAgendaOrDefault(Agenda agenda, String defaultTitle) { + Optional latestAnnounce = agendaAnnouncementRepository.findLatestByAgenda(agenda); + if (latestAnnounce.isEmpty()) { + return defaultTitle; + } + return latestAnnounce.get().getTitle(); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 7f382ec7b..e7167a5c1 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -739,8 +739,8 @@ public Agenda createAgendaWithTeamAndAgendaCapacityAndFinish(int teamCount, int } agenda.updateSchedule(LocalDateTime.now().minusDays(2), LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); - agenda.confirm(); - agenda.finish(); + agenda.confirmAgenda(); + agenda.finishAgenda(); em.persist(agenda); em.flush(); em.clear(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index d730d369f..6497c344d 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -119,6 +119,7 @@ void getAgendaSuccess() throws Exception { // then assertThat(result.getAgendaTitle()).isEqualTo(agenda.getTitle()); + assertThat(result.getAgendaMinPeople()).isEqualTo(agenda.getMinPeople()); assertThat(result.getAnnouncementTitle()).isEqualTo(announcement.getTitle()); } @@ -256,11 +257,11 @@ void createAgendaSuccess() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // when - String response = mockMvc.perform(post("/agenda/create") + String response = mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -273,6 +274,7 @@ void createAgendaSuccess() throws Exception { assertThat(agenda.isPresent()).isTrue(); assertThat(agenda.get().getTitle()).isEqualTo(dto.getAgendaTitle()); assertThat(agenda.get().getContent()).isEqualTo(dto.getAgendaContent()); + assertThat(agenda.get().getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); } @Test @@ -285,11 +287,11 @@ void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -306,11 +308,11 @@ void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(4)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -327,11 +329,11 @@ void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(7)) .agendaEndTime(LocalDateTime.now().plusDays(5)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -348,11 +350,11 @@ void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(7).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -369,11 +371,11 @@ void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(6).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -391,11 +393,11 @@ void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(value).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -413,11 +415,11 @@ void createAgendaFailedWhenNegativeMinPeople(int value) throws Exception { .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(value).agendaMaxPeople(5) - .agendaIsRanking(true).agendaIsOfficial(true).agendaLocation(Location.SEOUL).build(); + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); String request = objectMapper.writeValueAsString(dto); // expected - mockMvc.perform(post("/agenda/create") + mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) .contentType("application/json") .content(request)) @@ -658,13 +660,19 @@ void finishAgendaSuccessWithNoRanking() throws Exception { int teamSize = 10; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); + AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder() + .awards(List.of()) // 시상하지 않는 대회도 빈 리스트를 전송해야합니다. + .build(); IntStream.range(0, teamSize) .forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); + String request = objectMapper.writeValueAsString(agendaAwardsReqDto); // when mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken)) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) .andExpect(status().isNoContent()); Agenda result = em.createQuery("select a from Agenda a where a.agendaKey = :agendaKey", Agenda.class) .setParameter("agendaKey", agenda.getAgendaKey()).getSingleResult(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java index dfc42e846..c5e2afa5e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java @@ -36,91 +36,4 @@ void createAgendaSuccess() { assertNotNull(agenda); assertThat(agenda.getHostIntraId()).isEqualTo(user.getIntraId()); } - - @Nested - @DisplayName("Agenda 생성 실패") - class CreateAgendaFailed { - - @Test - @DisplayName("deadline이 start time보다 늦을 경우") - void createAgendaFailedWhenDeadlineIsBeforeStartTime() { - //given - UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(5)) - .agendaStartTime(LocalDateTime.now().plusDays(2)) - .agendaEndTime(LocalDateTime.now().plusDays(7)) - .build(); - - // expected - assertThrows(InvalidParameterException.class, - () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); - } - - @Test - @DisplayName("deadline이 end time보다 늦을 경우") - void createAgendaFailedWhenDeadlineIsBeforeEndTime() { - //given - UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(5)) - .agendaStartTime(LocalDateTime.now().plusDays(7)) - .agendaEndTime(LocalDateTime.now().plusDays(6)) - .build(); - - // expected - assertThrows(InvalidParameterException.class, - () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); - } - - @Test - @DisplayName("start time이 end time보다 늦을 경우") - void createAgendaFailedWhenStartTimeIsBeforeEndTime() { - //given - UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(3)) - .agendaStartTime(LocalDateTime.now().plusDays(6)) - .agendaEndTime(LocalDateTime.now().plusDays(5)) - .build(); - - // expected - assertThrows(InvalidParameterException.class, - () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); - } - - @Test - @DisplayName("min team이 max team보다 큰 경우") - void createAgendaFailedWhenMinTeamIsGreaterThanMaxTeam() { - //given - UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(3)) - .agendaStartTime(LocalDateTime.now().plusDays(6)) - .agendaEndTime(LocalDateTime.now().plusDays(8)) - .agendaMinTeam(5).agendaMaxTeam(2) - .build(); - - // expected - assertThrows(InvalidParameterException.class, - () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); - } - - @Test - @DisplayName("min people이 max people보다 큰 경우") - void createAgendaFailedWhenMinPeopleIsGreaterThanMaxPeople() { - //given - UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(3)) - .agendaStartTime(LocalDateTime.now().plusDays(6)) - .agendaEndTime(LocalDateTime.now().plusDays(8)) - .agendaMinPeople(5).agendaMaxPeople(2) - .build(); - - // expected - assertThrows(InvalidParameterException.class, - () -> AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user)); - } - } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 542b1962b..e16e76729 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -186,7 +186,7 @@ void getAgendaListHistorySuccess() { .build() )); Page agendaPage = new PageImpl<>(agendas.subList(0, 10), pageable, size); - when(agendaRepository.findAllByStatusIs(any(Pageable.class), eq(AgendaStatus.FINISH))) + when(agendaRepository.findAllByStatusIs(eq(AgendaStatus.FINISH), any(Pageable.class))) .thenReturn(agendaPage); // when @@ -194,7 +194,7 @@ void getAgendaListHistorySuccess() { // then verify(agendaRepository, times(1)) - .findAllByStatusIs(pageable, AgendaStatus.FINISH); + .findAllByStatusIs(AgendaStatus.FINISH, pageable); assertThat(result.size()).isEqualTo(size); for (int i = 1; i < result.size(); i++) { assertThat(result.get(i).getStartTime()) @@ -232,48 +232,11 @@ void finishAgendaSuccess() { .thenReturn(agendaTeams); // when - agendaService.finishAgendaWithAwards(confirmDto, agenda); + agendaService.awardAgenda(confirmDto, agenda); // then verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); - } - - @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회인 경우") - void finishAgendaSuccessWithNoRank() { - // given - Agenda agenda = Agenda.builder() - .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.CONFIRM).isRanking(false).build(); - - // when - agendaService.finishAgendaWithAwards(null, agenda); - - // then - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); - } - - @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 들어온 경우") - void finishAgendaSuccessWithNoRankAndAwards() { - // given - Agenda agenda = Agenda.builder() - .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.CONFIRM).isRanking(false).build(); - List agendaTeams = new ArrayList<>(); - IntStream.range(0, 10).forEach(i -> agendaTeams.add(AgendaTeam.builder().name("team" + i).build())); - AgendaTeamAward awardDto = AgendaTeamAward.builder() - .teamName("team1").awardName("award").awardPriority(1).build(); - AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() - .awards(List.of(awardDto)).build(); - - // when - agendaService.finishAgendaWithAwards(confirmDto, agenda); - - // then - verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); } @Test @@ -285,30 +248,15 @@ void finishAgendaSuccessWithNoRankAndEmptyAwards() { .status(AgendaStatus.CONFIRM).isRanking(false).build(); AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder() .awards(List.of()).build(); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any())) + .thenReturn(List.of()); // when - agendaService.finishAgendaWithAwards(confirmDto, agenda); - - // then - verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); - } - - @Test - @DisplayName("Agenda 시상 및 확정 성공 - 시상하지 않는 대회에 시상 내역이 null로 들어온 경우") - void finishAgendaSuccessWithNoRankAndNullAwards() { - // given - Agenda agenda = Agenda.builder() - .hostIntraId("intraId").startTime(LocalDateTime.now().minusDays(1)) - .status(AgendaStatus.CONFIRM).isRanking(false).build(); - AgendaAwardsReqDto confirmDto = AgendaAwardsReqDto.builder().build(); - - // when - agendaService.finishAgendaWithAwards(confirmDto, agenda); + agendaService.awardAgenda(confirmDto, agenda); // then - verify(agendaTeamRepository, never()).findAllByAgendaAndStatus(any(), any()); - assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.FINISH); + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(any(), any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); } @Test @@ -323,7 +271,7 @@ void finishAgendaFailedWithoutAwards() { // expected assertThrows(NullPointerException.class, - () -> agendaService.finishAgendaWithAwards(confirmDto, agenda)); + () -> agendaService.awardAgenda(confirmDto, agenda)); } @Test @@ -336,7 +284,7 @@ void finishAgendaFailedWithNullDto() { // expected assertThrows(NullPointerException.class, - () -> agendaService.finishAgendaWithAwards(null, agenda)); + () -> agendaService.awardAgenda(null, agenda)); } @Test @@ -356,7 +304,7 @@ void finishAgendaFailedWithInvalidTeam() { // expected assertThrows(NotExistException.class, - () -> agendaService.finishAgendaWithAwards(confirmDto, agenda)); + () -> agendaService.awardAgenda(confirmDto, agenda)); } } @@ -374,17 +322,16 @@ void confirmAgendaSuccess() { .profile(AgendaProfile.builder().build()).build(); when(agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN)) .thenReturn(List.of(agendaTeam)); - when(agendaTeamProfileRepository.findAllByAgendaTeam(agendaTeam)).thenReturn(List.of(participant)); + when(agendaTeamProfileRepository.findAllByAgendaTeamWithFetchProfile(agendaTeam)) + .thenReturn(List.of(participant)); doNothing().when(ticketService).refundTickets(any(), any()); - - // when - agendaService.confirmAgenda(agenda); + agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); // then verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); - verify(agendaTeamProfileRepository, times(1)).findAllByAgendaTeam(agendaTeam); + verify(agendaTeamProfileRepository, times(1)).findAllByAgendaTeamWithFetchProfile(agendaTeam); verify(ticketService, times(1)).refundTickets(any(), any()); assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); assertThat(agendaTeam.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); @@ -399,7 +346,7 @@ void confirmAgendaFailedWithNoAgenda() { .thenReturn(List.of()); // when - agendaService.confirmAgenda(agenda); + agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); // then verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); @@ -418,7 +365,7 @@ void confirmAgendaFailedWithAlreadyConfirm(AgendaStatus status) { // expected assertThrows(InvalidParameterException.class, - () -> agendaService.confirmAgenda(agenda)); + () -> agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda)); verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java index da6e81ef2..373ac6505 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java @@ -85,7 +85,7 @@ void getAgendaAnnouncementLatestSuccess() { when(agendaAnnouncementRepository.findLatestByAgenda(agenda)).thenReturn(Optional.of(announcement)); // when - agendaAnnouncementService.findAgendaWithLatestAnnouncement(agenda); + agendaAnnouncementService.findLatestAnnounceTitleByAgendaOrDefault(agenda, ""); // then verify(agendaAnnouncementRepository, times(1)).findLatestByAgenda(agenda); diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index fb4e2af7b..e6628745f 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -118,39 +118,7 @@ public Agenda(Long id, String title, String content, LocalDateTime deadline, this.isRanking = isRanking; } - public void addTeam(Location location, LocalDateTime now) { - mustBeWithinLocation(location); - mustStatusOnGoing(); - mustBeforeDeadline(now); - mustHaveCapacity(); - } - - public void confirmTeam(Location location, LocalDateTime now) { - mustBeWithinLocation(location); - mustStatusOnGoing(); - mustBeforeDeadline(now); - mustHaveCapacity(); - this.currentTeam++; - } - - public void attendTeam(Location location, LocalDateTime now) { - mustBeWithinLocation(location); - mustStatusOnGoing(); - mustBeforeDeadline(now); - } - - public void updateTeam(Location location, LocalDateTime now) { - mustBeWithinLocation(location); - mustStatusOnGoing(); - mustBeforeDeadline(now); - } - - public void cancelTeam(LocalDateTime now) { - mustStatusOnGoing(); - mustBeforeDeadline(now); - } - - public void confirm() { + public void confirmAgenda() { if (this.status == AgendaStatus.FINISH) { throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); } @@ -163,7 +131,7 @@ public void confirm() { this.status = AgendaStatus.CONFIRM; } - public void finish() { + public void finishAgenda() { if (this.status == AgendaStatus.OPEN) { throw new InvalidParameterException(AGENDA_DOES_NOT_CONFIRM); } @@ -260,6 +228,38 @@ public void updateAgendaTeamCapacity(int minPeople, int maxPeople, List { @Query("SELECT a FROM Agenda a WHERE a.status = :status") List findAllByStatusIs(AgendaStatus status); - Page findAllByStatusIs(Pageable pageable, AgendaStatus status); + Page findAllByStatusIs(AgendaStatus status, Pageable pageable); Optional findAgendaByAgendaKey(UUID usedTo); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 60df26164..13db6dd79 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -21,7 +21,13 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + /** + * 해당 메서드는 N+1 문제가 발생할 수 있습니다. + */ List findAllByAgendaTeam(AgendaTeam agendaTeam); + @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.profile WHERE atp.agendaTeam = :agendaTeam") + List findAllByAgendaTeamWithFetchProfile(AgendaTeam agendaTeam); + List findByProfile(AgendaProfile agendaProfile); } From 15020903fa8932f1b9d29776b6cef5ea7bbcbfdd Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:05:29 +0900 Subject: [PATCH 060/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Team=20Create=20?= =?UTF-8?q?API=20=EC=BF=BC=EB=A6=AC=20=EC=9E=98=EB=AA=BB=20=EC=93=B4=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20Open=20Te?= =?UTF-8?q?am=20Coaliton=20=EC=B6=94=EA=B0=80=20#930=20(#931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/response/OpenTeamResDto.java | 7 ++++++- .../agendateam/service/AgendaTeamService.java | 16 +++++++++++----- .../repo/agenda/AgendaTeamProfileRepository.java | 2 ++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java index b28162e31..d7970a91f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/OpenTeamResDto.java @@ -1,6 +1,9 @@ package gg.agenda.api.user.agendateam.controller.response; +import java.util.List; + import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Coalition; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,11 +14,13 @@ public class OpenTeamResDto { private String teamLeaderIntraId; private int teamMateCount; private String teamKey; + private List coalitions; - public OpenTeamResDto(AgendaTeam agendaTeam) { + public OpenTeamResDto(AgendaTeam agendaTeam, List coalitions) { this.teamName = agendaTeam.getName(); this.teamLeaderIntraId = agendaTeam.getLeaderIntraId(); this.teamMateCount = agendaTeam.getMateCount(); this.teamKey = agendaTeam.getTeamKey().toString(); + this.coalitions = coalitions; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 3e7730926..76219b795 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -130,9 +130,10 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD throw new BusinessException(LOCATION_NOT_VALID); } - agendaTeamProfileRepository.findByAgendaProfileAndIsExistTrue(agenda, agendaProfile).ifPresent(teamProfile -> { - throw new DuplicationException(AGENDA_TEAM_FORBIDDEN); - }); + agendaTeamProfileRepository.findByAgendaAndProfileAndIsExistTrue(agenda, agendaProfile) + .ifPresent(teamProfile -> { + throw new DuplicationException(AGENDA_TEAM_FORBIDDEN); + }); if (agenda.getIsOfficial()) { Ticket ticket = ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(agendaProfile) @@ -224,11 +225,16 @@ public void agendaTeamLeave(UserDto user, UUID agendaKey, UUID teamKey) { public List listOpenTeam(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - List agendaTeams = agendaTeamRepository.findByAgendaAndStatusAndIsPrivateFalse(agenda, OPEN, pageable).getContent(); return agendaTeams.stream() - .map(OpenTeamResDto::new) + .map(agendaTeam -> { + List coalitions = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam).stream() + .map(agendaTeamProfile -> agendaTeamProfile.getProfile().getCoalition()) + .collect(Collectors.toList()); + return new OpenTeamResDto(agendaTeam, coalitions); + }) .collect(Collectors.toList()); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 13db6dd79..4ec01ed7d 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -19,6 +19,8 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agenda = :agenda AND atp.profile = :agendaProfile " + + "AND atp.isExist = true") Optional findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); /** From 39962d830b325957a18fc61cf14211362b552bd5 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:51:26 +0900 Subject: [PATCH 061/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20AgendaCreateReqD?= =?UTF-8?q?to=20Custom=20Validtor=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20#934=20(#935)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/request/AgendaCreateReqDto.java | 8 +++++--- .../request/validator/AgendaScheduleValidator.java | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index 9bf148523..7c335cb5c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -12,6 +12,8 @@ import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import com.fasterxml.jackson.annotation.JsonFormat; + import gg.agenda.api.user.agenda.controller.request.validator.AgendaCapacityValid; import gg.agenda.api.user.agenda.controller.request.validator.AgendaScheduleValid; import gg.auth.UserDto; @@ -34,15 +36,15 @@ public class AgendaCreateReqDto { @NotBlank private String agendaContent; - @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Future(message = "마감일은 현재 시간 이후여야 합니다.") private LocalDateTime agendaDeadLine; - @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Future(message = "시작 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaStartTime; - @NotNull + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") @Future(message = "종료 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaEndTime; diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java index 79df23975..d87e92b36 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaScheduleValidator.java @@ -19,6 +19,11 @@ public boolean isValid(AgendaCreateReqDto value, ConstraintValidatorContext cont if (Objects.isNull(value)) { return true; } + if (Objects.isNull(value.getAgendaDeadLine()) + || Objects.isNull(value.getAgendaStartTime()) + || Objects.isNull(value.getAgendaEndTime())) { + return false; + } return mustHaveValidSchedule(value); } From 688aea49f73447e56ff68014c36d3e69993e3758 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:00:31 +0900 Subject: [PATCH 062/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20AgendaCreateReqD?= =?UTF-8?q?to=20DateTImeFormat=20=EC=88=98=EC=A0=95=20#936=20(#937)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/agenda/controller/request/AgendaCreateReqDto.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index 7c335cb5c..e3b213819 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -36,15 +36,15 @@ public class AgendaCreateReqDto { @NotBlank private String agendaContent; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @NotNull @Future(message = "마감일은 현재 시간 이후여야 합니다.") private LocalDateTime agendaDeadLine; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @NotNull @Future(message = "시작 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaStartTime; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + @NotNull @Future(message = "종료 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaEndTime; From 942146f0c1b257888e66843bb4a3ff0085a00958 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:20:58 +0900 Subject: [PATCH 063/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EB=82=B4?= =?UTF-8?q?=EA=B0=80=20=EC=B0=B8=EC=97=AC=20=ED=96=88=EB=8D=98=20=EB=8C=80?= =?UTF-8?q?=ED=9A=8C=20=EB=B3=B4=EA=B8=B0=20API=20#855=20(#932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 28 ++++- .../response/AttendedAgendaListResDto.java | 44 +++++++ .../service/AgendaProfileFindService.java | 27 ++++- .../AgendaProfileControllerTest.java | 109 +++++++++++++++++- .../agenda/AgendaTeamProfileRepository.java | 17 ++- 5 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 7b81f2bb6..8299b2353 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -4,11 +4,13 @@ import javax.validation.Valid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,13 +19,14 @@ import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.user.type.RoleType; -import gg.repo.user.UserRepository; +import gg.utils.dto.PageRequestDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -33,8 +36,6 @@ public class AgendaProfileController { private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileService agendaProfileService; - private final UserRepository userRepository; - private static final Logger log = LoggerFactory.getLogger(AgendaProfileController.class); /** * AgendaProfile 상세 조회 API @@ -92,5 +93,22 @@ public ResponseEntity> getCurrentAttendAgend intraId); return ResponseEntity.ok(currentAttendAgendaList); } + + /** + * 과거에 참여했던 Agenda 목록 조회하는 메서드 + * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 + */ + @GetMapping("/history/list") + public ResponseEntity> getAttendedAgendaList( + @Login @Parameter(hidden = true) UserDto user, @ModelAttribute @Valid PageRequestDto pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + String intraId = user.getIntraId(); + + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + List attendedAgendaList = agendaProfileFindService.findAttendedAgenda( + intraId, pageable); + return ResponseEntity.ok(attendedAgendaList); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java new file mode 100644 index 000000000..bce775205 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java @@ -0,0 +1,44 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import gg.agenda.api.user.agendateam.controller.response.TeamMateDto; +import gg.data.agenda.AgendaTeamProfile; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AttendedAgendaListResDto { + private String agendaId; + private String agendaTitle; + private LocalDateTime agendaStartTime; + private LocalDateTime agendaEndTime; + private int agendaCurrentTeam; + private String agendaLocation; + private UUID teamKey; + private Boolean isOfficial; + private int agendaMaxPeople; + private String teamName; + private List teamMates; + + public AttendedAgendaListResDto(AgendaTeamProfile agendaTeamProfile, + List agendaTeamProfileList) { + this.agendaId = agendaTeamProfile.getAgenda().getId().toString(); + this.agendaTitle = agendaTeamProfile.getAgenda().getTitle(); + this.agendaStartTime = agendaTeamProfile.getAgenda().getStartTime(); + this.agendaEndTime = agendaTeamProfile.getAgenda().getEndTime(); + this.agendaCurrentTeam = agendaTeamProfile.getAgenda().getCurrentTeam(); + this.agendaLocation = agendaTeamProfile.getAgenda().getLocation().toString(); + this.teamKey = agendaTeamProfile.getAgendaTeam().getTeamKey(); + this.isOfficial = agendaTeamProfile.getAgenda().getIsOfficial(); + this.agendaMaxPeople = agendaTeamProfile.getAgenda().getMaxPeople(); + this.teamName = agendaTeamProfile.getAgendaTeam().getName(); + this.teamMates = agendaTeamProfileList.stream() + .map(TeamMateDto::new) + .collect(Collectors.toList()); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index df78c24d2..c1b13349c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -5,10 +5,13 @@ import java.util.List; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; @@ -57,7 +60,7 @@ public List findCurrentAttendAgenda(String intraI AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - List agendaTeamProfiles = agendaTeamProfileRepository.findByProfile( + List agendaTeamProfiles = agendaTeamProfileRepository.findByProfileAndIsExistTrue( agendaProfile); return agendaTeamProfiles.stream() @@ -68,4 +71,26 @@ public List findCurrentAttendAgenda(String intraI .map(CurrentAttendAgendaListResDto::new) .collect(Collectors.toList()); } + + /** + * 자기가 참여했던 Agenda 목록 조회하는 메서드 + * @param intraId,pageable 페이지네이션 요청 정보, 로그인한 유저의 id + */ + @Transactional(readOnly = true) + public List findAttendedAgenda(String intraId, Pageable pageable) { + AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + Page agendaTeamProfilePage = + agendaTeamProfileRepository.findByProfileAndIsExistTrueAndAgendaStatus( + agendaProfile, AgendaStatus.FINISH, pageable); + + return agendaTeamProfilePage.getContent().stream() + .map(agendaTeamProfile -> { + List agendaTeamProfiles = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeamProfile.getAgendaTeam()); + return new AttendedAgendaListResDto(agendaTeamProfile, agendaTeamProfiles); + }) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 3c73d4fdf..f7e62f5dc 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; @@ -26,10 +28,12 @@ import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; +import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; import gg.data.user.User; @@ -37,6 +41,7 @@ import gg.repo.agenda.AgendaProfileRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; @IntegrationTest @Transactional @@ -243,7 +248,7 @@ void test() throws Exception { @Nested @DisplayName("내가 참여 중인 대회 보기") - class GetCurrentAttendAgenda { + class GetCurrentAttendAgendaList { @BeforeEach void beforeEach() { user = testDataUtils.createNewUser(); @@ -331,6 +336,108 @@ void testAgendaTeamNotFound() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("내가 과거에 참여했던 대회 보기") + class GetAttendedAgendaList { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3}) + @DisplayName("200 내가 참여 했었던 대회 조회 성공") + void getAttendedAgendaListSuccess(int page) throws Exception { + // given + int size = 10; + int total = 25; + agendaProfile = agendaMockData.createAgendaProfile(user, Location.SEOUL); + List attendedAgendas = new ArrayList<>(); + for (int i = 0; i < total; i++) { + User agendaCreateUser = testDataUtils.createNewUser(); + User otherUser = testDataUtils.createNewUser(); + LocalDateTime startTime = LocalDateTime.now().minusDays(i + 1); + Agenda agenda = agendaMockData.createAgenda(agendaCreateUser.getIntraId(), startTime, + AgendaStatus.FINISH); + AgendaTeam agendaTeam = agendaMockData.createAgendaTeam(agenda, otherUser, Location.SEOUL); + attendedAgendas.add(agendaMockData.createAgendaTeamProfile(agendaTeam, agendaProfile)); + } + + PageRequestDto pageRequest = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequest); + + // when + String response = mockMvc.perform(get("/agenda/profile/history/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size)) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + AttendedAgendaListResDto[] result = objectMapper.readValue(response, AttendedAgendaListResDto[].class); + + // then + assertThat(result).hasSize(size * page < total ? size : total % size); + attendedAgendas.sort((o1, o2) -> Long.compare(o2.getAgenda().getId(), o1.getAgenda().getId())); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getAgendaId()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgenda().getId().toString()); + assertThat(result[i].getAgendaTitle()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgenda().getTitle()); + assertThat(result[i].getAgendaLocation()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgenda().getLocation().toString()); + assertThat(result[i].getTeamKey()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgendaTeam().getTeamKey()); + assertThat(result[i].getIsOfficial()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgenda().getIsOfficial()); + assertThat(result[i].getTeamName()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgendaTeam().getName()); + } + } + + @Test + @DisplayName("200 내가 참여 했었던 대회가 없을 때 조회 성공") + void getAttendedAgendaListSuccessNoAgenda() throws Exception { + // given + agendaProfile = agendaMockData.createAgendaProfile(user, Location.SEOUL); + PageRequestDto pageRequest = new PageRequestDto(1, 10); + String request = objectMapper.writeValueAsString(pageRequest); + + // when + String response = mockMvc.perform(get("/agenda/profile/history/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", "1") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + AttendedAgendaListResDto[] result = objectMapper.readValue(response, AttendedAgendaListResDto[].class); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("400 잘못된 페이지 요청 시 실패") + void getAttendedAgendaListBadRequest() throws Exception { + // given + PageRequestDto pageRequest = new PageRequestDto(0, 10); + String request = objectMapper.writeValueAsString(pageRequest); + + // when & then + mockMvc.perform(get("/agenda/profile/history/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index 4ec01ed7d..b41f2f702 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -3,13 +3,17 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; +import gg.data.agenda.type.AgendaStatus; public interface AgendaTeamProfileRepository extends JpaRepository { @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam.agenda = :agenda " @@ -31,5 +35,16 @@ public interface AgendaTeamProfileRepository extends JpaRepository findAllByAgendaTeamWithFetchProfile(AgendaTeam agendaTeam); - List findByProfile(AgendaProfile agendaProfile); + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.profile = :agendaProfile AND atp.isExist = true") + List findByProfileAndIsExistTrue(@Param("agendaProfile") AgendaProfile agendaProfile); + + @Query( + "SELECT atp FROM AgendaTeamProfile atp " + + "WHERE atp.profile = :agendaProfile " + + "AND atp.isExist = true " + + "AND atp.agenda.status = :status" + ) + Page findByProfileAndIsExistTrueAndAgendaStatus( + @Param("agendaProfile") AgendaProfile agendaProfile, + @Param("status") AgendaStatus status, Pageable pageable); } From 48703b88ea652f4591e345abd8a2386f4c507de4 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:19:21 +0900 Subject: [PATCH 064/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug][Refactoring]=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=99=80=20=EB=8B=A4=EB=A5=B8=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20GetMapping?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Body=EB=B0=9B=EB=8A=94=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95=20#930=20(#933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 6 +- .../user/agenda/service/AgendaService.java | 14 +- .../AgendaAnnouncementController.java | 3 +- .../controller/AgendaTeamController.java | 22 +- .../agendateam/service/AgendaTeamService.java | 82 +++++--- .../ticket/controller/TicketController.java | 4 +- .../user/ticket/service/TicketService.java | 19 +- .../controller/AgendaControllerTest.java | 188 ++++++++---------- .../agenda/service/AgendaServiceTest.java | 11 +- .../AgendaAnnouncementControllerTest.java | 26 +-- .../agendateam/AgendaTeamControllerTest.java | 46 ++--- .../api/user/ticket/TicketControllerTest.java | 44 ++-- .../src/main/java/gg/data/agenda/Agenda.java | 14 +- .../src/main/java/gg/data/agenda/Ticket.java | 8 +- .../java/gg/repo/agenda/TicketRepository.java | 3 +- 15 files changed, 222 insertions(+), 268 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 8f44328af..41030fd98 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -12,6 +12,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -47,7 +48,7 @@ public ResponseEntity agendaDetails(@RequestParam("agenda_key") UU Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); String announcementTitle = agendaAnnouncementService .findLatestAnnounceTitleByAgendaOrDefault(agenda, ""); - AgendaResDto agendaResDto = AgendaResDto.MapStruct.INSTANCE.toDto(agenda, announcementTitle); + AgendaResDto agendaResDto = AgendaResDto.MapStruct.INSTANCE.toDto(agenda, announcementTitle); return ResponseEntity.ok(agendaResDto); } @@ -69,7 +70,8 @@ public ResponseEntity agendaAdd(@Login @Parameter(hidden = true } @GetMapping("/history") - public ResponseEntity> agendaListHistory(@RequestBody @Valid PageRequestDto pageRequest) { + public ResponseEntity> agendaListHistory( + @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 1d8695c54..517764664 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -11,16 +11,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; +import gg.agenda.api.user.agendateam.service.AgendaTeamService; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; -import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; import gg.repo.agenda.AgendaRepository; @@ -41,6 +39,8 @@ public class AgendaService { private final TicketService ticketService; + private final AgendaTeamService agendaTeamService; + @Transactional(readOnly = true) public Agenda findAgendaByAgendaKey(UUID agendaKey) { return agendaRepository.findByAgendaKey(agendaKey) @@ -91,13 +91,7 @@ public void awardAgenda(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { List openTeams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); for (AgendaTeam openTeam : openTeams) { - // TODO: AgendaTeamService의 cancelTeam 메서드를 호출하는 것이 더 좋을 수도 있음 - List participants = agendaTeamProfileRepository - .findAllByAgendaTeamWithFetchProfile(openTeam).stream() - .map(AgendaTeamProfile::getProfile) - .collect(Collectors.toList()); - ticketService.refundTickets(participants, agenda.getAgendaKey()); - openTeam.cancelTeam(); + agendaTeamService.leaveTeamAll(openTeam); } agenda.confirmAgenda(); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java index d7fb6d70e..0696f12c5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java @@ -12,6 +12,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -48,7 +49,7 @@ public ResponseEntity agendaAnnouncementAdd(@Login UserDto user, @RequestP @GetMapping public ResponseEntity> agendaAnnouncementList( - @RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid PageRequestDto pageRequest) { + @RequestParam("agenda_key") UUID agendaKey, @ModelAttribute @Valid PageRequestDto pageRequest) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); int page = pageRequest.getPage(); int size = pageRequest.getSize(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 04dc71105..b31eb29c3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agendateam.controller; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -12,6 +13,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -30,6 +32,7 @@ import gg.agenda.api.user.agendateam.service.AgendaTeamService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaTeam; import gg.utils.dto.PageRequestDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -47,8 +50,7 @@ public class AgendaTeamController { */ @GetMapping("/my") public ResponseEntity> myTeamSimpleDetails( - @Parameter(hidden = true) @Login UserDto user, - @RequestParam("agenda_key") UUID agendaKey) { + @Parameter(hidden = true) @Login UserDto user, @RequestParam("agenda_key") UUID agendaKey) { Optional myTeamSimpleResDto = agendaTeamService.detailsMyTeamSimple(user, agendaKey); if (myTeamSimpleResDto.isEmpty()) { return ResponseEntity.noContent().build(); @@ -98,7 +100,15 @@ public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto @PatchMapping("/cancel") public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login UserDto user, @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - agendaTeamService.agendaTeamLeave(user, agendaKey, teamKeyReqDto.getTeamKey()); + UUID teamKey = teamKeyReqDto.getTeamKey(); + + AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(agendaKey, teamKey); + agendaTeam.getAgenda().leaveTeam(LocalDateTime.now()); + if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + agendaTeamService.leaveTeamAll(agendaTeam); + } else { + agendaTeamService.leaveTeamMate(agendaTeam, user); + } return ResponseEntity.noContent().build(); } @@ -106,8 +116,8 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use * 아젠다 팀 공개 모집인 팀 목록 조회 * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ - @GetMapping("/open") - public ResponseEntity> openTeamList(@RequestBody @Valid PageRequestDto pageRequest, + @GetMapping("/open/list") + public ResponseEntity> openTeamList(@ModelAttribute @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); @@ -121,7 +131,7 @@ public ResponseEntity> openTeamList(@RequestBody @Valid Pag * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ @GetMapping("/confirm/list") - public ResponseEntity> confirmTeamList(@RequestBody @Valid PageRequestDto pageRequest, + public ResponseEntity> confirmTeamList(@ModelAttribute @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 76219b795..63e207f66 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; @@ -136,7 +137,8 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD }); if (agenda.getIsOfficial()) { - Ticket ticket = ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(agendaProfile) + Ticket ticket = ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + agendaProfile) .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); ticket.useTicket(agenda.getAgendaKey()); } @@ -179,42 +181,58 @@ public void confirmTeam(UserDto user, UUID agendaKey, UUID teamKey) { } /** - * 아젠다 팀 나가기 - * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 - * 트랜잭션의 원자성을 보장하기 위해 팀 나가기와 티켓 환불을 한 메서드에서 처리 + * 아젠다 팀 찾기 + * @param agendaKey 아젠다 키, teamKey 팀 키 */ - @Transactional - public void agendaTeamLeave(UserDto user, UUID agendaKey, UUID teamKey) { + @Transactional(readOnly = true) + public AgendaTeam getAgendaTeam(UUID agendaKey, UUID teamKey) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - - AgendaTeam agendaTeam = agendaTeamRepository + return agendaTeamRepository .findByAgendaAndTeamKeyAndStatus(agenda, teamKey, OPEN, CONFIRM) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); + } - agenda.cancelTeam(LocalDateTime.now()); + /** + * 아젠다 팀원 나가기 + * @param agendaTeam 아젠다 팀, user 사용자 정보 + */ + @Transactional + public void leaveTeamMate(AgendaTeam agendaTeam, UserDto user) { + AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + AgendaTeamProfile agendaTeamProfile = agendaTeamProfileRepository + .findByAgendaAndProfileAndIsExistTrue(agendaTeam.getAgenda(), agendaProfile) + .orElseThrow(() -> new ForbiddenException(NOT_TEAM_MATE)); + leaveTeam(agendaTeamProfile); + } - List profiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); + /** + * 팀원이 팀 나가기 + * @param agendaTeamProfile 팀 프로필 + */ + @Transactional(propagation = Propagation.MANDATORY) + public void leaveTeam(AgendaTeamProfile agendaTeamProfile) { + AgendaTeam agendaTeam = agendaTeamProfile.getAgendaTeam(); + agendaTeamProfile.leaveTeam(); + agendaTeam.leaveTeamMate(); + if (agendaTeamProfile.getAgenda().getIsOfficial()) { + ticketService.refundTicket(agendaTeamProfile); + } + } - List changedProfiles; + /** + * 팀장이 팀 나가기 + * @param agendaTeam 팀 + */ + @Transactional(propagation = Propagation.MANDATORY) + public void leaveTeamAll(AgendaTeam agendaTeam) { + List teamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); - if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { - changedProfiles = profiles - .stream() - .peek(AgendaTeamProfile::leaveTeam) - .map(AgendaTeamProfile::getProfile) - .collect(Collectors.toList()); - agendaTeam.leaveTeamLeader(); - ticketService.refundTickets(changedProfiles, agendaKey); - return; + for (AgendaTeamProfile teamProfile : teamProfiles) { + leaveTeam(teamProfile); } - AgendaTeamProfile teamMateProfile = profiles.stream() - .filter(profile -> profile.getProfile().getUserId().equals(user.getId())) - .findFirst().orElseThrow(() -> new ForbiddenException(NOT_TEAM_MATE)); - teamMateProfile.leaveTeam(); - agendaTeam.leaveTeamMate(); - changedProfiles = List.of(teamMateProfile.getProfile()); - ticketService.refundTickets(changedProfiles, agendaKey); + agendaTeam.leaveTeamLeader(); } /** @@ -276,17 +294,19 @@ public void modifyAttendTeam(UserDto user, TeamKeyReqDto teamKeyReqDto, UUID age .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); - Ticket ticket = ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(agendaProfile) - .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); - agendaTeamProfileRepository.findByAgendaAndProfileAndIsExistTrue(agenda, agendaProfile) .ifPresent(profile -> { throw new ForbiddenException(AGENDA_TEAM_FORBIDDEN); }); + if (agenda.getIsOfficial()) { + Ticket ticket = ticketRepository + .findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc(agendaProfile) + .orElseThrow(() -> new ForbiddenException(TICKET_NOT_EXIST)); + ticket.useTicket(agenda.getAgendaKey()); + } agenda.attendTeam(agendaProfile.getLocation(), LocalDateTime.now()); agendaTeam.attendTeam(agenda); - ticket.useTicket(agenda.getAgendaKey()); agendaTeamProfileRepository.save(new AgendaTeamProfile(agendaTeam, agenda, agendaProfile)); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 66d89071e..c800d2be0 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -10,8 +10,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; 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; @@ -52,7 +52,7 @@ public ResponseEntity ticketCountFind(@Parameter(hidden = tru @GetMapping("/history") public ResponseEntity> ticketHistoryList(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid PageRequestDto pageRequest) { + @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 38ae0a987..ac9dc43e2 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -2,10 +2,8 @@ import static gg.utils.exception.ErrorCode.*; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; import org.springframework.data.domain.Page; @@ -18,6 +16,7 @@ import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; @@ -33,21 +32,9 @@ public class TicketService { private final AgendaRepository agendaRepository; private final AgendaProfileRepository agendaProfileRepository; - /** - * 티켓 환불 - * @param changedProfiles 변경된 프로필 목록 - * @param agendaKey 아젠다 키 - * @Annotation 트랜잭션의 원자성을 보장하기 위해 부모 트랜잭션이 없을경우 예외를 발생시키는 Propagation.MANDATORY로 설정 - */ @Transactional(propagation = Propagation.MANDATORY) - public void refundTickets(List changedProfiles, UUID agendaKey) { - List tickets = new ArrayList<>(); - for ( - AgendaProfile profile : changedProfiles) { - Ticket ticket = Ticket.createRefundedTicket(profile, agendaKey); - tickets.add(ticket); - } - ticketRepository.saveAll(tickets); + public void refundTicket(AgendaTeamProfile changedTeamProfile) { + Ticket.createRefundedTicket(changedTeamProfile); } /** diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 6497c344d..9e765fa55 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -439,14 +439,12 @@ void getAgendaListHistorySuccess(int page) throws Exception { int totalCount = 35; int size = 10; List agendaHistory = agendaMockData.createAgendaHistory(totalCount); - PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String req = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/history") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(req)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); @@ -467,14 +465,12 @@ void getAgendaListHistoryWithNoContent() throws Exception { // given int page = 1; int size = 10; - PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String req = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/history") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(req)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); @@ -523,14 +519,12 @@ void getAgendaListHistoryWithExcessPage(int page) throws Exception { int totalCount = 35; int size = 10; List agendaHistory = agendaMockData.createAgendaHistory(totalCount); - PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String req = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/history") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(req)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); @@ -561,14 +555,11 @@ void getAgendaListHistoryWithoutSize() throws Exception { // given int page = 1; List agendaHistory = agendaMockData.createAgendaHistory(30); - PageRequestDto pageRequestDto = new PageRequestDto(page, null); - String req = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/history") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(req)) + .param("page", String.valueOf(page))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); @@ -596,13 +587,13 @@ void finishAgendaSuccess() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -638,13 +629,13 @@ void finishAgendaFailedWithNoAwards() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) + .awardPriority(i + 1).build()) + .collect(Collectors.toList()); // expected mockMvc.perform(patch("/agenda/finish") @@ -661,7 +652,7 @@ void finishAgendaSuccessWithNoRanking() throws Exception { Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder() - .awards(List.of()) // 시상하지 않는 대회도 빈 리스트를 전송해야합니다. + .awards(List.of()) // 시상하지 않는 대회도 빈 리스트를 전송해야합니다. .build(); IntStream.range(0, teamSize) .forEach(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)); @@ -689,13 +680,13 @@ void finishAgendaSuccessWithNoRankAndAwards() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) + .awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -731,13 +722,11 @@ void finishAgendaFailedWithInvalidTeam() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); awards.add(AgendaTeamAward.builder() .teamName("invalid_team").awardName("prize").awardPriority(1).build()); // invalid team @@ -759,13 +748,12 @@ void finishAgendaFailedWithNoAgenda() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) + .awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -830,13 +818,11 @@ void finishAgendaFailedNotHost() throws Exception { int awardSize = 3; User another = testDataUtils.createNewUser(); Agenda agenda = agendaMockData.createAgenda(another.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -857,13 +843,11 @@ void finishAgendaFailedAlreadyConfirm() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), AgendaStatus.FINISH); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -884,13 +868,11 @@ void finishAgendaFailedAlreadyCancel() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), AgendaStatus.CANCEL); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -911,13 +893,11 @@ void finishAgendaFailedBeforeStartTime() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().plusDays(1), AgendaStatus.OPEN); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -937,13 +917,11 @@ void finishAgendaFailedWithEmptyAwardName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardName("").awardPriority(awardSize).build()); // empty award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -965,13 +943,11 @@ void finishAgendaFailedWithNullAwardName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardPriority(awardSize).build()); // null award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -993,13 +969,11 @@ void finishAgendaFailedWithEmptyTeamName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .teamName("").awardPriority(awardSize).build()); // empty award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -1021,13 +995,11 @@ void finishAgendaFailedWithNullTeamName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize) - .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize) - .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData + .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); + List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() + .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .awardPriority(awardSize).build()); // null award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index e16e76729..439f26e17 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -28,6 +28,7 @@ import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; +import gg.agenda.api.user.agendateam.service.AgendaTeamService; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.data.agenda.Agenda; @@ -57,6 +58,9 @@ class AgendaServiceTest { @Mock AgendaTeamProfileRepository agendaTeamProfileRepository; + @Mock + AgendaTeamService agendaTeamService; + @Mock TicketService ticketService; @@ -322,19 +326,14 @@ void confirmAgendaSuccess() { .profile(AgendaProfile.builder().build()).build(); when(agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN)) .thenReturn(List.of(agendaTeam)); - when(agendaTeamProfileRepository.findAllByAgendaTeamWithFetchProfile(agendaTeam)) - .thenReturn(List.of(participant)); - doNothing().when(ticketService).refundTickets(any(), any()); + doNothing().when(agendaTeamService).leaveTeamAll(any()); // when agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); // then verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); - verify(agendaTeamProfileRepository, times(1)).findAllByAgendaTeamWithFetchProfile(agendaTeam); - verify(ticketService, times(1)).refundTickets(any(), any()); assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CONFIRM); - assertThat(agendaTeam.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); } @Test diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java index b5356c309..ed3c63080 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java @@ -1,13 +1,9 @@ package gg.agenda.api.user.agendaannouncement.controller; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.assertj.core.api.Assertions.*; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import java.awt.print.Pageable; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -26,7 +22,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; @@ -38,7 +33,6 @@ import gg.repo.agenda.AgendaAnnouncementRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; -import gg.utils.dto.PageRequestDto; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -226,15 +220,13 @@ void getAgendaAnnouncementListSuccess(int page) throws Exception { agendaMockData.createAgendaAnnouncementList(agenda, 30, false); List announcements = agendaMockData .createAgendaAnnouncementList(agenda, total, true); - PageRequestDto pageRequest = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequest); // when String response = mockMvc.perform(get("/agenda/announcement") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); @@ -260,15 +252,13 @@ void getAgendaAnnouncementListSuccessWhenNoEntity() throws Exception { int size = 10; Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); agendaMockData.createAgendaAnnouncementList(agenda, 30, false); - PageRequestDto pageRequest = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequest); // when String response = mockMvc.perform(get("/agenda/announcement") .param("agenda_key", agenda.getAgendaKey().toString()) .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); @@ -285,15 +275,13 @@ void getAgendaAnnouncementListFailedWithInvalidAgenda() throws Exception { Agenda agenda = agendaMockData.createAgenda(user.getIntraId()); agendaMockData.createAgendaAnnouncementList(agenda, 30, false); agendaMockData.createAgendaAnnouncementList(agenda, 30, true); - PageRequestDto pageRequest = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequest); // when mockMvc.perform(get("/agenda/announcement") .param("agenda_key", UUID.randomUUID().toString()) .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isNotFound()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 2cfa6ab40..3f568e298 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -883,7 +883,8 @@ public void leaveTeamMateSuccess() throws Exception { AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); - ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtp.getProfile()) .ifPresent(ticket -> { assertThat(ticket.getUsedTo()).isNull(); }); @@ -917,11 +918,13 @@ public void leaveTeamLeaderSuccess() throws Exception { AgendaTeamProfile updatedAtpLeader = agendaTeamProfileRepository.findById(atpLeader.getId()).orElse(null); assert updatedAtpLeader != null; assertThat(updatedAtpLeader.getIsExist()).isFalse(); - ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtp.getProfile()) + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtp.getProfile()) .ifPresent(ticket -> { assertThat(ticket.getUsedTo()).isNull(); }); - ticketRepository.findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(updatedAtpLeader.getProfile()) + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtpLeader.getProfile()) .ifPresent(ticket -> { assertThat(ticket.getUsedTo()).isNull(); }); @@ -1080,15 +1083,13 @@ public void openTeamGetSuccess(int page) throws Exception { Agenda agenda = agendaMockData.createAgenda(SEOUL); List teams = agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.OPEN); PageRequestDto req = new PageRequestDto(page, 5); - String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( - get("/agenda/team/open") + get("/agenda/team/open/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .param("page", String.valueOf(page)) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); // then @@ -1106,14 +1107,13 @@ public void openTeamGetSuccessNoTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( - get("/agenda/team/open") + get("/agenda/team/open/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); // then @@ -1126,14 +1126,13 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - get("/agenda/team/open") + get("/agenda/team/open/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isNotFound()); } } @@ -1160,15 +1159,13 @@ public void confirmTeamGetSuccess(int page) throws Exception { Agenda agenda = agendaMockData.createAgenda(SEOUL); List teams = agendaMockData.createAgendaTeamList(agenda, 23, AgendaTeamStatus.CONFIRM); PageRequestDto req = new PageRequestDto(page, 5); - String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .param("page", String.valueOf(page)) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); // then @@ -1192,8 +1189,8 @@ public void confirmTeamGetSuccessNoTeam() throws Exception { get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); // then @@ -1206,14 +1203,13 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( get("/agenda/team/confirm/list") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) - .content(content) - .contentType(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isNotFound()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index 9e3cc5a79..24d53a91a 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -212,15 +212,13 @@ void findTicketHistorySuccess(int page) throws Exception { ticketFixture.createTicket(seoulUserAgendaProfile); } PageRequestDto req = new PageRequestDto(page, 5); - String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("page", String.valueOf(page)) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -234,14 +232,12 @@ void findTicketHistorySuccessToNotApprove() throws Exception { //given ticketFixture.createTicket(seoulUserAgendaProfile, false, false, null, null); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -257,16 +253,13 @@ void findTicketHistorySuccessToUsed() throws Exception { Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, true, null, seoulAgenda.getAgendaKey()); - ticketRepository.save(ticket); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -282,16 +275,14 @@ void findTicketHistorySuccessToNotUsed() throws Exception { Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, false, null, null); - ticketRepository.save(ticket); PageRequestDto req = new PageRequestDto(1, 5); String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -307,16 +298,13 @@ void findTicketHistorySuccessToRefund() throws Exception { Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); Ticket ticket = ticketFixture.createTicket(seoulUserAgendaProfile, true, false, seoulAgenda.getAgendaKey(), null); - ticketRepository.save(ticket); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -330,14 +318,12 @@ void findTicketHistorySuccessToRefund() throws Exception { void findTicketHistorySuccessToEmptyTicket() throws Exception { //given PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); //when String res = mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + seoulUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); //then @@ -351,14 +337,12 @@ void findTicketHistoryFailToNotFoundProfile() throws Exception { User notExistUser = testDataUtils.createNewUser(); String notExistUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notExistUser); PageRequestDto req = new PageRequestDto(1, 5); - String content = objectMapper.writeValueAsString(req); //when mockMvc.perform( get("/agenda/ticket/history") .header("Authorization", "Bearer " + notExistUserAccessToken) - .content(content) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) .andExpect(status().isNotFound()); } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index e6628745f..514609fa9 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -230,14 +230,14 @@ public void updateAgendaTeamCapacity(int minPeople, int maxPeople, List { - Optional findByAgendaProfileAndIsApprovedTrueAndIsUsedFalse(AgendaProfile agendaProfile); + Optional findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + AgendaProfile agendaProfile); List findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(Long agendaProfileId); From 0d39476102086f98892c70644a39e3560649b5f5 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:28:46 +0900 Subject: [PATCH 065/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20#929=20(#938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +- .../controller/AgendaAdminController.java | 18 +- .../request/AgendaAdminUpdateReqDto.java | 19 +- .../agenda/service/AgendaAdminService.java | 29 +- .../agenda/controller/AgendaController.java | 14 +- .../request/AgendaCreateReqDto.java | 26 +- .../user/agenda/service/AgendaService.java | 31 +- .../controller/AgendaAdminControllerTest.java | 262 ++++++++++------ .../service/AgendaAdminServiceTest.java | 93 ++++-- .../AgendaTeamAdminControllerTest.java | 2 +- .../controller/AgendaControllerTest.java | 291 ++++++++++++------ .../dto/AgendaCreateReqDtoTest.java | 2 +- .../agenda/service/AgendaServiceTest.java | 27 +- .../src/main/java/gg/data/agenda/Agenda.java | 11 +- .../service/CustomOAuth2UserService.java | 3 +- .../utils/aws/AsyncNewItemImageUploader.java | 24 +- .../utils/aws/AsyncNewUserImageUploader.java | 43 ++- .../utils/{ => aws}/ItemImageHandler.java | 8 +- .../utils/{ => aws}/UserImageHandler.java | 10 +- .../controller/ItemAdminControllerTest.java | 14 +- .../user/controller/UserControllerTest.java | 15 +- gg-utils/build.gradle | 3 + .../java/gg/utils/exception/ErrorCode.java | 3 + .../java/gg/utils/file}/FileDownloader.java | 5 +- .../gg/utils/file}/ImageResizingUtil.java | 2 +- .../gg/utils/file}/JpegMultipartFile.java | 2 +- .../utils/file/handler/AwsImageHandler.java | 112 +++++++ .../gg/utils/file/handler/ImageHandler.java | 12 + .../converter/MultiValueMapConverter.java | 33 ++ .../utils/fixture/agenda/AgendaFixture.java | 22 ++ .../fixture/agenda/AgendaTeamFixture.java | 4 + 31 files changed, 821 insertions(+), 322 deletions(-) rename gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/{ => aws}/ItemImageHandler.java (92%) rename gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/{ => aws}/UserImageHandler.java (91%) rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils => gg-utils/src/main/java/gg/utils/file}/FileDownloader.java (94%) rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils => gg-utils/src/main/java/gg/utils/file}/ImageResizingUtil.java (96%) rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils => gg-utils/src/main/java/gg/utils/file}/JpegMultipartFile.java (96%) create mode 100644 gg-utils/src/main/java/gg/utils/file/handler/AwsImageHandler.java create mode 100644 gg-utils/src/main/java/gg/utils/file/handler/ImageHandler.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/converter/MultiValueMapConverter.java diff --git a/build.gradle b/build.gradle index 64fa269c5..e4e230955 100644 --- a/build.gradle +++ b/build.gradle @@ -112,6 +112,8 @@ subprojects { "**/aws/*", "*NotiMailSender*", '*SlackbotService*', + "**/file/*", + "*AwsImageHandler*" ] //커버리지 리포트 생성 @@ -323,4 +325,3 @@ project(':gg-utils') { dependencies { } } - diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index 45b66314d..00c5a6ed4 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -1,6 +1,9 @@ package gg.agenda.api.admin.agenda.controller; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -12,16 +15,19 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +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.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; import gg.agenda.api.admin.agenda.service.AgendaAdminService; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.custom.InvalidParameterException; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; @@ -51,10 +57,14 @@ public ResponseEntity> agendaList(@RequestBody @Valid Pa @ApiResponse(responseCode = "409", description = "Agenda 지역을 변경할 수 없음"), @ApiResponse(responseCode = "409", description = "Agenda 팀 제한을 변경할 수 없음"), @ApiResponse(responseCode = "409", description = "Agenda 팀 인원 제한을 변경할 수 없음")}) - @PatchMapping("/request") + @PostMapping("/request") public ResponseEntity agendaUpdate(@RequestParam("agenda_key") UUID agendaKey, - @RequestBody @Valid AgendaAdminUpdateReqDto agendaDto) { - agendaAdminService.updateAgenda(agendaKey, agendaDto); + @ModelAttribute @Valid AgendaAdminUpdateReqDto agendaDto, + @RequestParam(required = false) MultipartFile agendaPoster) { + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024 * 2) { // 2MB + throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); + } + agendaAdminService.updateAgenda(agendaKey, agendaDto, agendaPoster); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java index 5f21a5930..692339c82 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java @@ -1,11 +1,9 @@ package gg.agenda.api.admin.agenda.controller.request; +import java.net.URL; import java.time.LocalDateTime; -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import org.springframework.format.annotation.DateTimeFormat; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; @@ -20,9 +18,9 @@ public class AgendaAdminUpdateReqDto { private String agendaTitle; - private String agendaContents; + private String agendaContent; - private String agendaPoster; + private URL agendaPosterUri; private Boolean isOfficial; @@ -30,10 +28,13 @@ public class AgendaAdminUpdateReqDto { private AgendaStatus agendaStatus; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime agendaDeadLine; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime agendaStartTime; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime agendaEndTime; private Location agendaLocation; @@ -47,13 +48,13 @@ public class AgendaAdminUpdateReqDto { private int agendaMaxPeople; @Builder - public AgendaAdminUpdateReqDto(String agendaTitle, String agendaContents, String agendaPoster, Boolean isOfficial, + public AgendaAdminUpdateReqDto(String agendaTitle, String agendaContent, URL agendaPosterUri, Boolean isOfficial, Boolean isRanking, AgendaStatus agendaStatus, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, Location agendaLocation, int agendaMinTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople) { this.agendaTitle = agendaTitle; - this.agendaContents = agendaContents; - this.agendaPoster = agendaPoster; + this.agendaContent = agendaContent; + this.agendaPosterUri = agendaPosterUri; this.isOfficial = isOfficial; this.isRanking = isRanking; this.agendaStatus = agendaStatus; diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index f7c004244..3fb92b1ad 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -2,21 +2,30 @@ import static gg.utils.exception.ErrorCode.*; +import java.io.IOException; +import java.net.URL; import java.util.List; +import java.util.Objects; import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; +import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.ImageHandler; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class AgendaAdminService { @@ -25,19 +34,33 @@ public class AgendaAdminService { private final AgendaTeamAdminRepository agendaTeamAdminRepository; + private final ImageHandler imageHandler; + + @Value("${info.image.defaultUrl}") + private String defaultUri; + @Transactional(readOnly = true) public List getAgendaRequestList(Pageable pageable) { return agendaAdminRepository.findAll(pageable).getContent(); } @Transactional - public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto) { + public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto, MultipartFile agendaPoster) { Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); List teams = agendaTeamAdminRepository.findAllByAgenda(agenda); - agenda.updateInformation(agendaDto.getAgendaTitle(), agendaDto.getAgendaContents(), - agendaDto.getAgendaPoster()); + try { + if (Objects.nonNull(agendaPoster)) { + URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, agenda.getTitle(), defaultUri); + agenda.updatePosterUri(storedUrl.toString()); + } + } catch (IOException e) { + log.error("Failed to upload image", e); + throw new BusinessException(AGENDA_CREATE_FAILED); + } + + agenda.updateInformation(agendaDto.getAgendaTitle(), agendaDto.getAgendaContent()); agenda.updateIsOfficial(agendaDto.getIsOfficial()); agenda.updateIsRanking(agendaDto.getIsRanking()); agenda.updateAgendaStatus(agendaDto.getAgendaStatus()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 41030fd98..64e309b28 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -1,6 +1,9 @@ package gg.agenda.api.user.agenda.controller; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -19,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; @@ -31,6 +35,8 @@ import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.custom.InvalidParameterException; +import gg.utils.exception.user.UserImageLargeException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -63,8 +69,12 @@ public ResponseEntity> agendaListCurrent() { @PostMapping("/request") public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, - @RequestBody @Valid AgendaCreateReqDto agendaCreateReqDto) { - UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, user).getAgendaKey(); + @ModelAttribute @Valid AgendaCreateReqDto agendaCreateReqDto, + @RequestParam(required = false) MultipartFile agendaPoster) { + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024 * 2) { // 2MB + throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); + } + UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, agendaPoster, user).getAgendaKey(); AgendaKeyResDto responseDto = AgendaKeyResDto.builder().agendaKey(agendaKey).build(); return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index e3b213819..9fb013196 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agenda.controller.request; +import java.net.URL; import java.time.LocalDateTime; import javax.validation.constraints.Future; @@ -11,12 +12,12 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; import gg.agenda.api.user.agenda.controller.request.validator.AgendaCapacityValid; import gg.agenda.api.user.agenda.controller.request.validator.AgendaScheduleValid; -import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.type.Location; import lombok.AccessLevel; @@ -37,14 +38,17 @@ public class AgendaCreateReqDto { private String agendaContent; @NotNull + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Future(message = "마감일은 현재 시간 이후여야 합니다.") private LocalDateTime agendaDeadLine; @NotNull + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Future(message = "시작 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaStartTime; @NotNull + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Future(message = "종료 시간은 현재 시간 이후여야 합니다.") private LocalDateTime agendaEndTime; @@ -64,7 +68,7 @@ public class AgendaCreateReqDto { @Max(100) private int agendaMaxPeople; - private String agendaPoster; + private URL agendaPosterUri; @NotNull private Location agendaLocation; @@ -73,12 +77,12 @@ public class AgendaCreateReqDto { private Boolean agendaIsRanking; @Builder - public AgendaCreateReqDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + public AgendaCreateReqDto(String agendaTitle, String agendaContent, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, - int agendaMinPeople, int agendaMaxPeople, String agendaPoster, Location agendaLocation, + int agendaMinPeople, int agendaMaxPeople, URL agendaPosterUri, Location agendaLocation, Boolean agendaIsRanking) { this.agendaTitle = agendaTitle; - this.agendaContent = agendaContents; + this.agendaContent = agendaContent; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; this.agendaEndTime = agendaEndTime; @@ -86,11 +90,15 @@ public AgendaCreateReqDto(String agendaTitle, String agendaContents, LocalDateTi this.agendaMaxTeam = agendaMaxTeam; this.agendaMinPeople = agendaMinPeople; this.agendaMaxPeople = agendaMaxPeople; - this.agendaPoster = agendaPoster; + this.agendaPosterUri = agendaPosterUri; this.agendaLocation = agendaLocation; this.agendaIsRanking = agendaIsRanking; } + public void updatePosterUri(URL storedUrl) { + this.agendaPosterUri = storedUrl; + } + @Mapper public interface MapStruct { @@ -107,12 +115,12 @@ public interface MapStruct { @Mapping(target = "currentTeam", constant = "0") @Mapping(target = "minPeople", source = "dto.agendaMinPeople") @Mapping(target = "maxPeople", source = "dto.agendaMaxPeople") - @Mapping(target = "posterUri", source = "dto.agendaPoster") - @Mapping(target = "hostIntraId", source = "user.intraId") + @Mapping(target = "hostIntraId", source = "userIntraId") @Mapping(target = "location", source = "dto.agendaLocation") @Mapping(target = "isRanking", source = "dto.agendaIsRanking") @Mapping(target = "status", constant = "OPEN") @Mapping(target = "isOfficial", constant = "false") - Agenda toEntity(AgendaCreateReqDto dto, UserDto user); + @Mapping(target = "posterUri", source = "dto.agendaPosterUri") + Agenda toEntity(AgendaCreateReqDto dto, String userIntraId); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 517764664..7e761cb1e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -2,14 +2,18 @@ import static gg.utils.exception.ErrorCode.*; +import java.io.IOException; +import java.net.URL; import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import gg.agenda.api.user.agenda.controller.request.AgendaAwardsReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; @@ -24,9 +28,14 @@ import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; +import gg.utils.exception.custom.BusinessException; +import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.ImageHandler; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class AgendaService { @@ -41,6 +50,11 @@ public class AgendaService { private final AgendaTeamService agendaTeamService; + private final ImageHandler imageHandler; + + @Value("${info.image.defaultUrl}") + private String defaultUri; + @Transactional(readOnly = true) public Agenda findAgendaByAgendaKey(UUID agendaKey) { return agendaRepository.findByAgendaKey(agendaKey) @@ -60,9 +74,16 @@ private Comparator agendaComparatorWithIsOfficialThenDeadline() { } @Transactional - public Agenda addAgenda(AgendaCreateReqDto agendaCreateReqDto, UserDto user) { - Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(agendaCreateReqDto, user); - return agendaRepository.save(newAgenda); + public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster, UserDto user) { + try { + URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, createDto.getAgendaTitle(), defaultUri); + createDto.updatePosterUri(storedUrl); + Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(createDto, user.getIntraId()); + return agendaRepository.save(newAgenda); + } catch (IOException e) { + log.error("Failed to upload image for agenda poster", e); + throw new BusinessException(AGENDA_CREATE_FAILED); + } } @Transactional(readOnly = true) @@ -89,6 +110,10 @@ public void awardAgenda(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { @Transactional public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { + if (agenda.getCurrentTeam() < agenda.getMinTeam()) { + throw new ForbiddenException("팀이 모두 구성되지 않았습니다."); + } + List openTeams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); for (AgendaTeam openTeam : openTeams) { agendaTeamService.leaveTeamAll(openTeam); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index 4fefa5241..2ffafa170 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -6,6 +6,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -21,10 +22,14 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.MultiValueMap; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,7 +43,9 @@ import gg.data.user.User; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; +import gg.utils.file.handler.AwsImageHandler; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -65,6 +72,12 @@ public class AgendaAdminControllerTest { @Autowired AgendaAdminRepository agendaAdminRepository; + @Value("${info.image.defaultUrl}") + private String defaultUri; + + @MockBean + private AwsImageHandler imageHandler; + private User user; private String accessToken; @@ -140,27 +153,28 @@ class UpdateAgendaAdmin { @DisplayName("Admin Agenda 수정 및 삭제 성공 - 기본 정보") void updateAgendaAdminSuccessWithInformation() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); AgendaAdminUpdateReqDto agendaDto = - AgendaAdminUpdateReqDto.builder().agendaTitle("updated title").agendaContents("updated content") - .agendaPoster("updated poster").agendaStatus(FINISH).isOfficial(!agenda.getIsOfficial()) + AgendaAdminUpdateReqDto.builder().agendaTitle("updated title").agendaContent("updated content") + .agendaStatus(FINISH).isOfficial(!agenda.getIsOfficial()) .isRanking(!agenda.getIsRanking()).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then assert (updated.isPresent()); assertThat(updated.get().getTitle()).isEqualTo(agendaDto.getAgendaTitle()); - assertThat(updated.get().getContent()).isEqualTo(agendaDto.getAgendaContents()); - assertThat(updated.get().getPosterUri()).isEqualTo(agendaDto.getAgendaPoster()); + assertThat(updated.get().getContent()).isEqualTo(agendaDto.getAgendaContent()); assertThat(updated.get().getStatus()).isEqualTo(agendaDto.getAgendaStatus()); assertThat(updated.get().getIsOfficial()).isEqualTo(agendaDto.getIsOfficial()); assertThat(updated.get().getIsRanking()).isEqualTo(agendaDto.getIsRanking()); @@ -170,43 +184,57 @@ void updateAgendaAdminSuccessWithInformation() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - 스케쥴 정보") void updateAgendaAdminSuccessWithSchedule() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); - AgendaAdminUpdateReqDto agendaDto = - AgendaAdminUpdateReqDto.builder().agendaDeadLine(agenda.getDeadline().plusDays(1)) - .agendaStartTime(agenda.getStartTime().plusDays(1)).agendaEndTime(agenda.getEndTime().plusDays(1)) - .build(); - String request = objectMapper.writeValueAsString(agendaDto); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder() + .agendaDeadLine(agenda.getDeadline().plusDays(1)) + .agendaStartTime(agenda.getStartTime().plusDays(1)) + .agendaEndTime(agenda.getEndTime().plusDays(1)) + .build(); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); + params.get("agendaDeadLine").remove(0); + params.get("agendaDeadLine").add(agendaDto.getAgendaDeadLine().toString().substring(0, 19)); + params.get("agendaStartTime").remove(0); + params.get("agendaStartTime").add(agendaDto.getAgendaStartTime().toString().substring(0, 19)); + params.get("agendaEndTime").remove(0); + params.get("agendaEndTime").add(agendaDto.getAgendaEndTime().toString().substring(0, 19)); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then assert (updated.isPresent()); - assertThat(updated.get().getDeadline()).isEqualTo(agendaDto.getAgendaDeadLine()); - assertThat(updated.get().getStartTime()).isEqualTo(agendaDto.getAgendaStartTime()); - assertThat(updated.get().getEndTime()).isEqualTo(agendaDto.getAgendaEndTime()); + assertThat(updated.get().getDeadline()) + .isEqualTo(agendaDto.getAgendaDeadLine().toString().substring(0, 19)); + assertThat(updated.get().getStartTime()) + .isEqualTo(agendaDto.getAgendaStartTime().toString().substring(0, 19)); + assertThat(updated.get().getEndTime()) + .isEqualTo(agendaDto.getAgendaEndTime().toString().substring(0, 19)); } @Test @DisplayName("Admin Agenda 수정 및 삭제 성공 - 서울 대회를 MIX로 변경") void updateAgendaAdminSuccessWithLocationSeoulToMix() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); // SEOUL AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(MIX).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -219,16 +247,18 @@ void updateAgendaAdminSuccessWithLocationSeoulToMix() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - 서울 대회를 경산으로 변경") void updateAgendaAdminSuccessWithLocationSeoulToGyeongsan() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgenda(); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -241,14 +271,19 @@ void updateAgendaAdminSuccessWithLocationSeoulToGyeongsan() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - 경산 대회를 서울로 변경") void updateAgendaAdminSuccessWithLocationGyeongsanToSeoul() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgenda(GYEONGSAN); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isNoContent()); + mockMvc.perform(multipart("/agenda/admin/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .params(params) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); // then @@ -260,16 +295,18 @@ void updateAgendaAdminSuccessWithLocationGyeongsanToSeoul() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - 경산 대회를 MIX로 변경") void updateAgendaAdminSuccessWithLocationGyeongsanToMix() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamGyeongsan(10); // SEOUL AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(MIX).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -282,17 +319,19 @@ void updateAgendaAdminSuccessWithLocationGyeongsanToMix() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - Agenda 팀 제한 정보") void updateAgendaAdminSuccessWithAgendaCapacity() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam() + 1) .agendaMaxTeam(agenda.getMaxTeam() + 1).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -306,18 +345,20 @@ void updateAgendaAdminSuccessWithAgendaCapacity() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 성공 - Agenda 팀 허용 인원 제한 정보") void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(agenda.getMinPeople() + 1) .agendaMaxPeople(agenda.getMaxPeople() + 1).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // when - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNoContent()); Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); @@ -331,15 +372,17 @@ void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 대회가 존재하지 않는 경우") void updateAgendaAdminFailedWithNoAgenda() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", UUID.randomUUID().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isNotFound()); } @@ -347,16 +390,18 @@ void updateAgendaAdminFailedWithNoAgenda() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 서울 대회를 경산으로 변경할 수 없는 경우") void updateAgendaAdminFailedWithLocationSeoulToGyeongSan() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeam(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(GYEONGSAN).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -364,16 +409,18 @@ void updateAgendaAdminFailedWithLocationSeoulToGyeongSan() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 경산 대회를 서울 대회로 변경할 수 없는 경우") void updateAgendaAdminFailedWithLocationGyeongSanToSeoul() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamGyeongsan(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(SEOUL).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -382,16 +429,18 @@ void updateAgendaAdminFailedWithLocationGyeongSanToSeoul() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 혼합 대회를 다른 지역 대회로 변경할 수 없는 경우") void updateAgendaAdminFailedWithLocationMixToSeoul() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamMix(10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(SEOUL).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") + .params(params) .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -399,17 +448,19 @@ void updateAgendaAdminFailedWithLocationMixToSeoul() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 maxTeam 이상의 팀이 존재하는 경우") void updateAgendaAdminFailedWithAgendaInvalidCapacity() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacity(10, 2, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(10).agendaMaxTeam(2).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -417,17 +468,19 @@ void updateAgendaAdminFailedWithAgendaInvalidCapacity() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 maxTeam 이상의 팀이 존재하는 경우") void updateAgendaAdminFailedWithMaxTeam() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacity(10, 2, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam()) .agendaMaxTeam(agenda.getMaxTeam() - 5).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -435,17 +488,19 @@ void updateAgendaAdminFailedWithMaxTeam() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 종료한 대회에 minTeam 이하의 팀이 참여한 경우") void updateAgendaAdminFailedWithMinTeam() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaCapacityAndFinish(5, 5, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinTeam(agenda.getMinTeam() + 2) .agendaMaxTeam((agenda.getMaxTeam())).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -453,17 +508,19 @@ void updateAgendaAdminFailedWithMinTeam() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - minPeople이 maxPeople보다 큰 경우") void updateAgendaAdminFailedWithAgendaTeamInvalidCapacity() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacity(10, 2, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(10).agendaMaxPeople(2).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -471,17 +528,19 @@ void updateAgendaAdminFailedWithAgendaTeamInvalidCapacity() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 팀에 maxPeople 이상의 인원이 참여한 경우") void updateAgendaAdminFailedWithMaxPeople() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithTeamAndAgendaTeamCapacity(10, 2, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(agenda.getMinPeople()) .agendaMaxPeople(agenda.getMaxPeople() - 5).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request") + mockMvc.perform(multipart("/agenda/admin/request") .param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .params(params) + .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isBadRequest()); } @@ -489,16 +548,21 @@ void updateAgendaAdminFailedWithMaxPeople() throws Exception { @DisplayName("Admin Agenda 수정 및 삭제 실패 - 이미 확정된 팀에 minPeople 이하의 인원이 참여한 경우") void updateAgendaAdminFailedWithMinPeople() throws Exception { // given + URL mockUrl = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); Agenda agenda = agendaMockData.createAgendaWithStatusAndTeamWithAgendaTeamCapacity(OPEN, 10, 3, 10); AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaMinPeople(5).agendaMaxPeople(agenda.getMaxPeople()).build(); - String request = objectMapper.writeValueAsString(agendaDto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); // expected - mockMvc.perform(patch("/agenda/admin/request").param("agenda_key", agenda.getAgendaKey().toString()) - .header("Authorization", "Bearer " + accessToken).contentType(MediaType.APPLICATION_JSON) - .content(request)).andExpect(status().isBadRequest()); + mockMvc.perform(multipart("/agenda/admin/request") + .param("agenda_key", agenda.getAgendaKey().toString()) + .params(params) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java index 23bc47250..1e8f37511 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.io.IOException; +import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -15,9 +17,11 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.mock.web.MockMultipartFile; import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; @@ -30,6 +34,7 @@ import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.InvalidParameterException; import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.AwsImageHandler; @UnitTest public class AgendaAdminServiceTest { @@ -40,6 +45,9 @@ public class AgendaAdminServiceTest { @Mock AgendaTeamAdminRepository agendaTeamAdminRepository; + @Mock + AwsImageHandler imageHandler; + @InjectMocks AgendaAdminService agendaAdminService; @@ -86,10 +94,14 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() { @DisplayName("Admin Agenda 수정") class UpdateAgenda { + private final String defaultUrl = "http://localhost:8080/images/image.jpeg"; + @Test @DisplayName("Admin Agenda 수정 성공 - 기본 정보") - void updateAgendaSuccessWithInformation() { + void updateAgendaSuccessWithInformation() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -98,21 +110,22 @@ void updateAgendaSuccessWithInformation() { Agenda agenda = Agenda.builder().title("title").content("content").posterUri("posterUri").isOfficial(false) .isRanking(true).status(AgendaStatus.FINISH).build(); AgendaAdminUpdateReqDto agendaDto = - AgendaAdminUpdateReqDto.builder().agendaTitle("Updated title").agendaContents("Updated content") - .agendaPoster("Updated posterUri").isOfficial(true).isRanking(true) + AgendaAdminUpdateReqDto.builder().agendaTitle("Updated title").agendaContent("Updated content") + .isOfficial(true).isRanking(true) .agendaStatus(AgendaStatus.CANCEL).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // when - agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); verify(agendaTeamAdminRepository, times(1)).findAllByAgenda(any()); assertThat(agenda.getTitle()).isEqualTo(agendaDto.getAgendaTitle()); - assertThat(agenda.getContent()).isEqualTo(agendaDto.getAgendaContents()); - assertThat(agenda.getPosterUri()).isEqualTo(agendaDto.getAgendaPoster()); + assertThat(agenda.getContent()).isEqualTo(agendaDto.getAgendaContent()); + assertThat(agenda.getPosterUri()).isEqualTo(defaultUrl); assertThat(agenda.getIsOfficial()).isEqualTo(agendaDto.getIsOfficial()); assertThat(agenda.getIsRanking()).isEqualTo(agendaDto.getIsRanking()); assertThat(agenda.getStatus()).isEqualTo(agendaDto.getAgendaStatus()); @@ -120,8 +133,10 @@ void updateAgendaSuccessWithInformation() { @Test @DisplayName("Admin Agenda 수정 성공 - 스케줄 정보") - void updateAgendaSuccessWithSchedule() { + void updateAgendaSuccessWithSchedule() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -135,9 +150,10 @@ void updateAgendaSuccessWithSchedule() { .build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // when - agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); @@ -149,8 +165,10 @@ void updateAgendaSuccessWithSchedule() { @Test @DisplayName("Admin Agenda 수정 성공 - 지역 정보") - void updateAgendaSuccessWithLocation() { + void updateAgendaSuccessWithLocation() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -160,9 +178,10 @@ void updateAgendaSuccessWithLocation() { AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder().agendaLocation(Location.MIX).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // when - agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); @@ -172,8 +191,10 @@ void updateAgendaSuccessWithLocation() { @Test @DisplayName("Admin Agenda 수정 성공 - Agenda 팀 제한 정보") - void updateAgendaSuccessWithAgendaCapacity() { + void updateAgendaSuccessWithAgendaCapacity() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -184,9 +205,10 @@ void updateAgendaSuccessWithAgendaCapacity() { AgendaAdminUpdateReqDto.builder().agendaMinTeam(2).agendaMaxTeam(20).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // when - agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); @@ -197,8 +219,10 @@ void updateAgendaSuccessWithAgendaCapacity() { @Test @DisplayName("Admin Agenda 수정 성공 - Agenda 팀 인원 제한 정보") - void updateAgendaSuccessWithAgendaTeamCapacity() { + void updateAgendaSuccessWithAgendaTeamCapacity() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -209,9 +233,10 @@ void updateAgendaSuccessWithAgendaTeamCapacity() { AgendaAdminUpdateReqDto.builder().agendaMinPeople(2).agendaMaxPeople(20).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // when - agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto); + agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any()); @@ -224,17 +249,22 @@ void updateAgendaSuccessWithAgendaTeamCapacity() { @DisplayName("Admin Agenda 수정 실패 - Agenda를 찾을 수 없음") void updateAgendaFailAdminWithNotExistAgenda() { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); AgendaAdminUpdateReqDto agendaDto = mock(AgendaAdminUpdateReqDto.class); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.empty()); // expected - assertThrows(NotExistException.class, () -> agendaAdminService.updateAgenda(UUID.randomUUID(), agendaDto)); + assertThrows(NotExistException.class, + () -> agendaAdminService.updateAgenda(UUID.randomUUID(), agendaDto, file)); } @Test @DisplayName("Admin Agenda 수정 실패 - Agenda 지역을 변경할 수 없음") - void updateAgendaFailAdminWithCannotChangeLocation() { + void updateAgendaFailAdminWithCannotChangeLocation() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -246,16 +276,19 @@ void updateAgendaFailAdminWithCannotChangeLocation() { AgendaAdminUpdateReqDto.builder().agendaLocation(Location.SEOUL).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // expected assertThrows(InvalidParameterException.class, - () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file)); } @Test @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 제한을 변경할 수 없음") - void updateAgendaFailAdminWithCannotChangeMinTeam() { + void updateAgendaFailAdminWithCannotChangeMinTeam() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 5; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -267,16 +300,19 @@ void updateAgendaFailAdminWithCannotChangeMinTeam() { AgendaAdminUpdateReqDto.builder().agendaMinTeam(10).agendaMaxTeam(20).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // expected assertThrows(InvalidParameterException.class, - () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file)); } @Test @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 제한을 변경할 수 없음") - void updateAgendaFailAdminWithCannotChangeMaxTeam() { + void updateAgendaFailAdminWithCannotChangeMaxTeam() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 10; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -287,16 +323,19 @@ void updateAgendaFailAdminWithCannotChangeMaxTeam() { AgendaAdminUpdateReqDto.builder().agendaMinTeam(2).agendaMaxTeam(5).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // expected assertThrows(InvalidParameterException.class, - () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file)); } @Test @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 인원 제한을 변경할 수 없음") - void updateAgendaFailAdminWithCannotChangeMaxPeople() { + void updateAgendaFailAdminWithCannotChangeMaxPeople() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -309,16 +348,19 @@ void updateAgendaFailAdminWithCannotChangeMaxPeople() { AgendaAdminUpdateReqDto.builder().agendaMinPeople(2).agendaMaxPeople(5).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // expected assertThrows(InvalidParameterException.class, - () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file)); } @Test @DisplayName("Admin Agenda 수정 실패 - Agenda 팀 인원 제한을 변경할 수 없음") - void updateAgendaFailAdminWithCannotChangeMinPeople() { + void updateAgendaFailAdminWithCannotChangeMinPeople() throws IOException { // given + MockMultipartFile file = new MockMultipartFile("file", "test.jpg", + "image/jpeg", "test".getBytes()); int teamCount = 3; List teams = new ArrayList<>(); for (int i = 0; i < teamCount; i++) { @@ -331,10 +373,11 @@ void updateAgendaFailAdminWithCannotChangeMinPeople() { AgendaAdminUpdateReqDto.builder().agendaMinPeople(5).agendaMaxPeople(20).build(); when(agendaAdminRepository.findByAgendaKey(any())).thenReturn(Optional.of(agenda)); when(agendaTeamAdminRepository.findAllByAgenda(any())).thenReturn(teams); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL(defaultUrl)); // expected assertThrows(InvalidParameterException.class, - () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto)); + () -> agendaAdminService.updateAgenda(agenda.getAgendaKey(), agendaDto, file)); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index c6b44528f..124b7e63f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -111,7 +111,7 @@ void getAgendaTeamListAdminSuccess(int page) throws Exception { // given int size = 10; int total = 37; - Agenda agenda = agendaFixture.createAgenda(); + Agenda agenda = agendaFixture.createAgenda(2, 50, 1, 10); List teams = agendaTeamFixture .createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, total); PageRequestDto pageRequestDto = new PageRequestDto(page, size); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 9e765fa55..2ec720341 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -4,6 +4,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -21,10 +22,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.databind.ObjectMapper; @@ -47,7 +54,10 @@ import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; import lombok.extern.slf4j.Slf4j; @@ -79,6 +89,9 @@ public class AgendaControllerTest { @Autowired private AgendaTestDataUtils agendaTestDataUtils; + @MockBean + private AwsImageHandler imageHandler; + @Autowired EntityManager em; @@ -88,6 +101,9 @@ public class AgendaControllerTest { @Autowired AgendaTeamRepository agendaTeamRepository; + @Value("${info.image.defaultUrl}") + private String defaultUri; + private User user; private String accessToken; @@ -248,33 +264,79 @@ void getAgendaListSuccessWithNoAgenda() throws Exception { class CreateAgenda { @Test - @DisplayName("Agenda를 생성합니다.") + @DisplayName("Agenda 생성하기 성공 - 포스터가 없는 경우 기본 이미지로 생성합니다.") void createAgendaSuccess() throws Exception { // given + URL mockS3Path = new URL(defaultUri); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockS3Path); AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); - String request = objectMapper.writeValueAsString(dto); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, dto); // when String response = mockMvc.perform(post("/agenda/request") .header("Authorization", "Bearer " + accessToken) - .contentType("application/json") - .content(request)) + .params(params)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + AgendaKeyResDto result = objectMapper.readValue(response, AgendaKeyResDto.class); + Agenda agenda = agendaRepository.findByAgendaKey(result.getAgendaKey()) + .orElseThrow(() -> new NotExistException("Agenda not found")); + + // then + assertThat(agenda.getTitle()).isEqualTo(dto.getAgendaTitle()); + assertThat(agenda.getContent()).isEqualTo(dto.getAgendaContent()); + assertThat(agenda.getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); + assertThat(agenda.getPosterUri()).isEqualTo(defaultUri); + } + + @Test + @DisplayName("Agenda 생성하기 성공 - 포스터가 있는 경우 해당 이미지로 생성합니다.") + void createAgendaSuccessWithPosterImage() throws Exception { + // given + URL mockS3Path = new URL("https://test.com/test.jpeg"); + Mockito.when(imageHandler.uploadImageOrDefault( + Mockito.any(MultipartFile.class), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockS3Path); + + AgendaCreateReqDto dto = AgendaCreateReqDto.builder() + .agendaTitle("title").agendaContent("content") + .agendaDeadLine(LocalDateTime.now().plusDays(3)) + .agendaStartTime(LocalDateTime.now().plusDays(5)) + .agendaEndTime(LocalDateTime.now().plusDays(7)) + .agendaMinTeam(2).agendaMaxTeam(5).agendaMinPeople(1).agendaMaxPeople(5) + .agendaIsRanking(true).agendaLocation(Location.SEOUL).build(); + MockMultipartFile agendaPoster = new MockMultipartFile( + "agendaPoster", + "test.jpg", + "image/jpeg", + "test".getBytes() + ); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, dto); + + // when + String response = mockMvc.perform(multipart("/agenda/request") + .file(agendaPoster) + .header("Authorization", "Bearer " + accessToken) + .params(params)) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); AgendaKeyResDto result = objectMapper.readValue(response, AgendaKeyResDto.class); - Optional agenda = agendaRepository.findByAgendaKey(result.getAgendaKey()); + Agenda agenda = agendaRepository.findByAgendaKey(result.getAgendaKey()) + .orElseThrow(() -> new NotExistException("Agenda not found")); // then - assertThat(agenda.isPresent()).isTrue(); - assertThat(agenda.get().getTitle()).isEqualTo(dto.getAgendaTitle()); - assertThat(agenda.get().getContent()).isEqualTo(dto.getAgendaContent()); - assertThat(agenda.get().getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); + assertThat(agenda.getTitle()).isEqualTo(dto.getAgendaTitle()); + assertThat(agenda.getContent()).isEqualTo(dto.getAgendaContent()); + assertThat(agenda.getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); + assertThat(agenda.getPosterUri()).isNotEqualTo(defaultUri); + assertThat(agenda.getPosterUri()).isEqualTo(mockS3Path.toString()); } @Test @@ -282,7 +344,7 @@ void createAgendaSuccess() throws Exception { void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(6)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -303,7 +365,7 @@ void createAgendaFailedWhenDeadlineIsAfterStartTime() throws Exception { void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(4)) @@ -324,7 +386,7 @@ void createAgendaFailedWhenDeadlineIsAfterEndTime() throws Exception { void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(7)) .agendaEndTime(LocalDateTime.now().plusDays(5)) @@ -345,7 +407,7 @@ void createAgendaFailedWhenStartTimeIsAfterEndTime() throws Exception { void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -366,7 +428,7 @@ void createAgendaFailedWhenMinTeamGreaterThanMaxTeam() throws Exception { void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -388,7 +450,7 @@ void createAgendaFailedWhenMinPeopleGreaterThanMaxPeople() throws Exception { void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -410,7 +472,7 @@ void createAgendaFailedWhenNegativeMinTeam(int value) throws Exception { void createAgendaFailedWhenNegativeMinPeople(int value) throws Exception { // given AgendaCreateReqDto dto = AgendaCreateReqDto.builder() - .agendaTitle("title").agendaContents("content") + .agendaTitle("title").agendaContent("content") .agendaDeadLine(LocalDateTime.now().plusDays(3)) .agendaStartTime(LocalDateTime.now().plusDays(5)) .agendaEndTime(LocalDateTime.now().plusDays(7)) @@ -587,13 +649,15 @@ void finishAgendaSuccess() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()) - .awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -629,14 +693,13 @@ void finishAgendaFailedWithNoAwards() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) - .awardPriority(i + 1).build()) + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) .collect(Collectors.toList()); - // expected mockMvc.perform(patch("/agenda/finish") .param("agenda_key", agenda.getAgendaKey().toString()) @@ -680,13 +743,15 @@ void finishAgendaSuccessWithNoRankAndAwards() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), false, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) - .collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) - .awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -722,11 +787,15 @@ void finishAgendaFailedWithInvalidTeam() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true, AgendaStatus.CONFIRM); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); awards.add(AgendaTeamAward.builder() .teamName("invalid_team").awardName("prize").awardPriority(1).build()); // invalid team @@ -748,12 +817,15 @@ void finishAgendaFailedWithNoAgenda() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i) - .awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -818,11 +890,15 @@ void finishAgendaFailedNotHost() throws Exception { int awardSize = 3; User another = testDataUtils.createNewUser(); Agenda agenda = agendaMockData.createAgenda(another.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -843,11 +919,15 @@ void finishAgendaFailedAlreadyConfirm() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), AgendaStatus.FINISH); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -868,11 +948,15 @@ void finishAgendaFailedAlreadyCancel() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), AgendaStatus.CANCEL); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -893,11 +977,15 @@ void finishAgendaFailedBeforeStartTime() throws Exception { int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().plusDays(1), AgendaStatus.OPEN); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); String response = objectMapper.writeValueAsString(agendaAwardsReqDto); @@ -917,11 +1005,15 @@ void finishAgendaFailedWithEmptyAwardName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardName("").awardPriority(awardSize).build()); // empty award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -943,11 +1035,15 @@ void finishAgendaFailedWithNullAwardName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().teamName(agendaTeams.get(awardSize).getName()) .awardPriority(awardSize).build()); // null award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -969,11 +1065,15 @@ void finishAgendaFailedWithEmptyTeamName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .teamName("").awardPriority(awardSize).build()); // empty award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -995,11 +1095,15 @@ void finishAgendaFailedWithNullTeamName() throws Exception { int teamSize = 10; int awardSize = 3; Agenda agenda = agendaMockData.createAgenda(user.getIntraId(), LocalDateTime.now().minusDays(10), true); - List agendaTeams = IntStream.range(0, teamSize).mapToObj(i -> agendaMockData - .createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)).collect(Collectors.toList()); - List awards = IntStream.range(0, awardSize).mapToObj(i -> AgendaTeamAward.builder() - .teamName(agendaTeams.get(i).getName()).awardName("prize" + i).awardPriority(i + 1).build()) - .collect(Collectors.toList()); + List agendaTeams = + IntStream.range(0, teamSize) + .mapToObj(i -> agendaMockData.createAgendaTeam(agenda, "team" + i, AgendaTeamStatus.CONFIRM)) + .collect(Collectors.toList()); + List awards = + IntStream.range(0, awardSize) + .mapToObj(i -> AgendaTeamAward.builder().teamName(agendaTeams.get(i).getName()) + .awardName("prize" + i).awardPriority(i + 1).build()) + .collect(Collectors.toList()); awards.add(AgendaTeamAward.builder().awardName("prize" + awardSize) .awardPriority(awardSize).build()); // null award name AgendaAwardsReqDto agendaAwardsReqDto = AgendaAwardsReqDto.builder().awards(awards).build(); @@ -1023,7 +1127,7 @@ class ConfirmAgenda { @DisplayName("Agenda 확정하기 성공") void confirmAgendaSuccess() throws Exception { // given - Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams(user.getIntraId(), 10, AgendaStatus.OPEN); + Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams(user.getIntraId(), 5, AgendaStatus.OPEN); List openTeams = agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.OPEN, 3); // when @@ -1059,7 +1163,7 @@ void confirmAgendaFailedWithNoAgenda() throws Exception { @DisplayName("Agenda 확정하기 실패 - 개최자가 아닌 경우") void confirmAgendaFailedWithNotHost() throws Exception { // given - Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams("not host", 10, AgendaStatus.OPEN); + Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams("not host", 5, AgendaStatus.OPEN); // when mockMvc.perform(patch("/agenda/confirm") @@ -1073,8 +1177,9 @@ void confirmAgendaFailedWithNotHost() throws Exception { @DisplayName("Agenda 확정하기 실패 - 대회의 상태가 OPEN이 아닌 경우") void confirmAgendaFailedWithNotOpenAgenda(AgendaStatus status) throws Exception { // given - Agenda agenda = agendaTestDataUtils.createAgendaAndAgendaTeams(user.getIntraId(), 10, status); - agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.OPEN, 3); + Agenda agenda = agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.OPEN); + agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, 5); + agenda.updateAgendaStatus(status); // expected mockMvc.perform(patch("/agenda/confirm") diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java index c5e2afa5e..4bdd20599 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/dto/AgendaCreateReqDtoTest.java @@ -30,7 +30,7 @@ void createAgendaSuccess() { .build(); // when - Agenda agenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user); + Agenda agenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(dto, user.getIntraId()); // then assertNotNull(agenda); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 439f26e17..355c5461d 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -4,6 +4,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -43,6 +46,7 @@ import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.InvalidParameterException; import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.ImageHandler; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -64,6 +68,9 @@ class AgendaServiceTest { @Mock TicketService ticketService; + @Mock + ImageHandler imageHandler; + @InjectMocks AgendaService agendaService; @@ -151,25 +158,21 @@ class CreateAgenda { @Test @DisplayName("Agenda 생성 성공") - void createAgendaSuccess() { + void createAgendaSuccess() throws IOException { // given + AgendaCreateReqDto agendaCreateReqDto = AgendaCreateReqDto.builder().build(); UserDto user = UserDto.builder().intraId("intraId").build(); - AgendaCreateReqDto agendaCreateReqDto = AgendaCreateReqDto.builder() - .agendaDeadLine(LocalDateTime.now().plusDays(5)) - .agendaStartTime(LocalDateTime.now().plusDays(8)) - .agendaEndTime(LocalDateTime.now().plusDays(10)) - .agendaMinTeam(2).agendaMaxTeam(5) - .agendaMinPeople(1).agendaMaxPeople(5) - .build(); Agenda agenda = Agenda.builder().build(); - when(agendaRepository.save(any(Agenda.class))).thenReturn(agenda); + when(agendaRepository.save(any())).thenReturn(agenda); + when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL("http://localhost")); // when - Agenda result = agendaService.addAgenda(agendaCreateReqDto, user); + Agenda result = agendaService.addAgenda(agendaCreateReqDto, null, user); // then - verify(agendaRepository, times(1)).save(any(Agenda.class)); - assertThat(result.getAgendaKey()).isEqualTo(agenda.getAgendaKey()); + verify(agendaRepository, times(1)).save(any()); + verify(imageHandler, times(1)).uploadImageOrDefault(any(), any(), any()); + assertThat(result).isEqualTo(agenda); } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 514609fa9..0e695c75f 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -17,6 +17,8 @@ import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import org.springframework.core.io.support.ResourcePatternUtils; + import gg.data.BaseTimeEntity; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; @@ -144,14 +146,17 @@ public void finishAgenda() { this.status = AgendaStatus.FINISH; } - public void updateInformation(String title, String content, String posterUri) { + public void updateInformation(String title, String content) { if (Objects.nonNull(title) && !title.isBlank()) { this.title = title; } - if (Objects.nonNull(content) && !title.isBlank()) { + if (Objects.nonNull(content) && !content.isBlank()) { this.content = content; } - if (Objects.nonNull(posterUri) && !title.isBlank()) { + } + + public void updatePosterUri(String posterUri) { + if (Objects.nonNull(posterUri) && ResourcePatternUtils.isUrl(posterUri)) { this.posterUri = posterUri; } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java index 70a32d952..4c483e028 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java @@ -2,6 +2,7 @@ import static gg.data.agenda.type.Coalition.*; +import java.io.IOException; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -78,7 +79,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } } - private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) { + private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) throws IOException { ProviderType providerType = ProviderType.keyOf( userRequest.getClientRegistration().getRegistrationId().toUpperCase()); User savedUser; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewItemImageUploader.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewItemImageUploader.java index 341643928..ab920cb7f 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewItemImageUploader.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewItemImageUploader.java @@ -1,6 +1,7 @@ package gg.pingpong.api.global.utils.aws; import java.io.IOException; +import java.net.URL; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -8,28 +9,21 @@ import org.springframework.web.multipart.MultipartFile; import gg.data.pingpong.store.Item; -import gg.pingpong.api.global.utils.ItemImageHandler; +import gg.utils.file.handler.ImageHandler; +import lombok.RequiredArgsConstructor; @Component +@RequiredArgsConstructor public class AsyncNewItemImageUploader { - private final ItemImageHandler itemImageHandler; + private final ImageHandler imageHandler; @Value("${info.image.itemNotFoundUrl}") - private String defaultImageUrl; - - public AsyncNewItemImageUploader(ItemImageHandler itemImageHandler) { - this.itemImageHandler = itemImageHandler; - } + private String defaultUrl; @Transactional - public void upload(Item item, - MultipartFile multipartFile) throws IOException { - String s3ImageUrl = itemImageHandler.updateAndGetS3ImageUri(multipartFile, item); - if (s3ImageUrl == null) { - item.imageUpdate(defaultImageUrl); - } else { - item.imageUpdate(s3ImageUrl); - } + public void upload(Item item, MultipartFile multipartFile) throws IOException { + URL s3ImageUrl = imageHandler.uploadImageOrDefault(multipartFile, item.getName(), defaultUrl); + item.imageUpdate(s3ImageUrl.toString()); } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewUserImageUploader.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewUserImageUploader.java index 5f8673d33..74f0ddfc3 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewUserImageUploader.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/AsyncNewUserImageUploader.java @@ -1,11 +1,11 @@ package gg.pingpong.api.global.utils.aws; +import static gg.utils.exception.ErrorCode.*; + import java.io.IOException; +import java.net.URL; import java.time.LocalDateTime; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; - import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -14,39 +14,37 @@ import gg.data.user.User; import gg.data.user.UserImage; -import gg.pingpong.api.global.utils.UserImageHandler; import gg.repo.user.UserImageRepository; import gg.repo.user.UserRepository; +import gg.utils.exception.custom.NotExistException; +import gg.utils.file.handler.ImageHandler; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Component +@RequiredArgsConstructor public class AsyncNewUserImageUploader { - private final UserImageHandler userImageHandler; + + private final ImageHandler imageHandler; + private final UserRepository userRepository; - @PersistenceContext - private EntityManager entityManager; - @Value("${info.image.defaultUrl}") - private String defaultImageUrl; private final UserImageRepository userImageRepository; - public AsyncNewUserImageUploader(UserImageHandler userImageHandler, UserRepository userRepository, - UserImageRepository userImageRepository) { - this.userImageHandler = userImageHandler; - this.userRepository = userRepository; - this.userImageRepository = userImageRepository; - } + @Value("${info.image.defaultUrl}") + private String defaultImageUrl; @Async("asyncExecutor") @Transactional - public void upload(String intraId, String imageUrl) { - String s3ImageUrl = userImageHandler.uploadAndGetS3ImageUri(intraId, imageUrl); - if (defaultImageUrl.equals(s3ImageUrl)) { + public void upload(String intraId, String imageUrl) throws IOException { + URL s3ImageUrl = imageHandler.uploadImageFromUrlOrDefault(imageUrl, intraId, defaultImageUrl); + if (defaultImageUrl.equals(s3ImageUrl.toString())) { return; } userRepository.findByIntraId(intraId).ifPresent(user -> { - UserImage userImage = new UserImage(user, (s3ImageUrl != null) ? s3ImageUrl : defaultImageUrl, + UserImage userImage = new UserImage(user, + (s3ImageUrl.toString() != null) ? s3ImageUrl.toString() : defaultImageUrl, LocalDateTime.now(), null, true); userImageRepository.save(userImage); userRepository.updateUserImage(user.getId(), userImage.getImageUri()); @@ -55,9 +53,10 @@ public void upload(String intraId, String imageUrl) { @Transactional public void update(String intraId, MultipartFile multipartFile) throws IOException { - User user = userRepository.findByIntraId(intraId).get(); - String s3ImageUrl = userImageHandler.updateAndGetS3ImageUri(multipartFile, user); - s3ImageUrl = s3ImageUrl == null ? defaultImageUrl : s3ImageUrl; + User user = userRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(USER_NOT_FOUND)); + URL storedUrl = imageHandler.uploadImageOrDefault(multipartFile, user.getIntraId(), defaultImageUrl); + String s3ImageUrl = storedUrl.toString(); UserImage userImage = new UserImage(user, s3ImageUrl, LocalDateTime.now(), null, true); userImageRepository.saveAndFlush(userImage); user.updateImageUri(s3ImageUrl); diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ItemImageHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/ItemImageHandler.java similarity index 92% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ItemImageHandler.java rename to gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/ItemImageHandler.java index cd61914f0..aa4d6df7f 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ItemImageHandler.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/ItemImageHandler.java @@ -1,11 +1,10 @@ -package gg.pingpong.api.global.utils; +package gg.pingpong.api.global.utils.aws; import java.io.IOException; import java.io.InputStream; import java.util.UUID; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import com.amazonaws.services.s3.AmazonS3; @@ -15,7 +14,10 @@ import gg.data.pingpong.store.Item; -@Component +/** + * This Module has been replaced by gg.utils.file.handler.AwsImageHandler + */ +@Deprecated public class ItemImageHandler { private final AmazonS3 amazonS3; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/UserImageHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/UserImageHandler.java similarity index 91% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/UserImageHandler.java rename to gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/UserImageHandler.java index 21d46a36e..bf7627418 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/UserImageHandler.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/aws/UserImageHandler.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.utils; +package gg.pingpong.api.global.utils.aws; import java.io.IOException; import java.io.InputStream; @@ -15,8 +15,14 @@ import gg.data.user.User; import gg.repo.user.UserImageRepository; +import gg.utils.file.FileDownloader; +import gg.utils.file.ImageResizingUtil; +import gg.utils.file.JpegMultipartFile; -@Component +/** + * This Module has been replaced by gg.utils.file.handler.AwsImageHandler + */ +@Deprecated public class UserImageHandler { private final AmazonS3 amazonS3; private final FileDownloader fileDownloader; diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/admin/item/controller/ItemAdminControllerTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/admin/item/controller/ItemAdminControllerTest.java index eaefbf250..651d167da 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/admin/item/controller/ItemAdminControllerTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/admin/item/controller/ItemAdminControllerTest.java @@ -4,6 +4,7 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.util.List; import javax.transaction.Transactional; @@ -14,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageRequest; @@ -31,11 +33,11 @@ import gg.pingpong.api.admin.store.controller.request.ItemUpdateRequestDto; import gg.pingpong.api.admin.store.controller.response.ItemListResponseDto; import gg.pingpong.api.admin.store.service.ItemAdminService; -import gg.pingpong.api.global.utils.ItemImageHandler; import gg.repo.user.UserRepository; import gg.utils.ItemTestUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.file.handler.AwsImageHandler; @IntegrationTest @AutoConfigureMockMvc @@ -58,7 +60,10 @@ class ItemAdminControllerTest { @Autowired ItemTestUtils itemTestUtils; @MockBean - ItemImageHandler itemImageHandler; + AwsImageHandler imageHandler; + + @Value("${info.image.defaultUrl}") + private String defaultImageUrl; Item item; @@ -99,8 +104,9 @@ public void getAllItemHistoryTest() throws Exception { @Test @DisplayName("POST /pingpong/admin/items/history/{itemId}") public void updateItemTest() throws Exception { - Mockito.when(itemImageHandler.uploadToS3(Mockito.any(), Mockito.anyString())) - .thenAnswer(invocation -> invocation.getArgument(1, String.class)); + URL mockUrl = new URL(defaultImageUrl); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockUrl); String accessToken = testDataUtils.getAdminLoginAccessToken(); Long userId = tokenProvider.getUserIdFromAccessToken(accessToken); diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/controller/UserControllerTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/controller/UserControllerTest.java index 38d9abf55..28c671779 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/controller/UserControllerTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/controller/UserControllerTest.java @@ -4,6 +4,7 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.time.LocalDateTime; import java.util.Arrays; import java.util.Optional; @@ -17,13 +18,13 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.databind.ObjectMapper; @@ -46,7 +47,6 @@ import gg.data.user.type.RoleType; import gg.data.user.type.SnsType; import gg.pingpong.api.admin.store.controller.request.ItemUpdateRequestDto; -import gg.pingpong.api.global.utils.UserImageHandler; import gg.pingpong.api.user.game.controller.request.RankResultReqDto; import gg.pingpong.api.user.game.service.GameService; import gg.pingpong.api.user.store.service.CoinHistoryService; @@ -77,6 +77,7 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.dto.GameInfoDto; import gg.utils.exception.user.UserNotFoundException; +import gg.utils.file.handler.AwsImageHandler; import lombok.extern.slf4j.Slf4j; @IntegrationTest @@ -116,11 +117,14 @@ class UserControllerTest { @Autowired ItemTestUtils itemTestUtils; @MockBean - UserImageHandler userImageHandler; + AwsImageHandler imageHandler; User admin; @Autowired private MockMvc mockMvc; + @Value("${info.image.defaultUrl}") + private String defaultUrl; + @BeforeEach public void setUp() { testDataUtils.createTierSystem("pingpong"); @@ -570,9 +574,8 @@ public void getUserCoinHistory() throws Exception { @Test @DisplayName("[post]/pingpong/users/profile-image") public void getUserImage() throws Exception { - String mockS3Path = "mockS3Path"; - Mockito.when(userImageHandler - .uploadToS3(Mockito.any(MultipartFile.class), Mockito.any(String.class))) + URL mockS3Path = new URL(defaultUrl); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.any(String.class), Mockito.anyString())) .thenReturn(mockS3Path); // String accessToken = testDataUtils.getLoginAccessToken(); ItemUpdateRequestDto dto = new ItemUpdateRequestDto("name", "mainContent", diff --git a/gg-utils/build.gradle b/gg-utils/build.gradle index 3c6d27f6b..3ecc8935a 100644 --- a/gg-utils/build.gradle +++ b/gg-utils/build.gradle @@ -73,6 +73,8 @@ unitTestCoverageReport { dependencies { + implementation "com.amazonaws:aws-java-sdk-s3:1.12.281" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' @@ -84,6 +86,7 @@ dependencies { testFixturesImplementation("org.testcontainers:junit-jupiter:1.19.3") testFixturesImplementation("org.testcontainers:mysql:1.19.3") testFixturesImplementation("com.redis:testcontainers-redis:2.0.1") + testFixturesImplementation("com.fasterxml.jackson.core:jackson-databind") testFixturesImplementation("org.projectlombok:lombok:1.18.26") testFixturesCompileOnly("org.projectlombok:lombok") diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 2234daf38..61e3ffe18 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -193,6 +193,8 @@ public enum ErrorCode { // agenda AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), + AGENDA_CREATE_FAILED(500, "AG", "일정 생성에 실패했습니다."), + AGENDA_UPDATE_FAILED(500, "AG", "일정 수정에 실패했습니다."), AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), AGENDA_TEAM_ALREADY_CONFIRM(400, "AG", "이미 확정된 팀입니다."), AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), @@ -200,6 +202,7 @@ public enum ErrorCode { AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG", "공지사항이 존재하지 않습니다."), AGENDA_TEAM_FULL(400, "AG", "팀이 꽉 찼습니다."), AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), + AGENDA_POSTER_SIZE_TOO_LARGE(400, "AG", "포스터 사이즈가 너무 큽니다."), AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), UPDATE_LOCATION_NOT_VALID(400, "AG", "지역을 변경할 수 없습니다."), diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/FileDownloader.java b/gg-utils/src/main/java/gg/utils/file/FileDownloader.java similarity index 94% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/FileDownloader.java rename to gg-utils/src/main/java/gg/utils/file/FileDownloader.java index b7a080041..fd6306c11 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/FileDownloader.java +++ b/gg-utils/src/main/java/gg/utils/file/FileDownloader.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.utils; +package gg.utils.file; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -10,7 +10,8 @@ @Component public class FileDownloader { - private RestTemplate restTemplate; + + private final RestTemplate restTemplate; public FileDownloader() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ImageResizingUtil.java b/gg-utils/src/main/java/gg/utils/file/ImageResizingUtil.java similarity index 96% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ImageResizingUtil.java rename to gg-utils/src/main/java/gg/utils/file/ImageResizingUtil.java index f6e586987..eedb9ed19 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ImageResizingUtil.java +++ b/gg-utils/src/main/java/gg/utils/file/ImageResizingUtil.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.utils; +package gg.utils.file; import java.awt.*; import java.awt.image.BufferedImage; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/JpegMultipartFile.java b/gg-utils/src/main/java/gg/utils/file/JpegMultipartFile.java similarity index 96% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/JpegMultipartFile.java rename to gg-utils/src/main/java/gg/utils/file/JpegMultipartFile.java index 3426c5906..03c5edd6b 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/JpegMultipartFile.java +++ b/gg-utils/src/main/java/gg/utils/file/JpegMultipartFile.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.utils; +package gg.utils.file; import java.io.ByteArrayInputStream; import java.io.File; diff --git a/gg-utils/src/main/java/gg/utils/file/handler/AwsImageHandler.java b/gg-utils/src/main/java/gg/utils/file/handler/AwsImageHandler.java new file mode 100644 index 000000000..96cc9859a --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/file/handler/AwsImageHandler.java @@ -0,0 +1,112 @@ +package gg.utils.file.handler; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; + +import gg.utils.file.FileDownloader; +import gg.utils.file.ImageResizingUtil; +import gg.utils.file.JpegMultipartFile; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AwsImageHandler implements ImageHandler { + + private final AmazonS3 amazonS3; + + private final FileDownloader fileDownloader; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @Value("${cloud.aws.s3.dir}") + private String dir; + + @Override + public URL uploadImageOrDefault(MultipartFile multipartFile, + String filename, String defaultUrl) throws IOException { + if (filename.isBlank() || isDefaultImage(multipartFile)) { + return new URL(defaultUrl); + } + String originalFilename = multipartFile.getOriginalFilename(); + String storeFileName = createStoredFileName(originalFilename, filename); + URL storedUrl = uploadToS3(multipartFile, storeFileName); + if (Objects.isNull(storedUrl)) { + return new URL(defaultUrl); + } + return storedUrl; + } + + private static boolean isDefaultImage(MultipartFile multipartFile) { + if (Objects.isNull(multipartFile)) { + return true; + } + if (Objects.isNull(multipartFile.getOriginalFilename())) { + return true; + } + return multipartFile.getOriginalFilename().equals("small_default.jpeg"); + } + + @Override + public URL uploadImageFromUrlOrDefault(String imageUrl, String filename, String defaultUrl) throws IOException { + if (filename.isBlank() || ResourcePatternUtils.isUrl(imageUrl)) { + return new URL(defaultUrl); + } + byte[] downloadedImageBytes = fileDownloader.downloadFromUrl(imageUrl); + byte[] resizedImageBytes = ImageResizingUtil.resizeImageBytes(downloadedImageBytes, 0.5); + MultipartFile multipartFile = new JpegMultipartFile(resizedImageBytes, filename); + URL storedUrl = uploadToS3(multipartFile, multipartFile.getOriginalFilename()); + if (Objects.isNull(storedUrl)) { + return new URL(defaultUrl); + } + return storedUrl; + } + + private String createStoredFileName(String originalFilename, String filename) { + String ext = extractExtensionOrDefault(originalFilename, "jpeg"); + return filename + "-" + UUID.randomUUID() + "." + ext; + } + + private String extractExtensionOrDefault(String uploadFileName, String defaultExtension) { + if (uploadFileName == null) { + return defaultExtension; + } + + int pos = uploadFileName.lastIndexOf("."); + if (pos == -1) { + return defaultExtension; + } + + return uploadFileName.substring(pos + 1); + } + + public URL uploadToS3(MultipartFile multipartFile, String fileName) throws IOException { + String s3FileName = this.dir + fileName; + InputStream inputStream = multipartFile.getInputStream(); + + ObjectMetadata objMeta = new ObjectMetadata(); + objMeta.setContentLength(multipartFile.getSize()); + + PutObjectRequest putObjectRequest = new PutObjectRequest( + bucketName, + s3FileName, + inputStream, + objMeta + ).withCannedAcl(CannedAccessControlList.PublicRead); + amazonS3.putObject(putObjectRequest); + return amazonS3.getUrl(bucketName, s3FileName); + } +} diff --git a/gg-utils/src/main/java/gg/utils/file/handler/ImageHandler.java b/gg-utils/src/main/java/gg/utils/file/handler/ImageHandler.java new file mode 100644 index 000000000..d6ee34f4a --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/file/handler/ImageHandler.java @@ -0,0 +1,12 @@ +package gg.utils.file.handler; + +import java.io.IOException; +import java.net.URL; + +import org.springframework.web.multipart.MultipartFile; +public interface ImageHandler { + + URL uploadImageOrDefault(MultipartFile multipartFile, String filename, String defaultUrl) throws IOException; + + URL uploadImageFromUrlOrDefault(String imageUrl, String filename, String defaultUrl) throws IOException; +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/converter/MultiValueMapConverter.java b/gg-utils/src/testFixtures/java/gg/utils/converter/MultiValueMapConverter.java new file mode 100644 index 000000000..d2b561eff --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/converter/MultiValueMapConverter.java @@ -0,0 +1,33 @@ +package gg.utils.converter; + +import java.util.Map; + +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class MultiValueMapConverter { + + public static MultiValueMap convert(ObjectMapper objectMapper, Object dto) { + try { + MultiValueMap params = new LinkedMultiValueMap<>(); + Map map = objectMapper.convertValue(dto, new TypeReference<>() { + }); + params.setAll(map); + return params; + } catch (Exception e) { + log.error("Url Parameter 변환중 오류가 발생했습니다. requestDto={}", dto, e); + throw new IllegalStateException("Url Parameter 변환중 오류가 발생했습니다."); + } + } +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java index d7af90700..c87625451 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -41,6 +41,28 @@ public Agenda createAgenda() { return agendaRepository.save(agenda); } + public Agenda createAgenda(int minTeam, int maxTeam, int minPeople, int maxPeople) { + Agenda agenda = Agenda.builder() + .title("title " + UUID.randomUUID()) + .content("content " + UUID.randomUUID()) + .deadline(LocalDateTime.now().plusDays(3)) + .startTime(LocalDateTime.now().plusDays(5)) + .endTime(LocalDateTime.now().plusDays(6)) + .minTeam(minTeam) + .maxTeam(maxTeam) + .currentTeam(0) + .minPeople(minPeople) + .maxPeople(maxPeople) + .status(OPEN) + .posterUri("posterUri") + .hostIntraId("hostIntraId") + .location(Location.MIX) + .isOfficial(true) + .isRanking(true) + .build(); + return agendaRepository.save(agenda); + } + public Agenda createAgenda(String intraId, AgendaStatus status) { Agenda agenda = Agenda.builder() .title("title " + UUID.randomUUID()) diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index e05f24020..f30b34e44 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -4,6 +4,7 @@ import static gg.data.agenda.type.Location.*; import static java.util.UUID.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -159,6 +160,9 @@ public List createAgendaTeamList(Agenda agenda, AgendaTeamStatus sta .isPrivate(false) .build(); teams.add(agendaTeam); + if (status == CONFIRM) { + agenda.confirmTeam(agendaTeam.getLocation(), LocalDateTime.now()); + } } return agendaTeamRepository.saveAll(teams); } From bcd03f6280127cab207d1f292d0c985acb83b83c Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:33:17 +0900 Subject: [PATCH 066/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EA=B0=9C?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EA=B8=B0(=EC=96=B4=EB=93=9C=EB=AF=BC)=20API=20#856=20?= =?UTF-8?q?(#941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: seungsje --- .../agenda/AgendaProfileAdminRepository.java | 2 - .../AgendaProfileAdminController.java | 38 ++++ .../AgendaProfileChangeAdminReqDto.java | 33 ++++ .../service/AgendaProfileAdminService.java | 35 ++++ .../request/AgendaProfileChangeReqDto.java | 2 +- .../AgendaProfileControllerAdminTest.java | 170 ++++++++++++++++++ .../java/gg/data/agenda/AgendaProfile.java | 6 + .../api/global/config/SwaggerConfig.java | 9 + .../security/config/SecurityConfig.java | 1 + .../java/gg/utils/TestDataUtils.java | 14 ++ 10 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/AgendaProfileAdminController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/request/AgendaProfileChangeAdminReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/service/AgendaProfileAdminService.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java index f6631c0f5..a8e52007e 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaProfileAdminRepository.java @@ -10,8 +10,6 @@ @Repository public interface AgendaProfileAdminRepository extends JpaRepository { - - @Query("SELECT a FROM AgendaProfile a WHERE a.intraId = :intraId") Optional findByIntraId(String intraId); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/AgendaProfileAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/AgendaProfileAdminController.java new file mode 100644 index 000000000..9625759c4 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/AgendaProfileAdminController.java @@ -0,0 +1,38 @@ +package gg.agenda.api.admin.agendaprofile.controller; + +import javax.validation.Valid; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.admin.agendaprofile.controller.request.AgendaProfileChangeAdminReqDto; +import gg.agenda.api.admin.agendaprofile.service.AgendaProfileAdminService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/admin/profile") +public class AgendaProfileAdminController { + private final AgendaProfileAdminService agendaProfileAdminService; + + /** + * 관리자 개인 프로필 변경 API + * + * @param intraId 수정할 사용자의 intra_id + * @param reqDto 변경할 프로필 정보 + * @return HTTP 상태 코드와 빈 응답 + */ + @PatchMapping + public ResponseEntity agendaProfileModify( + @RequestParam String intraId, + @RequestBody @Valid AgendaProfileChangeAdminReqDto reqDto) { + agendaProfileAdminService.modifyAgendaProfile(intraId, reqDto); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} + diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/request/AgendaProfileChangeAdminReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/request/AgendaProfileChangeAdminReqDto.java new file mode 100644 index 000000000..d5dcf76e8 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/controller/request/AgendaProfileChangeAdminReqDto.java @@ -0,0 +1,33 @@ +package gg.agenda.api.admin.agendaprofile.controller.request; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import org.hibernate.validator.constraints.URL; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AgendaProfileChangeAdminReqDto { + + @NotBlank + @Size(max = 50, message = "userContent의 길이가 허용된 범위를 초과합니다.") + private String userContent; + + @URL + @Size(max = 200, message = "userGithub의 길이가 허용된 범위를 초과합니다.") + private String userGithub; + + @NotBlank + private String userLocation; + + @Builder + public AgendaProfileChangeAdminReqDto(String userContent, String userGithub, String userLocation) { + this.userContent = userContent; + this.userGithub = userGithub; + this.userLocation = userLocation; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/service/AgendaProfileAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/service/AgendaProfileAdminService.java new file mode 100644 index 000000000..b5718a539 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaprofile/service/AgendaProfileAdminService.java @@ -0,0 +1,35 @@ +package gg.agenda.api.admin.agendaprofile.service; + +import static gg.utils.exception.ErrorCode.*; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Service; + +import gg.admin.repo.agenda.AgendaProfileAdminRepository; +import gg.agenda.api.admin.agendaprofile.controller.request.AgendaProfileChangeAdminReqDto; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Location; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AgendaProfileAdminService { + private final AgendaProfileAdminRepository agendaProfileAdminRepository; + + /** + * AgendaProfile 변경 메서드 + * @param intraId 로그인한 유저의 id + * @param reqDto 변경할 프로필 정보 + */ + @Transactional + public void modifyAgendaProfile(String intraId, AgendaProfileChangeAdminReqDto reqDto) { + AgendaProfile agendaProfile = agendaProfileAdminRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + agendaProfile.updateProfileAdmin(reqDto.getUserContent(), reqDto.getUserGithub(), + Location.valueOfLocation(reqDto.getUserLocation())); + agendaProfileAdminRepository.save(agendaProfile); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java index 6a280f4ad..b8d415b3d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/request/AgendaProfileChangeReqDto.java @@ -18,7 +18,7 @@ public class AgendaProfileChangeReqDto { private String userContent; @URL - @Size(max = 100, message = "userGithub의 길이가 허용된 범위를 초과합니다.") + @Size(max = 200, message = "userGithub의 길이가 허용된 범위를 초과합니다.") private String userGithub; @Builder diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java new file mode 100644 index 000000000..c86e31f91 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java @@ -0,0 +1,170 @@ +package gg.agenda.api.admin.agendaprofile; + +import static gg.data.agenda.type.Location.*; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.admin.repo.agenda.AgendaProfileAdminRepository; +import gg.agenda.api.AgendaMockData; +import gg.agenda.api.admin.agendaprofile.controller.request.AgendaProfileChangeAdminReqDto; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import gg.data.user.type.RoleType; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaProfileControllerAdminTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private TestDataUtils testDataUtils; + @Autowired + private AgendaMockData agendaMockData; + @Autowired + private AgendaProfileAdminRepository agendaProfileAdminRepository; + User user; + String accessToken; + + @Nested + @DisplayName("개인 프로필 정보 변경") + class UpdateAgendaProfile { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewAdminUser(RoleType.ADMIN); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("유효한 정보로 개인 프로필을 변경합니다.") + void updateProfileWithValidData() throws Exception { + // Given + AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + agendaMockData.createTicket(agendaProfile); + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto("Valid user content", + "https://github.com/validUser", "SEOUL"); + String content = objectMapper.writeValueAsString(requestDto); + // When + mockMvc.perform(patch("/agenda/admin/profile") + .param("intraId", user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNoContent()); + // Then + AgendaProfile result = agendaProfileAdminRepository.findByIntraId(user.getIntraId()).orElseThrow(null); + assertThat(result.getContent()).isEqualTo(requestDto.getUserContent()); + assertThat(result.getGithubUrl()).isEqualTo(requestDto.getUserGithub()); + assertThat(result.getLocation().name()).isEqualTo(requestDto.getUserLocation()); + } + + @Test + @DisplayName("ENUM 이외의 지역 정보가 들어온 경우 MIX로 저장합니다.") + void updateProfileWithInvalidLocation() throws Exception { + // Given + agendaMockData.createAgendaProfile(user, SEOUL); + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto("Valid user content", + "https://github.com/validUser", "INVALID_LOCATION"); + String content = objectMapper.writeValueAsString(requestDto); + + // When + mockMvc.perform(patch("/agenda/admin/profile") + .param("intraId", user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNoContent()); + + // Then + AgendaProfile result = agendaProfileAdminRepository.findByIntraId(user.getIntraId()).orElseThrow(); + assertThat(result.getLocation()).isEqualTo(Location.MIX); + } + + @Test + @DisplayName("userContent 없이 개인 프로필을 변경합니다.") + void updateProfileWithoutUserContent() throws Exception { + // Given + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto("", + "https://github.com/validUser", "SEOUL"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/admin/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("잘못된 형식의 userGithub로 개인 프로필을 변경합니다.") + void updateProfileWithInvalidUserGithub() throws Exception { + // Given + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto("Valid user content", + "invalidGithubUrl", "SEOUL"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/admin/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("userContent가 허용된 길이를 초과하여 개인 프로필을 변경합니다.") + void updateProfileWithExceededUserContentLength() throws Exception { + // Given + String longContent = "a".repeat(1001); // Assuming the limit is 1000 characters + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto(longContent, + "https://github.com/validUser", "SEOUL"); + String content = objectMapper.writeValueAsString(requestDto); + // When & Then + mockMvc.perform(patch("/agenda/admin/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("userGithub가 허용된 길이를 초과하여 개인 프로필을 변경합니다.") + void updateProfileWithExceededUserGithubLength() throws Exception { + // Given + String longGithubUrl = "https://github.com/" + "a".repeat(256); // Assuming the limit is 255 characters + AgendaProfileChangeAdminReqDto requestDto = new AgendaProfileChangeAdminReqDto("Valid user content", + longGithubUrl, "SEOUL"); + + String content = objectMapper.writeValueAsString(requestDto); + + // When & Then + mockMvc.perform(patch("/agenda/admin/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } + } + +} + + + diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index e75d317f3..bcadfe695 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -62,4 +62,10 @@ public void updateProfile(String content, String githubUrl) { this.content = content; this.githubUrl = githubUrl; } + + public void updateProfileAdmin(String content, String githubUrl, Location location) { + this.content = content; + this.githubUrl = githubUrl; + this.location = location; + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java index eff142567..998244642 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/config/SwaggerConfig.java @@ -23,6 +23,15 @@ public GroupedOpenApi agendaGroup() { .build(); } + @Bean + public GroupedOpenApi agendaAdminGroup() { + return GroupedOpenApi.builder() + .group("agenda admin") + .pathsToMatch("/agenda/admin/**") + .packagesToScan("gg.agenda.api.admin") + .build(); + } + @Bean public GroupedOpenApi partyGroup() { return GroupedOpenApi.builder() diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java index 56a62b45d..35322e80d 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java @@ -40,6 +40,7 @@ protected void configure(HttpSecurity http) throws Exception { .authorizeRequests() .antMatchers("/pingpong/admin/**").hasRole("ADMIN") .antMatchers("/party/admin/**").hasRole("ADMIN") + .antMatchers("/agenda/admin/**").hasRole("ADMIN") .antMatchers("/admin/recruitments/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/pingpong/users/{intraId}").hasAnyRole("USER", "ADMIN") .antMatchers(HttpMethod.POST, "/pingpong/match").hasAnyRole("USER", "ADMIN") diff --git a/gg-utils/src/testFixtures/java/gg/utils/TestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/TestDataUtils.java index cc6ae463a..07592f5d3 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/TestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/TestDataUtils.java @@ -205,6 +205,20 @@ public User createNewUser() { return user; } + public User createNewAdminUser(RoleType roleType) { + String randomId = UUID.randomUUID().toString().substring(0, 30); + User user = User.builder() + .eMail("email") + .intraId(randomId) + .racketType(RacketType.PENHOLDER) + .snsNotiOpt(SnsType.NONE) + .roleType(roleType) + .totalExp(1000) + .build(); + userRepository.save(user); + return user; + } + public User createNewUser(String intraId) { User user = User.builder() .eMail("email") From 1f65e80b65553ce69862b1a5cafe22734f835715 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:25:12 +0900 Subject: [PATCH 067/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=ED=95=98=EA=B8=B0=20API=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20#939=20(#943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AgendaTeamAdminService.java | 2 +- .../agenda/controller/AgendaController.java | 11 +- .../user/agenda/service/AgendaService.java | 8 ++ .../controller/AgendaTeamController.java | 9 +- .../agendateam/service/AgendaTeamService.java | 51 ++++--- .../controller/AgendaControllerTest.java | 124 ++++++++++++++++++ .../agenda/service/AgendaServiceTest.java | 55 ++++++++ .../src/main/java/gg/data/agenda/Agenda.java | 50 ++++--- .../main/java/gg/data/agenda/AgendaTeam.java | 22 +--- .../gg/data/agenda/AgendaTeamProfile.java | 2 +- .../exception/GlobalExceptionHandler.java | 8 ++ .../gg/repo/agenda/AgendaTeamRepository.java | 6 + .../java/gg/utils/AgendaTestDataUtils.java | 44 +++++++ .../utils/fixture/agenda/AgendaFixture.java | 4 +- .../fixture/agenda/AgendaTeamFixture.java | 41 ++++++ .../agenda/AgendaTeamProfileFixture.java | 10 ++ 16 files changed, 376 insertions(+), 71 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index 1eac37949..93a5abccd 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -77,7 +77,7 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { .forEach(agentTeamProfile -> { String intraId = agentTeamProfile.getProfile().getIntraId(); agentTeamProfile.getAgendaTeam().leaveTeamMateAdmin(intraId); - agentTeamProfile.leaveTeam(); + agentTeamProfile.changeExistFalse(); }); // AgendaTeam 팀원 추가하기 diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index 64e309b28..ab339be3b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -71,7 +71,7 @@ public ResponseEntity> agendaListCurrent() { public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, @ModelAttribute @Valid AgendaCreateReqDto agendaCreateReqDto, @RequestParam(required = false) MultipartFile agendaPoster) { - if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024 * 2) { // 2MB + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024) { // 1MB throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); } UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, agendaPoster, user).getAgendaKey(); @@ -112,4 +112,13 @@ public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agend agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + @PatchMapping("/cancel") + public ResponseEntity agendaCancel(@RequestParam("agenda_key") UUID agendaKey, + @Login @Parameter(hidden = true) UserDto user) { + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + agenda.mustModifiedByHost(user.getIntraId()); + agendaService.cancelAgenda(agenda); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 7e761cb1e..295b74401 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -120,4 +120,12 @@ public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { } agenda.confirmAgenda(); } + + @Transactional + public void cancelAgenda(Agenda agenda) { + List attendTeams = agendaTeamRepository.findAllByAgendaAndStatus(agenda, + AgendaTeamStatus.OPEN, AgendaTeamStatus.CONFIRM); + attendTeams.forEach(agendaTeamService::leaveTeamAll); + agenda.cancelAgenda(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index b31eb29c3..6a98ddef9 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -99,11 +99,10 @@ public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto */ @PatchMapping("/cancel") public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - UUID teamKey = teamKeyReqDto.getTeamKey(); - - AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(agendaKey, teamKey); - agendaTeam.getAgenda().leaveTeam(LocalDateTime.now()); + @RequestBody @Valid TeamKeyReqDto teamKeyReqDto) { + AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); + agendaTeam.agendaTeamStatusMustBeOpen(); + agendaTeam.getAgenda().agendaStatusMustBeOpen(); if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { agendaTeamService.leaveTeamAll(agendaTeam); } else { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 63e207f66..735febd34 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -182,29 +182,39 @@ public void confirmTeam(UserDto user, UUID agendaKey, UUID teamKey) { /** * 아젠다 팀 찾기 - * @param agendaKey 아젠다 키, teamKey 팀 키 + * @param teamKey 팀 KEY */ @Transactional(readOnly = true) - public AgendaTeam getAgendaTeam(UUID agendaKey, UUID teamKey) { - Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) - .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - return agendaTeamRepository - .findByAgendaAndTeamKeyAndStatus(agenda, teamKey, OPEN, CONFIRM) + public AgendaTeam getAgendaTeam(UUID teamKey) { + return agendaTeamRepository.findByTeamKeyFetchJoin(teamKey) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); } + /** + * 팀장이 팀 나가기 + * @param agendaTeam 팀 + */ + @Transactional(propagation = Propagation.MANDATORY) + public void leaveTeamAll(AgendaTeam agendaTeam) { + List agendaTeamProfiles = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam); + agendaTeamProfiles.forEach(agendaTeamProfile -> leaveTeam(agendaTeam, agendaTeamProfile)); + agendaTeam.cancelTeam(); + } + /** * 아젠다 팀원 나가기 * @param agendaTeam 아젠다 팀, user 사용자 정보 */ @Transactional public void leaveTeamMate(AgendaTeam agendaTeam, UserDto user) { - AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) - .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - AgendaTeamProfile agendaTeamProfile = agendaTeamProfileRepository - .findByAgendaAndProfileAndIsExistTrue(agendaTeam.getAgenda(), agendaProfile) + List agendaTeamProfiles = agendaTeamProfileRepository + .findByAgendaTeamAndIsExistTrue(agendaTeam); + AgendaTeamProfile agendaTeamProfile = agendaTeamProfiles.stream() + .filter(profile -> profile.getProfile().getUserId().equals(user.getId())) + .findFirst() .orElseThrow(() -> new ForbiddenException(NOT_TEAM_MATE)); - leaveTeam(agendaTeamProfile); + leaveTeam(agendaTeam, agendaTeamProfile); } /** @@ -212,29 +222,14 @@ public void leaveTeamMate(AgendaTeam agendaTeam, UserDto user) { * @param agendaTeamProfile 팀 프로필 */ @Transactional(propagation = Propagation.MANDATORY) - public void leaveTeam(AgendaTeamProfile agendaTeamProfile) { - AgendaTeam agendaTeam = agendaTeamProfile.getAgendaTeam(); - agendaTeamProfile.leaveTeam(); + public void leaveTeam(AgendaTeam agendaTeam, AgendaTeamProfile agendaTeamProfile) { agendaTeam.leaveTeamMate(); + agendaTeamProfile.changeExistFalse(); if (agendaTeamProfile.getAgenda().getIsOfficial()) { ticketService.refundTicket(agendaTeamProfile); } } - /** - * 팀장이 팀 나가기 - * @param agendaTeam 팀 - */ - @Transactional(propagation = Propagation.MANDATORY) - public void leaveTeamAll(AgendaTeam agendaTeam) { - List teamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); - - for (AgendaTeamProfile teamProfile : teamProfiles) { - leaveTeam(teamProfile); - } - agendaTeam.leaveTeamLeader(); - } - /** * 아젠다 팀 공개 모집인 팀 목록 조회 * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 2ec720341..3d85cf0ef 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -56,10 +56,13 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; +import gg.utils.fixture.agenda.AgendaTeamProfileFixture; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -86,6 +89,12 @@ public class AgendaControllerTest { @Autowired private AgendaTeamFixture agendaTeamFixture; + @Autowired + private AgendaProfileFixture agendaProfileFixture; + + @Autowired + private AgendaTeamProfileFixture agendaTeamProfileFixture; + @Autowired private AgendaTestDataUtils agendaTestDataUtils; @@ -1188,4 +1197,119 @@ void confirmAgendaFailedWithNotOpenAgenda(AgendaStatus status) throws Exception .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("Agenda 취소하기") + class CancelAgenda { + @Test + @DisplayName("Agenda 취소하기 성공") + void cancelAgendaSuccess() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaTeamProfiles(user, AgendaStatus.OPEN); + + // when + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); + + // then + Agenda canceledAgenda = agendaRepository.findById(agenda.getId()) + .orElseThrow(() -> new BusinessException("cancelAgendaSuccess - 테스트 실패")); + assertThat(canceledAgenda.getStatus()).isEqualTo(AgendaStatus.CANCEL); + + em.createQuery("select at from AgendaTeam at where at.agenda = :agenda", + AgendaTeam.class).setParameter("agenda", agenda) + .getResultStream() + .forEach(agendaTeam -> assertThat(agendaTeam.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL)); + } + + @Test + @DisplayName("Agenda 취소하기 실패 - 존재하지 않는 Agenda인 경우") + void cancelAgendaFailedWithInvalidAgenda() throws Exception { + // given + // No Agenda + + // expected + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", UUID.randomUUID().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Agenda 취소하기 실패 - 개최자가 아닌 경우") + void cancelAgendaFailedWithNotHost() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaTeamProfiles(user, AgendaStatus.OPEN); + User other = testDataUtils.createNewUser(); + String otherAccessToken = testDataUtils.getLoginAccessTokenFromUser(other); + // expected + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + otherAccessToken)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("Agenda 취소하기 실패 - 이미 취소된 경우") + void cancelAgendaFailedWithAlreadyCancel() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaTeamProfiles(user, AgendaStatus.OPEN); + agenda.cancelAgenda(); + + // expected + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 취소하기 실패 - 이미 확정된 경우") + void cancelAgendaFailedWithAlreadyConfirm() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaTeamProfiles(user, AgendaStatus.OPEN); + agenda.confirmAgenda(); + + // expected + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 취소하기 실패 - 이미 종료된 경우") + void cancelAgendaFailedWithAlreadyFinish() throws Exception { + // given + Agenda agenda = agendaTestDataUtils.createAgendaTeamProfiles(user, AgendaStatus.OPEN); + agenda.confirmAgenda(); + agenda.finishAgenda(); + + // expected + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Agenda 취소하기 성공 - 참여한 팀이 없는 경우") + void cancelAgendaSuccessWithNoTeams() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.OPEN); + + // when + mockMvc.perform(patch("/agenda/cancel") + .param("agenda_key", agenda.getAgendaKey().toString()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); + + // then + Agenda result = agendaRepository.findById(agenda.getId()) + .orElseThrow(() -> new BusinessException("cancelAgendaSuccessWithNoTeams - 테스트 실패")); + assertThat(result.getStatus()).isEqualTo(AgendaStatus.CANCEL); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 355c5461d..a0bb027e8 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -371,4 +371,59 @@ void confirmAgendaFailedWithAlreadyConfirm(AgendaStatus status) { verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(agenda, AgendaTeamStatus.OPEN); } } + + @Nested + @DisplayName("Agenda 취소하기") + class CancelAgenda { + @Test + @DisplayName("Agenda 취소하기 성공") + void cancelAgendaSuccess() { + // given + Agenda agenda = Agenda.builder().status(AgendaStatus.OPEN).build(); + List agendaTeams = List.of(mock(AgendaTeam.class)); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any(), any())) + .thenReturn(agendaTeams); + doNothing().when(agendaTeamService).leaveTeamAll(any()); + + // when + agendaService.cancelAgenda(agenda); + + // then + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(any(), any(), any()); + verify(agendaTeamService, times(agendaTeams.size())).leaveTeamAll(any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CANCEL); + } + + @ParameterizedTest + @EnumSource(value = AgendaStatus.class, names = {"CONFIRM", "FINISH", "CANCEL"}) + @DisplayName("Agenda 취소하기 실패 - AgendaStatus가 OPEN이 아닌 경우") + void cancelAgendaFailedWithNotOpen(AgendaStatus status) { + // given + Agenda agenda = Agenda.builder().status(status).build(); + List agendaTeam = List.of(mock(AgendaTeam.class)); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any(), any())) + .thenReturn(agendaTeam); + doNothing().when(agendaTeamService).leaveTeamAll(any()); + + // expected + assertThrows(InvalidParameterException.class, + () -> agendaService.cancelAgenda(agenda)); + } + + @Test + @DisplayName("Agenda 취소하기 성공 - AgendaTeam이 없는 경우") + void cancelAgendaSuccessWithNoAgendaTeam() { + // given + Agenda agenda = Agenda.builder().status(AgendaStatus.OPEN).build(); + when(agendaTeamRepository.findAllByAgendaAndStatus(any(), any(), any())) + .thenReturn(List.of()); + + // when + agendaService.cancelAgenda(agenda); + + verify(agendaTeamRepository, times(1)).findAllByAgendaAndStatus(any(), any(), any()); + verify(agendaTeamService, never()).leaveTeamAll(any()); + assertThat(agenda.getStatus()).isEqualTo(AgendaStatus.CANCEL); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 0e695c75f..71ca03055 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -121,28 +121,17 @@ public Agenda(Long id, String title, String content, LocalDateTime deadline, } public void confirmAgenda() { - if (this.status == AgendaStatus.FINISH) { - throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); - } - if (this.status == AgendaStatus.CANCEL) { - throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); - } - if (this.status == AgendaStatus.CONFIRM) { - throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); - } + this.agendaStatusMustBeOpen(); this.status = AgendaStatus.CONFIRM; } + public void cancelAgenda() { + this.agendaStatusMustBeOpen(); + this.status = AgendaStatus.CANCEL; + } + public void finishAgenda() { - if (this.status == AgendaStatus.OPEN) { - throw new InvalidParameterException(AGENDA_DOES_NOT_CONFIRM); - } - if (this.status == AgendaStatus.CANCEL) { - throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); - } - if (this.status == AgendaStatus.FINISH) { - throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); - } + this.agendaStatusMustBeConfirm(); this.status = AgendaStatus.FINISH; } @@ -263,6 +252,7 @@ public void updateTeam(Location location, LocalDateTime now) { public void leaveTeam(LocalDateTime now) { mustStatusOpen(); mustBeforeDeadline(now); + this.currentTeam--; } private void mustBeWithinLocation(Location location) { @@ -304,4 +294,28 @@ public void mustModifiedByHost(String userIntraId) { } throw new ForbiddenException(AGENDA_MODIFICATION_FORBIDDEN); } + + public void agendaStatusMustBeOpen() { + if (this.status == AgendaStatus.FINISH) { + throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); + } + if (this.status == AgendaStatus.CANCEL) { + throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); + } + if (this.status == AgendaStatus.CONFIRM) { + throw new InvalidParameterException(AGENDA_ALREADY_CONFIRMED); + } + } + + public void agendaStatusMustBeConfirm() { + if (this.status == AgendaStatus.OPEN) { + throw new InvalidParameterException(AGENDA_DOES_NOT_CONFIRM); + } + if (this.status == AgendaStatus.CANCEL) { + throw new InvalidParameterException(AGENDA_ALREADY_CANCELED); + } + if (this.status == AgendaStatus.FINISH) { + throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index eed842862..a3c57c143 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -3,6 +3,7 @@ import static gg.data.agenda.type.AgendaTeamStatus.*; import static gg.utils.exception.ErrorCode.*; +import java.time.LocalDateTime; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -107,24 +108,13 @@ public void confirm() { this.status = CONFIRM; } - public void leaveTeamLeader() { - if (this.status == CANCEL) { - throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); - } - if (this.status == CONFIRM) { - throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); - } + public void cancelTeam() { this.status = CANCEL; this.mateCount = 0; + this.agenda.leaveTeam(LocalDateTime.now()); } public void leaveTeamMate() { - if (this.status == CANCEL) { - throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); - } - if (this.status == CONFIRM) { - throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); - } this.mateCount--; } @@ -189,10 +179,12 @@ public void updateLocation(Location location, List profiles) this.location = location; } - public void cancelTeam() { + public void agendaTeamStatusMustBeOpen() { + if (this.status == CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } if (this.status == CONFIRM) { throw new BusinessException(AGENDA_TEAM_ALREADY_CONFIRM); } - this.status = CANCEL; } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java index 3def25008..ea5183a63 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeamProfile.java @@ -54,7 +54,7 @@ public AgendaTeamProfile(AgendaTeam agendaTeam, Agenda agenda, AgendaProfile pro this.isExist = true; } - public void leaveTeam() { + public void changeExistFalse() { this.isExist = false; } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/exception/GlobalExceptionHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/exception/GlobalExceptionHandler.java index 9e0365043..92d550dbb 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/exception/GlobalExceptionHandler.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/exception/GlobalExceptionHandler.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; @@ -141,4 +142,11 @@ protected ResponseEntity handleException(BusinessException exception) { ErrorResponse response = new ErrorResponse(exception.getErrorCode()); return new ResponseEntity<>(response, HttpStatus.valueOf(response.getStatus())); } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + protected ResponseEntity handleException(MaxUploadSizeExceededException exception) { + String message = "File Size Exceeded: maximum permitted size of 1MB"; + log.error(message); + return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); + } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 6d1f37018..2d1e48888 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -27,12 +27,18 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.teamKey = :teamKey") Optional findByTeamKey(UUID teamKey); + @Query("SELECT a FROM AgendaTeam a JOIN FETCH a.agenda WHERE a.teamKey = :teamKey") + Optional findByTeamKeyFetchJoin(UUID teamKey); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :name AND a.status = :status") Optional findByAgendaAndNameAndStatus(Agenda agenda, String name, AgendaTeamStatus status); @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status") List findAllByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND (a.status = :status1 OR a.status = :status2)") + List findAllByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status1, AgendaTeamStatus status2); + @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java index 7532cece9..505e4de61 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -1,13 +1,24 @@ package gg.utils; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + import org.springframework.stereotype.Component; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; +import gg.data.agenda.type.Location; +import gg.data.user.User; import gg.utils.fixture.agenda.AgendaAnnouncementFixture; import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; +import gg.utils.fixture.agenda.AgendaTeamProfileFixture; import lombok.RequiredArgsConstructor; @Component @@ -20,6 +31,13 @@ public class AgendaTestDataUtils { private final AgendaTeamFixture agendaTeamFixture; + private final AgendaProfileFixture agendaProfileFixture; + + private final AgendaTeamProfileFixture agendaTeamProfileFixture; + + @PersistenceContext + private final EntityManager em; + public Agenda createAgendaAndAnnouncements(int size) { Agenda agenda = agendaFixture.createAgenda(); agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, size / 2, true); @@ -32,4 +50,30 @@ public Agenda createAgendaAndAgendaTeams(String intraId, int size, AgendaStatus agendaTeamFixture.createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, size); return agenda; } + + public Agenda createAgendaTeamProfiles(User user, AgendaStatus status) { + AgendaProfile host = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); + Agenda agenda = agendaFixture.createAgenda(host.getIntraId(), status); + for (int i = 0; i < 3; i++) { + AgendaProfile leader = agendaProfileFixture.createAgendaProfile(); + List mates = agendaProfileFixture.createAgendaProfileList(3); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, leader, AgendaTeamStatus.OPEN); + agendaTeamProfileFixture.createAgendaTeamProfileList(agenda, team, mates); + } + for (int i = 0; i < 3; i++) { + AgendaProfile leader = agendaProfileFixture.createAgendaProfile(); + List mates = agendaProfileFixture.createAgendaProfileList(3); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, leader, AgendaTeamStatus.OPEN); + agendaTeamProfileFixture.createAgendaTeamProfileList(agenda, team, mates); + team.confirm(); + } + for (int i = 0; i < 3; i++) { + AgendaProfile leader = agendaProfileFixture.createAgendaProfile(); + List mates = agendaProfileFixture.createAgendaProfileList(3); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, leader, AgendaTeamStatus.OPEN); + agendaTeamProfileFixture.createAgendaTeamProfileList(agenda, team, mates); + team.cancelTeam(); + } + return agenda; + } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java index c87625451..c99385bf5 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -71,10 +71,10 @@ public Agenda createAgenda(String intraId, AgendaStatus status) { .startTime(LocalDateTime.now().plusDays(5)) .endTime(LocalDateTime.now().plusDays(6)) .minTeam(2) - .maxTeam(5) + .maxTeam(20) .currentTeam(0) .minPeople(1) - .maxPeople(5) + .maxPeople(10) .status(status) .posterUri("posterUri") .hostIntraId(intraId) diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java index f30b34e44..21064c62f 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamFixture.java @@ -9,9 +9,13 @@ import java.util.List; import java.util.UUID; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + import org.springframework.stereotype.Component; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; @@ -32,6 +36,9 @@ public class AgendaTeamFixture { private final AgendaTeamRepository agendaTeamRepository; + @PersistenceContext + private final EntityManager em; + public AgendaTeam createAgendaTeam(Agenda agenda) { AgendaTeam agendaTeam = AgendaTeam.builder() .agenda(agenda) @@ -48,6 +55,40 @@ public AgendaTeam createAgendaTeam(Agenda agenda) { return agendaTeamRepository.save(agendaTeam); } + public AgendaTeam createAgendaTeam(Agenda agenda, AgendaProfile profile) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId(profile.getIntraId()) + .status(OPEN) + .location(MIX) + .mateCount(1) + .awardPriority(1) + .isPrivate(false) + .build(); + return agendaTeamRepository.save(agendaTeam); + } + + public AgendaTeam createAgendaTeam(Agenda agenda, AgendaProfile profile, AgendaTeamStatus status) { + AgendaTeam agendaTeam = AgendaTeam.builder() + .agenda(agenda) + .teamKey(UUID.randomUUID()) + .name("name") + .content("content") + .leaderIntraId(profile.getIntraId()) + .status(status) + .location(MIX) + .mateCount(1) + .awardPriority(1) + .isPrivate(false) + .build(); + AgendaTeam savedTeam = agendaTeamRepository.save(agendaTeam); + agendaTeamProfileFixture.createAgendaTeamProfile(agenda, savedTeam, profile); + return savedTeam; + } + public AgendaTeam createAgendaTeam(Agenda agenda, User user) { AgendaTeam agendaTeam = AgendaTeam.builder() .agenda(agenda) diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java index d647554c4..d5af5054d 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaTeamProfileFixture.java @@ -1,5 +1,8 @@ package gg.utils.fixture.agenda; +import java.util.List; +import java.util.stream.Collectors; + import org.springframework.stereotype.Component; import gg.data.agenda.Agenda; @@ -35,4 +38,11 @@ public AgendaTeamProfile createAgendaTeamProfile(AgendaTeam team, AgendaProfile .build(); return agendaTeamProfileRepository.save(agendaTeamProfile); } + + public List createAgendaTeamProfileList(Agenda agenda, + AgendaTeam team, List mates) { + return mates.stream() + .map(mate -> createAgendaTeamProfile(agenda, team, mate)) + .collect(Collectors.toList()); + } } From 2fd29686c349649e1b5c27b2cb8b0531d2e7bdee Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:32:46 +0900 Subject: [PATCH 068/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Agenda?= =?UTF-8?q?=20Admin=20=EC=A1=B0=ED=9A=8C=20Query=20Parameter=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#948=20(#949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaAdminController.java | 3 +- .../AgendaAnnouncementAdminController.java | 3 +- .../controller/AgendaTeamAdminController.java | 9 +++--- .../request/AgendaTeamKeyReqDto.java | 23 --------------- .../controller/AgendaAdminControllerTest.java | 9 +++--- ...AgendaAnnouncementAdminControllerTest.java | 18 ++++-------- .../AgendaTeamAdminControllerTest.java | 28 ++++++------------- 7 files changed, 26 insertions(+), 67 deletions(-) delete mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index 00c5a6ed4..f56ae339a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -41,7 +40,7 @@ public class AgendaAdminController { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공")}) @GetMapping("/request/list") - public ResponseEntity> agendaList(@RequestBody @Valid PageRequestDto pageDto) { + public ResponseEntity> agendaList(@ModelAttribute @Valid PageRequestDto pageDto) { int page = pageDto.getPage(); int size = pageDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java index 518e1132e..d5d8505c1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,7 +34,7 @@ public class AgendaAnnouncementAdminController { @GetMapping() public ResponseEntity> agendaAnnouncementList( - @RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid PageRequestDto pageRequest) { + @RequestParam("agenda_key") UUID agendaKey, @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index 5a97fa225..833cbd02d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -12,13 +12,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamResDto; @@ -37,7 +37,7 @@ public class AgendaTeamAdminController { @GetMapping("/list") public ResponseEntity> agendaTeamList(@RequestParam("agenda_key") UUID agendaKey, - @RequestBody @Valid PageRequestDto pageRequestDto) { + @ModelAttribute @Valid PageRequestDto pageRequestDto) { int page = pageRequestDto.getPage(); int size = pageRequestDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); @@ -49,9 +49,8 @@ public ResponseEntity> agendaTeamList(@RequestParam("agen } @GetMapping - public ResponseEntity agendaTeamDetail( - @RequestBody @Valid AgendaTeamKeyReqDto agendaTeamKeyReqDto) { - AgendaTeam agendaTeam = agendaTeamAdminService.getAgendaTeamByTeamKey(agendaTeamKeyReqDto.getTeamKey()); + public ResponseEntity agendaTeamDetail(@RequestParam("team_key") UUID agendaTeamKey) { + AgendaTeam agendaTeam = agendaTeamAdminService.getAgendaTeamByTeamKey(agendaTeamKey); List participants = agendaTeamAdminService.getAgendaProfileListByAgendaTeam(agendaTeam); AgendaTeamDetailResDto agendaTeamDetailResDto = AgendaTeamDetailResDto.MapStruct.INSTANCE .toDto(agendaTeam, participants); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java deleted file mode 100644 index 937611b77..000000000 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/request/AgendaTeamKeyReqDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package gg.agenda.api.admin.agendateam.controller.request; - -import java.util.UUID; - -import javax.validation.constraints.NotNull; - -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgendaTeamKeyReqDto { - - @NotNull - private UUID teamKey; - - @Builder - public AgendaTeamKeyReqDto(UUID teamKey) { - this.teamKey = teamKey; - } -} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index 2ffafa170..d2dccf792 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -105,13 +105,12 @@ void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.OPEN)); agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.FINISH)); agendas.addAll(agendaMockData.createNonOfficialAgendaList(5, AgendaStatus.CANCEL)); - PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/admin/request/list") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON).content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); @@ -131,12 +130,12 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { int page = 1; int size = 10; PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/admin/request/list") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON).content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java index 31904d27d..67cad42dd 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java @@ -92,15 +92,13 @@ void getAgendaAnnouncementAdminSuccess(int page) throws Exception { Agenda agenda = agendaFixture.createAgenda(); List announcements = agendaAnnouncementFixture.createAgendaAnnouncementList(agenda, 37); - PageRequestDto pageDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageDto); // when String response = mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); @@ -121,15 +119,13 @@ void getAgendaAnnouncementAdminSuccessWithNoContent() throws Exception { int page = 1; int size = 10; Agenda agenda = agendaFixture.createAgenda(); - PageRequestDto pageDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageDto); // when String response = mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); @@ -144,15 +140,13 @@ void getAgendaAnnouncementAdminFailedWithNoAgenda() throws Exception { // given int page = 1; int size = 10; - PageRequestDto pageDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageDto); // expected mockMvc.perform(get("/agenda/admin/announcement") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", UUID.randomUUID().toString()) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isNotFound()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index 124b7e63f..34f87f7bc 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -27,7 +27,6 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; import gg.admin.repo.agenda.AgendaTeamProfileAdminRepository; -import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamKeyReqDto; import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamMateReqDto; import gg.agenda.api.admin.agendateam.controller.request.AgendaTeamUpdateDto; import gg.agenda.api.admin.agendateam.controller.response.AgendaTeamDetailResDto; @@ -114,15 +113,13 @@ void getAgendaTeamListAdminSuccess(int page) throws Exception { Agenda agenda = agendaFixture.createAgenda(2, 50, 1, 10); List teams = agendaTeamFixture .createAgendaTeamList(agenda, AgendaTeamStatus.CONFIRM, total); - PageRequestDto pageRequestDto = new PageRequestDto(page, size); - String request = objectMapper.writeValueAsString(pageRequestDto); // when String response = mockMvc.perform(get("/agenda/admin/team/list") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaTeamResDto[] result = objectMapper.readValue(response, AgendaTeamResDto[].class); @@ -140,15 +137,15 @@ void getAgendaTeamListAdminSuccess(int page) throws Exception { @DisplayName("Admin AgendaTeam 전체 조회 실패 - Agenda 없음") void getAgendaTeamListAdminFailedWithNoAgenda() throws Exception { // given - PageRequestDto pageRequestDto = new PageRequestDto(1, 10); - String request = objectMapper.writeValueAsString(pageRequestDto); + int page = 1; + int size = 10; // expected mockMvc.perform(get("/agenda/admin/team/list") .header("Authorization", "Bearer " + accessToken) .param("agenda_key", UUID.randomUUID().toString()) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) .andExpect(status().isNotFound()); } } @@ -165,13 +162,11 @@ void getAgendaTeamDetailAdminSuccess() throws Exception { List profiles = agendaProfileFixture.createAgendaProfileList(5); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); - String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); // when String response = mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("team_key", team.getTeamKey().toString())) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaTeamDetailResDto result = objectMapper.readValue(response, AgendaTeamDetailResDto.class); @@ -200,13 +195,10 @@ void getAgendaTeamDetailAdminFailedWithNoTeamKey() throws Exception { @DisplayName("Admin AgendaTeam 상세 조회 실패 - 존재하지 않는 Team") void getAgendaTeamDetailAdminFailedWithNotFoundTeam() throws Exception { // given - String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(UUID.randomUUID())); - // expected mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("team_key", UUID.randomUUID().toString())) .andExpect(status().isNotFound()); } @@ -216,13 +208,11 @@ void getAgendaTeamDetailAdminSuccessWithNoTeamMates() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); - String request = objectMapper.writeValueAsString(new AgendaTeamKeyReqDto(team.getTeamKey())); // when String response = mockMvc.perform(get("/agenda/admin/team") .header("Authorization", "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .param("team_key", team.getTeamKey().toString())) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); AgendaTeamDetailResDto result = objectMapper.readValue(response, AgendaTeamDetailResDto.class); From 80669c1eff1a032840f079fa096c4c16849e8cd0 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:08:27 +0900 Subject: [PATCH 069/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8B=B0?= =?UTF-8?q?=EC=BC=93=20=EB=B0=9C=EA=B8=89=20=EC=99=84=EB=A3=8C=20API=20#90?= =?UTF-8?q?9=20(#942)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/application.yml | 3 + gg-agenda-api/build.gradle | 3 + .../service/AgendaProfileService.java | 9 +- .../controller/AgendaTeamController.java | 9 +- .../controller/request/TeamUpdateReqDto.java | 4 + .../ticket/controller/TicketController.java | 32 ++++++ .../user/ticket/service/TicketService.java | 105 ++++++++++++++++++ .../java/gg/agenda/api/AgendaMockData.java | 1 + .../agendateam/AgendaTeamControllerTest.java | 90 ++++----------- .../api/user/ticket/TicketControllerTest.java | 51 +++++++-- .../src/test/resources/application.yml | 3 + gg-auth/build.gradle | 2 + .../main/java/gg/auth/FortyTwoAuthUtil.java | 103 +++++++++++++++++ .../java/gg/auth/utils/RefreshTokenUtil.java | 58 ++++++++++ .../java/gg/data/agenda/AgendaProfile.java | 6 +- .../main/java/gg/data/agenda/Auth42Token.java | 22 ++++ .../src/main/java/gg/data/agenda/Ticket.java | 17 +++ .../jwt/utils/TokenAuthenticationFilter.java | 2 +- .../jwt/utils/TokenHeaders.java | 2 +- .../security/config/SecurityConfig.java | 3 +- .../OAuthAuthenticationSuccessHandler.java | 8 +- .../OauthAuthenticationFailureHandler.java | 2 +- ...izationRequestBasedOnCookieRepository.java | 2 +- .../service/CustomOAuth2UserService.java | 15 +-- .../service/sns/SlackPartybotService.java | 2 +- .../noti/service/sns/SlackbotService.java | 2 +- .../user/user/controller/UserController.java | 4 +- .../service/UserAuthenticationService.java | 2 +- .../resources/db/migration/V3__agenda.sql | 1 + .../rank/controller/RankV2ControllerTest.java | 2 +- .../TournamentControllerMvcTest.java | 2 +- .../UserAuthenticationServiceUnitTest.java | 2 +- .../src/test/resources/application.yml | 5 +- .../src/test/resources/application.yml | 3 + .../repo/user/Auth42TokenRedisRepository.java | 40 +++++++ .../gg/repo/user}/JwtRedisRepository.java | 2 +- gg-utils/build.gradle | 2 +- .../java/gg}/utils/ApplicationYmlRead.java | 2 +- .../src/main/java/gg/utils/DateTimeUtil.java | 18 +++ .../java/gg/utils}/cookie/CookieUtil.java | 4 +- .../java/gg/utils/exception/ErrorCode.java | 5 + .../main/java/gg}/utils/external/ApiUtil.java | 36 +++++- .../fixture/agenda/AgendaProfileFixture.java | 3 + .../utils/fixture/agenda/TicketFixture.java | 4 +- 44 files changed, 576 insertions(+), 117 deletions(-) create mode 100644 gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java create mode 100644 gg-auth/src/main/java/gg/auth/utils/RefreshTokenUtil.java create mode 100644 gg-data/src/main/java/gg/data/agenda/Auth42Token.java rename gg-pingpong-api/src/main/java/gg/pingpong/api/global/{security => }/jwt/utils/TokenAuthenticationFilter.java (98%) rename gg-pingpong-api/src/main/java/gg/pingpong/api/global/{security => }/jwt/utils/TokenHeaders.java (74%) create mode 100644 gg-repo/src/main/java/gg/repo/user/Auth42TokenRedisRepository.java rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/repository => gg-repo/src/main/java/gg/repo/user}/JwtRedisRepository.java (93%) rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global => gg-utils/src/main/java/gg}/utils/ApplicationYmlRead.java (93%) create mode 100644 gg-utils/src/main/java/gg/utils/DateTimeUtil.java rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/security => gg-utils/src/main/java/gg/utils}/cookie/CookieUtil.java (95%) rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global => gg-utils/src/main/java/gg}/utils/external/ApiUtil.java (65%) diff --git a/gg-admin-repo/src/test/resources/application.yml b/gg-admin-repo/src/test/resources/application.yml index f28374ce9..7bad2a46d 100644 --- a/gg-admin-repo/src/test/resources/application.yml +++ b/gg-admin-repo/src/test/resources/application.yml @@ -72,6 +72,9 @@ info: image: defaultUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/small_default.jpeg' itemNotFoundUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/not_found.svg' + web: + coalitionUrl: 'https://api.intra.42.fr/v2/users/{id}/coalitions' + pointHistoryUrl: 'https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id' constant: allowedMinimalStartDays: 2 diff --git a/gg-agenda-api/build.gradle b/gg-agenda-api/build.gradle index 4f8e11d62..e8a4bdd9d 100644 --- a/gg-agenda-api/build.gradle +++ b/gg-agenda-api/build.gradle @@ -19,6 +19,9 @@ dependencies { /* StringUtils */ implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' testImplementation testFixtures(project(':gg-utils')) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java index 279b35456..2f30b1d8a 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java @@ -2,9 +2,8 @@ import static gg.utils.exception.ErrorCode.*; -import javax.transaction.Transactional; - import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendaprofile.controller.request.AgendaProfileChangeReqDto; import gg.data.agenda.AgendaProfile; @@ -36,4 +35,10 @@ public void modifyAgendaProfile(Long userId, AgendaProfileChangeReqDto reqDto) { agendaProfile.updateProfile(reqDto.getUserContent(), reqDto.getUserGithub()); agendaProfileRepository.save(agendaProfile); } + + @Transactional(readOnly = true) + public AgendaProfile getAgendaProfile(Long userId) { + return agendaProfileRepository.findByUserId(userId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 6a98ddef9..878fc56c0 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -65,7 +65,7 @@ public ResponseEntity> myTeamSimpleDetails( */ @GetMapping public ResponseEntity agendaTeamDetails(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { TeamDetailsResDto teamDetailsResDto = agendaTeamService.detailsAgendaTeam(user, agendaKey, teamKeyReqDto); return ResponseEntity.ok(teamDetailsResDto); } @@ -88,7 +88,7 @@ public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Lo */ @PatchMapping("/confirm") public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { agendaTeamService.confirmTeam(user, agendaKey, teamKeyReqDto.getTeamKey()); return ResponseEntity.ok().build(); } @@ -99,7 +99,8 @@ public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto */ @PatchMapping("/cancel") public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamKeyReqDto teamKeyReqDto) { + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + UUID teamKey = teamKeyReqDto.getTeamKey(); AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); agendaTeam.agendaTeamStatusMustBeOpen(); agendaTeam.getAgenda().agendaStatusMustBeOpen(); @@ -145,7 +146,7 @@ public ResponseEntity> confirmTeamList(@ModelAttribute @ */ @PostMapping("/join") public ResponseEntity attendTeamModify(@Parameter(hidden = true) @Login UserDto user, - @RequestBody @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { agendaTeamService.modifyAttendTeam(user, teamKeyReqDto, agendaKey); return ResponseEntity.status(HttpStatus.CREATED).build(); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java index 7610c9b70..810d4afcb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/request/TeamUpdateReqDto.java @@ -4,6 +4,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import lombok.Builder; import lombok.Getter; @@ -15,12 +16,15 @@ public class TeamUpdateReqDto { @NotNull private UUID teamKey; @NotBlank + @Size(max = 30) private String teamName; @NotBlank + @Size(max = 500) private String teamContent; @NotNull private Boolean teamIsPrivate; @NotBlank + @Size(max = 10) private String teamLocation; @Builder diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index c800d2be0..3e94f68f4 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -2,6 +2,7 @@ import java.util.List; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import org.springframework.data.domain.PageRequest; @@ -9,8 +10,12 @@ import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -20,7 +25,9 @@ import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.utils.cookie.CookieUtil; import gg.utils.dto.PageRequestDto; +import gg.utils.exception.user.TokenNotValidException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -28,6 +35,7 @@ @RequiredArgsConstructor @RequestMapping("/agenda/ticket") public class TicketController { + private final CookieUtil cookieUtil; private final TicketService ticketService; /** @@ -50,6 +58,30 @@ public ResponseEntity ticketCountFind(@Parameter(hidden = tru return ResponseEntity.ok().body(new TicketCountResDto(ticketCount)); } + /** + * 티켓 승인/거절 + * @param user 사용자 정보 + */ + @PatchMapping + public ResponseEntity ticketApproveModify(@Parameter(hidden = true) @Login UserDto user, + HttpServletResponse response) { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + + try { + ticketService.modifyTicketApprove(user, authentication); + } catch (TokenNotValidException e) { + cookieUtil.deleteCookie(response, "refresh_token"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + return ResponseEntity.noContent().build(); + } + + /** + * 티켓 이력 조회 + * @param user 사용자 정보 + * @param pageRequest 페이지 정보 + */ @GetMapping("/history") public ResponseEntity> ticketHistoryList(@Parameter(hidden = true) @Login UserDto user, @ModelAttribute @Valid PageRequestDto pageRequest) { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index ac9dc43e2..3601c3b1b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -2,17 +2,28 @@ import static gg.utils.exception.ErrorCode.*; +import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.HttpClientErrorException; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; +import gg.auth.FortyTwoAuthUtil; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -21,17 +32,28 @@ import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.TicketRepository; +import gg.utils.DateTimeUtil; import gg.utils.exception.custom.DuplicationException; import gg.utils.exception.custom.NotExistException; +import gg.utils.external.ApiUtil; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class TicketService { + private final ApiUtil apiUtil; + private final FortyTwoAuthUtil fortyTwoAuthUtil; private final TicketRepository ticketRepository; private final AgendaRepository agendaRepository; + private final AgendaProfileService agendaProfileService; private final AgendaProfileRepository agendaProfileRepository; + @Value("${info.web.pointHistoryUrl}") + private String pointHistoryUrl; + + private static final String selfDonation = "Provided points to the pool"; + private static final String autoDonation = "correction points trimming weekly"; + @Transactional(propagation = Propagation.MANDATORY) public void refundTicket(AgendaTeamProfile changedTeamProfile) { Ticket.createRefundedTicket(changedTeamProfile); @@ -65,6 +87,89 @@ public int findTicketCount(UserDto user) { return tickets.size(); } + /** + * 티켓 승인/거절 + * @param user 사용자 정보 + */ + @Transactional + public void modifyTicketApprove(UserDto user, Authentication authentication) { + AgendaProfile profile = agendaProfileService.getAgendaProfile(user.getId()); + Ticket setUpTicket = getSetUpTicket(profile); + + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken)authentication; + OAuth2AuthorizedClient oAuth2AuthorizedClient = fortyTwoAuthUtil.getOAuth2AuthorizedClient(oauthToken); + + List> pointHistory = getPointHistory(profile, oAuth2AuthorizedClient, authentication); + + processTicketApproval(profile, setUpTicket, pointHistory); + } + + /** + * 티켓 setup 조회 + * @param profile AgendaProfile + * @return 티켓 설정 + */ + private Ticket getSetUpTicket(AgendaProfile profile) { + return ticketRepository.findByAgendaProfileAndIsApprovedFalse(profile) + .orElseThrow(() -> new NotExistException(NOT_SETUP_TICKET)); + } + + /** + * 포인트 이력 조회 + * @param profile AgendaProfile + * @param client OAuth2AuthorizedClient + * @param authentication Authentication + * @return 포인트 이력 + */ + private List> getPointHistory(AgendaProfile profile, OAuth2AuthorizedClient client, + Authentication authentication) { + String url = pointHistoryUrl.replace("{id}", profile.getFortyTwoId().toString()); + ParameterizedTypeReference>> responseType = new ParameterizedTypeReference<>() { + }; + + try { + return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { + client = fortyTwoAuthUtil.refreshAccessToken(client, authentication); + return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + } + throw e; + } + } + + /** + * 티켓 승인 처리 + * @param profile AgendaProfile + * @param setUpTicket Ticket + * @param pointHistory 포인트 이력 + */ + private void processTicketApproval(AgendaProfile profile, Ticket setUpTicket, + List> pointHistory) { + LocalDateTime cutoffTime = setUpTicket.getCreatedAt(); + + int ticketSum = pointHistory.stream() + .takeWhile( + history -> DateTimeUtil.convertToSeoulDateTime(history.get("created_at")).isAfter(cutoffTime)) + .filter(history -> { + String reason = history.get("reason"); + return reason.contains(selfDonation) || reason.contains(autoDonation); + }) + .mapToInt(history -> Integer.parseInt(history.get("sum")) * (-1)) + .sum(); + + if (ticketSum == 0) { + throw new NotExistException(POINT_HISTORY_NOT_FOUND); + } + + if (ticketSum >= 2) { + ticketRepository.save(Ticket.createApproveTicket(profile)); + } + + setUpTicket.changeIsApproved(); + ticketRepository.save(setUpTicket); + } + /** * 티켓 이력 조회 * @param user 사용자 정보 diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index e7167a5c1..22005c090 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -497,6 +497,7 @@ public AgendaProfile createAgendaProfile(User user, Location location) { .location(location) .intraId(user.getIntraId()) .userId(user.getId()) + .fortyTwoId(user.getId()) .build(); return agendaProfileRepository.save(agendaProfile); } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 3f568e298..c08bcda35 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -462,14 +462,12 @@ public void teamDetailsGetSuccess() throws Exception { AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); agendaMockData.createAgendaTeamProfile(team, gyeongsanUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when String res = mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -491,14 +489,12 @@ public void teamDetailsGetSuccess() throws Exception { @DisplayName("404 agenda가 없음으로 인한 팀 상세 정보 조회 실패") public void teamDetailsGetFailByNoAgenda() throws Exception { //given - TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", UUID.randomUUID().toString()) - .content(content) + .param("teamKey", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -515,7 +511,7 @@ public void teamDetailsGetFailByNoTeam() throws Exception { get("/agenda/team") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -526,14 +522,12 @@ public void teamDetailsGetFailByConfirmTeam() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CONFIRM); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -545,14 +539,12 @@ public void teamDetailsGetFailByCancelTeam() throws Exception { Agenda agenda = agendaMockData.createAgenda(FINISH); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, MIX, AgendaTeamStatus.CANCEL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( get("/agenda/team") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -672,14 +664,12 @@ public void confirmTeamSuccess() throws Exception { Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); // then @@ -693,14 +683,12 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); UUID noTeamKey = UUID.randomUUID(); - TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) - .content(content) + .param("teamKey", noTeamKey.toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -710,14 +698,12 @@ public void noAgendaFail() throws Exception { public void noTeamFail() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -732,14 +718,12 @@ public void notValidTeamHostFail() throws Exception { User notHostUser = testDataUtils.createNewUser(); String notHostUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notHostUser); agendaMockData.createAgendaTeamProfile(team, agendaMockData.createAgendaProfile(notHostUser, SEOUL)); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + notHostUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -751,14 +735,12 @@ public void notValidTeamStatusFail() throws Exception { Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CANCEL); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/confirm") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -866,14 +848,12 @@ public void leaveTeamMateSuccess() throws Exception { AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when mockMvc.perform( patch("/agenda/team/cancel") .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); // then @@ -898,14 +878,12 @@ public void leaveTeamLeaderSuccess() throws Exception { AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); AgendaTeamProfile atpLeader = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when mockMvc.perform( patch("/agenda/team/cancel") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); // then @@ -936,14 +914,12 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); UUID noTeamKey = UUID.randomUUID(); - TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/cancel") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", noAgendaKey.toString()) - .content(content) + .param("teamKey", noTeamKey.toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -953,14 +929,12 @@ public void noAgendaFail() throws Exception { public void noTeamFail() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); - TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/cancel") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -1049,14 +1023,12 @@ public void notTeamMateFail() throws Exception { Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/cancel") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -1234,14 +1206,12 @@ public void applyTeamSuccess() throws Exception { Agenda agenda = agendaFixture.createAgenda(SEOUL); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); ticketFixture.createTicket(seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()); // then @@ -1262,14 +1232,12 @@ public void noAgendaProfileFail() throws Exception { ticketFixture.createTicket(seoulUserAgendaProfile); User noProfileUser = testDataUtils.createNewUser(); String noProfileUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(noProfileUser); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + noProfileUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -1280,14 +1248,12 @@ public void noAgendaFail() throws Exception { //given UUID noAgendaKey = UUID.randomUUID(); UUID noTeamKey = UUID.randomUUID(); - TeamKeyReqDto req = new TeamKeyReqDto(noTeamKey); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", noAgendaKey.toString()) - .content(content) + .param("teamKey", noTeamKey.toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -1298,14 +1264,12 @@ public void noTeamFail() throws Exception { //given Agenda agenda = agendaFixture.createAgenda(SEOUL); ticketFixture.createTicket(seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(UUID.randomUUID()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", UUID.randomUUID().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -1394,14 +1358,12 @@ public void alreadyApplyFail() throws Exception { AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); ticketFixture.createTicket(seoulUserAgendaProfile); agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -1412,14 +1374,12 @@ public void noTicketFail() throws Exception { //given Agenda agenda = agendaFixture.createAgenda(SEOUL); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); } @@ -1431,14 +1391,12 @@ public void notValidTeamStatus() throws Exception { Agenda agenda = agendaFixture.createAgenda(SEOUL); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL, AgendaTeamStatus.CANCEL); ticketFixture.createTicket(seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @@ -1450,14 +1408,12 @@ public void notValidTeamStatusConfirm() throws Exception { Agenda agenda = agendaFixture.createAgenda(SEOUL); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL, AgendaTeamStatus.CONFIRM); ticketFixture.createTicket(seoulUserAgendaProfile); - TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); - String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( post("/agenda/team/join") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) - .content(content) + .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index 24d53a91a..5f5e9e15e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -25,15 +26,13 @@ import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.data.user.User; -import gg.repo.agenda.AgendaTeamRepository; import gg.repo.agenda.TicketRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.external.ApiUtil; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; -import gg.utils.fixture.agenda.AgendaTeamFixture; -import gg.utils.fixture.agenda.AgendaTeamProfileFixture; import gg.utils.fixture.agenda.TicketFixture; @IntegrationTest @@ -49,17 +48,13 @@ public class TicketControllerTest { @Autowired private TicketRepository ticketRepository; @Autowired - private AgendaTeamRepository agendaTeamRepository; - @Autowired private AgendaFixture agendaFixture; @Autowired - private AgendaTeamFixture agendaTeamFixture; - @Autowired private AgendaProfileFixture agendaProfileFixture; @Autowired - private AgendaTeamProfileFixture agendaTeamProfileFixture; - @Autowired private TicketFixture ticketFixture; + @MockBean + private ApiUtil apiUtil; User seoulUser; User gyeongsanUser; String seoulUserAccessToken; @@ -346,4 +341,42 @@ void findTicketHistoryFailToNotFoundProfile() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + class ApproveTicketTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(seoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaProfileFixture.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @DisplayName("setup 된 티켓 approve 실패 - 프로필이 없는 경우") + @Transactional + public void testTicketSetupAndRefundFailToNotFoundProfile() throws Exception { + User notExistUser = testDataUtils.createNewUser(); + String notExistUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(notExistUser); + // when + mockMvc.perform(patch("/agenda/ticket") + .header("Authorization", "Bearer " + notExistUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("setup 된 티켓 approve 실패 - 티켓이 없는 경우") + @Transactional + public void testTicketSetupAndRefundFailToNotFoundTicket() throws Exception { + // when + mockMvc.perform(patch("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-agenda-api/src/test/resources/application.yml b/gg-agenda-api/src/test/resources/application.yml index e01be0052..6e703ec8a 100644 --- a/gg-agenda-api/src/test/resources/application.yml +++ b/gg-agenda-api/src/test/resources/application.yml @@ -72,6 +72,9 @@ info: image: defaultUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/small_default.jpeg' itemNotFoundUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/not_found.svg' + web: + coalitionUrl: 'https://api.intra.42.fr/v2/users/{id}/coalitions' + pointHistoryUrl: 'https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id' constant: allowedMinimalStartDays: 2 diff --git a/gg-auth/build.gradle b/gg-auth/build.gradle index 1a7f1307b..ea88c4a13 100644 --- a/gg-auth/build.gradle +++ b/gg-auth/build.gradle @@ -15,6 +15,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.2' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" diff --git a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java new file mode 100644 index 000000000..22a614fc0 --- /dev/null +++ b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java @@ -0,0 +1,103 @@ +package gg.auth; + +import static gg.utils.exception.ErrorCode.*; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; + +import gg.utils.exception.ErrorCode; +import gg.utils.exception.custom.NotExistException; +import gg.utils.external.ApiUtil; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class FortyTwoAuthUtil { + private final ApiUtil apiUtil; + private final OAuth2AuthorizedClientService authorizedClientService; + + /** + * OAuth2AuthorizedClient 조회 + * @param oauthToken OAuth2AuthenticationToken + * @return OAuth2AuthorizedClient + */ + public OAuth2AuthorizedClient getOAuth2AuthorizedClient(OAuth2AuthenticationToken oauthToken) { + String registrationId = oauthToken.getAuthorizedClientRegistrationId(); + OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(registrationId, + oauthToken.getName()); + if (client.getRefreshToken() == null) { + throw new NotExistException(AUTH_NOT_FOUND); + } + return client; + } + + /** + * 토큰 갱신 + * @param client OAuth2AuthorizedClient + * @param authentication Authentication + * @return 갱신된 OAuth2AuthorizedClient + */ + public OAuth2AuthorizedClient refreshAccessToken(OAuth2AuthorizedClient client, Authentication authentication) { + try { + ClientRegistration registration = client.getClientRegistration(); + if (client.getRefreshToken() == null) { + throw new NotExistException(AUTH_NOT_FOUND); + } + + String tokenUri = registration.getProviderDetails().getTokenUri(); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "refresh_token"); + params.add("refresh_token", client.getRefreshToken().getTokenValue()); + params.add("client_id", registration.getClientId()); + params.add("client_secret", registration.getClientSecret()); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + List> responseBody = apiUtil.apiCall(tokenUri, List.class, headers, params, + HttpMethod.POST); + if (responseBody == null || responseBody.isEmpty()) { + throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); + } + Map map = responseBody.get(0); + + OAuth2AccessToken newAccessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + (String)map.get("access_token"), + Instant.now(), + Instant.now().plusSeconds((Integer)map.get("expires_in")) + ); + + OAuth2RefreshToken newRefreshToken = new OAuth2RefreshToken( + (String)map.get("refresh_token"), + Instant.now() + ); + + OAuth2AuthorizedClient newClient = new OAuth2AuthorizedClient( + registration, client.getPrincipalName(), newAccessToken, newRefreshToken); + + String principalName = authentication.getName(); + authorizedClientService.removeAuthorizedClient(registration.getRegistrationId(), principalName); + authorizedClientService.saveAuthorizedClient(newClient, authentication); + + return newClient; + } catch (RestClientException e) { + throw new NotExistException(AUTH_NOT_FOUND); + } + } +} diff --git a/gg-auth/src/main/java/gg/auth/utils/RefreshTokenUtil.java b/gg-auth/src/main/java/gg/auth/utils/RefreshTokenUtil.java new file mode 100644 index 000000000..c812c22ee --- /dev/null +++ b/gg-auth/src/main/java/gg/auth/utils/RefreshTokenUtil.java @@ -0,0 +1,58 @@ +package gg.auth.utils; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import gg.data.agenda.Auth42Token; +import gg.utils.exception.ErrorCode; +import gg.utils.exception.custom.NotExistException; +import gg.utils.external.ApiUtil; +import lombok.RequiredArgsConstructor; + +@Deprecated +@Component +@RequiredArgsConstructor +public class RefreshTokenUtil { + private final ApiUtil apiUtil; + + @Value("${spring.security.oauth2.client.registration.42.client-id}") + private String clientId; + + @Value("${spring.security.oauth2.client.registration.42.client-secret}") + private String clientSecret; + + @Value("${spring.security.oauth2.client.registration.42.redirect-uri}") + private String url; + + public Auth42Token refreshAuth42Token(Auth42Token auth42Token) { + final HttpHeaders headers = new HttpHeaders(); + + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + MultiValueMap params = createTokenRefreshParam(auth42Token.getRefreshToken()); + + List> response = apiUtil.apiCall(url, List.class, headers, params, HttpMethod.POST); + if (response == null || response.isEmpty()) { + throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); + } + Map map = response.get(0); + return new Auth42Token(auth42Token.getIntra42Id(), (String)map.get("access_token"), + (String)map.get("refresh_token")); + } + + private MultiValueMap createTokenRefreshParam(final String refreshToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "refresh_token"); + params.add("client_id", clientId); + params.add("client_secret", clientSecret); + params.add("refresh_token", refreshToken); + return params; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java index bcadfe695..2637324be 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaProfile.java @@ -27,6 +27,9 @@ public class AgendaProfile extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "forty_two_id", nullable = false) + private Long fortyTwoId; + @Column(name = "content", length = 1000, nullable = false) private String content; @@ -49,13 +52,14 @@ public class AgendaProfile extends BaseTimeEntity { @Builder public AgendaProfile(String content, String githubUrl, Coalition coalition, Location location, String intraId, - Long userId) { + Long userId, Long fortyTwoId) { this.content = content; this.githubUrl = githubUrl; this.coalition = coalition; this.location = location; this.intraId = intraId; this.userId = userId; + this.fortyTwoId = fortyTwoId; } public void updateProfile(String content, String githubUrl) { diff --git a/gg-data/src/main/java/gg/data/agenda/Auth42Token.java b/gg-data/src/main/java/gg/data/agenda/Auth42Token.java new file mode 100644 index 000000000..f1daa3cea --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/Auth42Token.java @@ -0,0 +1,22 @@ +package gg.data.agenda; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Deprecated +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Auth42Token { + private String intra42Id; + private String accessToken; + private String refreshToken; + + @Builder + public Auth42Token(String intra42Id, String accessToken, String refreshToken) { + this.intra42Id = intra42Id; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index ebc35fea7..f9ea0e840 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -75,6 +75,18 @@ public static void createRefundedTicket(AgendaTeamProfile agendaTeamProfile) { .build(); } + public static Ticket createApproveTicket(AgendaProfile agendaProfile) { + return Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(null) + .usedTo(null) + .isApproved(true) + .approvedAt(LocalDateTime.now()) + .isUsed(false) + .usedAt(null) + .build(); + } + public static Ticket createNotApporveTicket(AgendaProfile agendaProfile) { return Ticket.builder() .agendaProfile(agendaProfile) @@ -92,4 +104,9 @@ public void useTicket(UUID usedTo) { this.usedAt = LocalDateTime.now(); this.isUsed = true; } + + public void changeIsApproved() { + this.isApproved = true; + this.approvedAt = LocalDateTime.now(); + } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenAuthenticationFilter.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenAuthenticationFilter.java similarity index 98% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenAuthenticationFilter.java rename to gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenAuthenticationFilter.java index 9758a9f54..7599f54e5 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenAuthenticationFilter.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenAuthenticationFilter.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.security.jwt.utils; +package gg.pingpong.api.global.jwt.utils; import static org.apache.commons.lang3.StringUtils.*; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenHeaders.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenHeaders.java similarity index 74% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenHeaders.java rename to gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenHeaders.java index 139c30e09..855e2894f 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/utils/TokenHeaders.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/jwt/utils/TokenHeaders.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.security.jwt.utils; +package gg.pingpong.api.global.jwt.utils; public class TokenHeaders { public static final String REFRESH_TOKEN = "refresh_token"; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java index 35322e80d..8c869d3c9 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/config/SecurityConfig.java @@ -12,10 +12,10 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import gg.pingpong.api.global.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.security.config.properties.CorsProperties; import gg.pingpong.api.global.security.handler.OAuthAuthenticationSuccessHandler; import gg.pingpong.api.global.security.handler.OauthAuthenticationFailureHandler; -import gg.pingpong.api.global.security.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.security.repository.OAuthAuthorizationRequestBasedOnCookieRepository; import lombok.RequiredArgsConstructor; @@ -42,6 +42,7 @@ protected void configure(HttpSecurity http) throws Exception { .antMatchers("/party/admin/**").hasRole("ADMIN") .antMatchers("/agenda/admin/**").hasRole("ADMIN") .antMatchers("/admin/recruitments/**").hasRole("ADMIN") + .antMatchers("/agenda/admin/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/pingpong/users/{intraId}").hasAnyRole("USER", "ADMIN") .antMatchers(HttpMethod.POST, "/pingpong/match").hasAnyRole("USER", "ADMIN") .antMatchers(HttpMethod.POST, "/pingpong/tournaments/{tournamentId}/users").hasAnyRole("USER", "ADMIN") diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OAuthAuthenticationSuccessHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OAuthAuthenticationSuccessHandler.java index 5edce39f5..ee374d6e3 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OAuthAuthenticationSuccessHandler.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OAuthAuthenticationSuccessHandler.java @@ -16,12 +16,12 @@ import gg.auth.utils.AuthTokenProvider; import gg.data.user.User; import gg.data.user.type.RoleType; +import gg.pingpong.api.global.jwt.utils.TokenHeaders; import gg.pingpong.api.global.security.UserPrincipal; -import gg.pingpong.api.global.security.cookie.CookieUtil; -import gg.pingpong.api.global.security.jwt.repository.JwtRedisRepository; -import gg.pingpong.api.global.security.jwt.utils.TokenHeaders; -import gg.pingpong.api.global.utils.ApplicationYmlRead; +import gg.repo.user.JwtRedisRepository; import gg.repo.user.UserRepository; +import gg.utils.ApplicationYmlRead; +import gg.utils.cookie.CookieUtil; import gg.utils.exception.user.UserNotFoundException; import lombok.RequiredArgsConstructor; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java index dcbec7888..98ae3e5fd 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/handler/OauthAuthenticationFailureHandler.java @@ -10,7 +10,7 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; -import gg.pingpong.api.global.utils.ApplicationYmlRead; +import gg.utils.ApplicationYmlRead; import lombok.RequiredArgsConstructor; @Component diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/repository/OAuthAuthorizationRequestBasedOnCookieRepository.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/repository/OAuthAuthorizationRequestBasedOnCookieRepository.java index 574f0474c..e7c87dd9d 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/repository/OAuthAuthorizationRequestBasedOnCookieRepository.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/repository/OAuthAuthorizationRequestBasedOnCookieRepository.java @@ -9,7 +9,7 @@ import com.nimbusds.oauth2.sdk.util.StringUtils; -import gg.pingpong.api.global.security.cookie.CookieUtil; +import gg.utils.cookie.CookieUtil; import lombok.RequiredArgsConstructor; @Repository diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java index 4c483e028..349fc5ec5 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java @@ -8,6 +8,7 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -19,7 +20,6 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; @@ -34,7 +34,6 @@ import gg.pingpong.api.global.security.info.OAuthUserInfoFactory; import gg.pingpong.api.global.security.info.ProviderType; import gg.pingpong.api.global.utils.aws.AsyncNewUserImageUploader; -import gg.pingpong.api.global.utils.external.ApiUtil; import gg.repo.agenda.AgendaProfileRepository; import gg.repo.rank.RankRepository; import gg.repo.rank.TierRepository; @@ -43,6 +42,7 @@ import gg.repo.user.UserRepository; import gg.utils.RedisKeyManager; import gg.utils.exception.tier.TierNotFoundException; +import gg.utils.external.ApiUtil; import lombok.RequiredArgsConstructor; @Service @@ -60,11 +60,9 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { @Value("${info.image.defaultUrl}") private String defaultImageUrl; - @Value("https://api.intra.42.fr/v2/users/{id}/coalitions") + @Value("${info.web.coalitionUrl}") private String coalitionUrl; - private RestTemplate restTemplate; - @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User user = super.loadUser(userRequest); @@ -104,6 +102,7 @@ private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) throw String token = userRequest.getAccessToken().getTokenValue(); createProfile(userInfo, savedUser, token); } + return UserPrincipal.create(savedUser, user.getAttributes()); } @@ -141,6 +140,7 @@ private void createProfile(OAuthUserInfo userInfo, User user, String accessToken .content("안녕하세요! " + userInfo.getIntraId() + "입니다.") .githubUrl(null) .coalition(findCoalition(userInfo.getUserId(), accessToken)) + .fortyTwoId(Long.valueOf(userInfo.getUserId())) .location(userInfo.getLocation()) .build(); agendaProfileRepository.save(agendaProfile); @@ -152,8 +152,9 @@ private Coalition findCoalition(String id, String accessToken) { headers.set("Authorization", "Bearer " + accessToken); headers.setContentType(MediaType.APPLICATION_JSON); - // HttpEntity 객체를 생성하여 헤더를 포함한 요청을 보냄 - List> response = apiUtil.apiCall(url, List.class, headers, HttpMethod.GET); + ParameterizedTypeReference>> responseType = new ParameterizedTypeReference<>() { + }; + List> response = apiUtil.apiCall(url, responseType, headers, HttpMethod.GET); if (response != null && !response.isEmpty()) { Map coalition = response.get(0); diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java index 16cb97331..bf558daff 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java @@ -17,7 +17,7 @@ import org.springframework.util.MultiValueMap; import gg.data.user.User; -import gg.pingpong.api.global.utils.external.ApiUtil; +import gg.utils.external.ApiUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java index c6660b8bc..5635c41b6 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java @@ -16,10 +16,10 @@ import gg.auth.UserDto; import gg.data.noti.Noti; -import gg.pingpong.api.global.utils.external.ApiUtil; import gg.pingpong.api.user.noti.dto.UserNotiDto; import gg.pingpong.api.user.noti.service.NotiService; import gg.utils.exception.noti.SlackSendException; +import gg.utils.external.ApiUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/controller/UserController.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/controller/UserController.java index a8f277cc6..ee4bca164 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/controller/UserController.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/controller/UserController.java @@ -31,8 +31,7 @@ import gg.data.pingpong.game.type.Mode; import gg.data.user.type.OauthType; import gg.data.user.type.RoleType; -import gg.pingpong.api.global.security.cookie.CookieUtil; -import gg.pingpong.api.global.security.jwt.utils.TokenHeaders; +import gg.pingpong.api.global.jwt.utils.TokenHeaders; import gg.pingpong.api.user.user.controller.request.UserModifyRequestDto; import gg.pingpong.api.user.user.controller.request.UserProfileImageRequestDto; import gg.pingpong.api.user.user.controller.response.UserAttendanceResponseDto; @@ -53,6 +52,7 @@ import gg.pingpong.api.user.user.service.UserAuthenticationService; import gg.pingpong.api.user.user.service.UserCoinService; import gg.pingpong.api.user.user.service.UserService; +import gg.utils.cookie.CookieUtil; import gg.utils.dto.PageRequestDto; import gg.utils.exception.user.KakaoOauth2AlreadyExistException; import io.swagger.v3.oas.annotations.Parameter; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/service/UserAuthenticationService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/service/UserAuthenticationService.java index 71456b835..2c3eaf94d 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/service/UserAuthenticationService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/user/service/UserAuthenticationService.java @@ -3,7 +3,7 @@ import org.springframework.stereotype.Service; import gg.auth.utils.AuthTokenProvider; -import gg.pingpong.api.global.security.jwt.repository.JwtRedisRepository; +import gg.repo.user.JwtRedisRepository; import gg.utils.exception.user.TokenNotValidException; import lombok.RequiredArgsConstructor; diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index b9cff480f..2770051d8 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -64,6 +64,7 @@ CREATE TABLE `agenda_profile` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `user_id` BIGINT NOT NULL, + `forty_two_id`BIGINT NOT NULL, `intra_id` VARCHAR(30) NOT NULL, `content` VARCHAR(1000) NOT NULL, `github_url` VARCHAR(255) NULL, diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/rank/controller/RankV2ControllerTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/rank/controller/RankV2ControllerTest.java index 114f4030d..f8e0398a4 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/rank/controller/RankV2ControllerTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/rank/controller/RankV2ControllerTest.java @@ -23,8 +23,8 @@ import gg.auth.utils.AuthTokenProvider; import gg.data.user.User; import gg.pingpong.api.global.config.WebConfig; +import gg.pingpong.api.global.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.security.config.SecurityConfig; -import gg.pingpong.api.global.security.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.utils.querytracker.LoggingInterceptor; import gg.pingpong.api.user.rank.controller.response.ExpRankPageResponseDto; import gg.pingpong.api.user.rank.controller.response.RankPageResponseDto; diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/tournament/controller/TournamentControllerMvcTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/tournament/controller/TournamentControllerMvcTest.java index 511c34b33..6685d3bfd 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/tournament/controller/TournamentControllerMvcTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/tournament/controller/TournamentControllerMvcTest.java @@ -23,8 +23,8 @@ import gg.auth.UserDto; import gg.auth.config.AuthWebConfig; import gg.pingpong.api.global.config.WebConfig; +import gg.pingpong.api.global.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.security.config.SecurityConfig; -import gg.pingpong.api.global.security.jwt.utils.TokenAuthenticationFilter; import gg.pingpong.api.global.utils.querytracker.LoggingInterceptor; import gg.pingpong.api.user.tournament.controller.request.TournamentFilterRequestDto; import gg.pingpong.api.user.tournament.controller.response.TournamentGameListResponseDto; diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/service/UserAuthenticationServiceUnitTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/service/UserAuthenticationServiceUnitTest.java index 713c49a25..330615aa0 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/service/UserAuthenticationServiceUnitTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/user/service/UserAuthenticationServiceUnitTest.java @@ -12,7 +12,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import gg.auth.utils.AuthTokenProvider; -import gg.pingpong.api.global.security.jwt.repository.JwtRedisRepository; +import gg.repo.user.JwtRedisRepository; import gg.utils.annotation.UnitTest; import gg.utils.exception.user.TokenNotValidException; diff --git a/gg-pingpong-api/src/test/resources/application.yml b/gg-pingpong-api/src/test/resources/application.yml index f28374ce9..eddd3339a 100644 --- a/gg-pingpong-api/src/test/resources/application.yml +++ b/gg-pingpong-api/src/test/resources/application.yml @@ -72,7 +72,10 @@ info: image: defaultUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/small_default.jpeg' itemNotFoundUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/not_found.svg' - + web: + coalitionUrl: 'https://api.intra.42.fr/v2/users/{id}/coalitions' + pointHistoryUrl: 'https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id' + constant: allowedMinimalStartDays: 2 tournamentSchedule: "0 0 0 * * *" diff --git a/gg-recruit-api/src/test/resources/application.yml b/gg-recruit-api/src/test/resources/application.yml index e01be0052..6e703ec8a 100644 --- a/gg-recruit-api/src/test/resources/application.yml +++ b/gg-recruit-api/src/test/resources/application.yml @@ -72,6 +72,9 @@ info: image: defaultUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/small_default.jpeg' itemNotFoundUrl: 'https://42gg-public-test-image.s3.ap-northeast-2.amazonaws.com/images/not_found.svg' + web: + coalitionUrl: 'https://api.intra.42.fr/v2/users/{id}/coalitions' + pointHistoryUrl: 'https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id' constant: allowedMinimalStartDays: 2 diff --git a/gg-repo/src/main/java/gg/repo/user/Auth42TokenRedisRepository.java b/gg-repo/src/main/java/gg/repo/user/Auth42TokenRedisRepository.java new file mode 100644 index 000000000..17b31cc1f --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/user/Auth42TokenRedisRepository.java @@ -0,0 +1,40 @@ +package gg.repo.user; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.Auth42Token; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Deprecated +@Repository +@RequiredArgsConstructor +public class Auth42TokenRedisRepository { + private final RedisTemplate redisTemplate; + private static final long TOKEN_EXPIRATION_TIME = 7 * 24 * 60 * 60; // 7일 + + public void save42Token(String intraId, Auth42Token token) { + redisTemplate.opsForValue().set(intraId, token, TOKEN_EXPIRATION_TIME, TimeUnit.SECONDS); + } + + public Optional findByIntraId(String intraId) { + return Optional.ofNullable(redisTemplate.opsForValue().get(intraId)); + } + + public void expire42Token(String intraId) { + redisTemplate.expire(intraId, 0, TimeUnit.SECONDS); + } + + public void update42Token(String intraId, Auth42Token token) { + Long currentTtl = redisTemplate.getExpire(intraId, TimeUnit.SECONDS); + + if (currentTtl == null) { + throw new NotExistException("토큰이 존재하지 않습니다."); + } + redisTemplate.opsForValue().set(intraId, token, currentTtl, TimeUnit.SECONDS); + } +} diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/repository/JwtRedisRepository.java b/gg-repo/src/main/java/gg/repo/user/JwtRedisRepository.java similarity index 93% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/repository/JwtRedisRepository.java rename to gg-repo/src/main/java/gg/repo/user/JwtRedisRepository.java index f98b6141b..e410f79fd 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/jwt/repository/JwtRedisRepository.java +++ b/gg-repo/src/main/java/gg/repo/user/JwtRedisRepository.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.security.jwt.repository; +package gg.repo.user; import java.util.concurrent.TimeUnit; diff --git a/gg-utils/build.gradle b/gg-utils/build.gradle index 3ecc8935a..7db24c688 100644 --- a/gg-utils/build.gradle +++ b/gg-utils/build.gradle @@ -73,8 +73,8 @@ unitTestCoverageReport { dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' implementation "com.amazonaws:aws-java-sdk-s3:1.12.281" - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ApplicationYmlRead.java b/gg-utils/src/main/java/gg/utils/ApplicationYmlRead.java similarity index 93% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ApplicationYmlRead.java rename to gg-utils/src/main/java/gg/utils/ApplicationYmlRead.java index b675bbc22..99290db35 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/ApplicationYmlRead.java +++ b/gg-utils/src/main/java/gg/utils/ApplicationYmlRead.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.utils; +package gg.utils; import java.util.Map; diff --git a/gg-utils/src/main/java/gg/utils/DateTimeUtil.java b/gg-utils/src/main/java/gg/utils/DateTimeUtil.java new file mode 100644 index 000000000..e4641ff2a --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/DateTimeUtil.java @@ -0,0 +1,18 @@ +package gg.utils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtil { + private static final ZoneId SEOUL_ZONE_ID = ZoneId.of("Asia/Seoul"); + private static final ZoneId UTC_ZONE_ID = ZoneId.of("UTC"); + + public static LocalDateTime convertToSeoulDateTime(String dateTimeString) { + return ZonedDateTime.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME) + .withZoneSameInstant(UTC_ZONE_ID) + .withZoneSameInstant(SEOUL_ZONE_ID) + .toLocalDateTime(); + } +} diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/cookie/CookieUtil.java b/gg-utils/src/main/java/gg/utils/cookie/CookieUtil.java similarity index 95% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/cookie/CookieUtil.java rename to gg-utils/src/main/java/gg/utils/cookie/CookieUtil.java index a7e4663ff..a4e49fe63 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/cookie/CookieUtil.java +++ b/gg-utils/src/main/java/gg/utils/cookie/CookieUtil.java @@ -1,4 +1,4 @@ -package gg.pingpong.api.global.security.cookie; +package gg.utils.cookie; import java.util.Base64; import java.util.Optional; @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.SerializationUtils; -import gg.pingpong.api.global.utils.ApplicationYmlRead; +import gg.utils.ApplicationYmlRead; import lombok.RequiredArgsConstructor; @Component diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 61e3ffe18..9c46586f2 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -192,6 +192,8 @@ public enum ErrorCode { INVALID_CHECKLIST(400, "RE001", "잘못된 요청 데이터입니다."), // agenda + AUTH_NOT_FOUND(404, "AG", "42 정보가 만료되었습니다."), + AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), AGENDA_CREATE_FAILED(500, "AG", "일정 생성에 실패했습니다."), AGENDA_UPDATE_FAILED(500, "AG", "일정 수정에 실패했습니다."), @@ -220,6 +222,9 @@ public enum ErrorCode { AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), ALREADY_TICKET_SETUP(409, "AG", "이미 티켓 신청이 되어있습니다."), + NOT_SETUP_TICKET(404, "AG", "티켓 신청이 되어있지 않습니다."), + POINT_HISTORY_NOT_FOUND(404, "AG", "기부 내역이 존재하지 않습니다."), + TICKET_FORBIDDEN(403, "AG", "티켓 신청은 1분의 대기시간이 있습니다."), AGENDA_ALREADY_FINISHED(409, "AG", "이미 종료된 일정입니다."), AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 확정된 일정입니다."), AGENDA_ALREADY_CANCELED(409, "AG", "이미 취소된 일정입니다."), diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java b/gg-utils/src/main/java/gg/utils/external/ApiUtil.java similarity index 65% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java rename to gg-utils/src/main/java/gg/utils/external/ApiUtil.java index 2947aaff3..e8c1df77b 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/external/ApiUtil.java +++ b/gg-utils/src/main/java/gg/utils/external/ApiUtil.java @@ -1,11 +1,14 @@ -package gg.pingpong.api.global.utils.external; +package gg.utils.external; +import java.util.List; import java.util.Map; import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; @@ -14,6 +17,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import gg.utils.exception.user.TokenNotValidException; + @Component public class ApiUtil { private final RestTemplate restTemplate; @@ -54,8 +59,35 @@ public T apiCall(String url, Class responseType, HttpHeaders headers, Htt HttpEntity request = new HttpEntity<>(headers); ResponseEntity res = restTemplate.exchange(url, method, request, responseType); if (!res.getStatusCode().is2xxSuccessful()) { - throw new RuntimeException("api call error"); + throw new TokenNotValidException(); } return res.getBody(); } + + public T apiCall(String url, ParameterizedTypeReference responseType, HttpHeaders headers, + HttpMethod method) { + HttpEntity request = new HttpEntity<>(headers); + ResponseEntity res = restTemplate.exchange(url, method, request, responseType); + if (!res.getStatusCode().is2xxSuccessful()) { + throw new TokenNotValidException(); + } + return res.getBody(); + } + + /** + * API 호출 + * @param url 호출할 URL + * @param accessToken 액세스 토큰 + * @param responseType 응답 타입 + * @return 응답 + */ + public List> callApiWithAccessToken(String url, String accessToken, + ParameterizedTypeReference>> responseType) { + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.setContentType(MediaType.APPLICATION_JSON); + + return apiCall(url, responseType, headers, HttpMethod.GET); + } } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java index cd77a2f28..0f09648a5 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaProfileFixture.java @@ -30,6 +30,7 @@ public AgendaProfile createAgendaProfile(User user, Location location) { .location(location) .intraId(user.getIntraId()) .userId(user.getId()) + .fortyTwoId(user.getId()) .build(); return agendaProfileRepository.save(agendaProfile); } @@ -43,6 +44,7 @@ public AgendaProfile createAgendaProfile() { .location(Location.SEOUL) .intraId(user.getIntraId()) .userId(user.getId()) + .fortyTwoId(user.getId()) .build(); return agendaProfileRepository.save(agendaProfile); } @@ -58,6 +60,7 @@ public List createAgendaProfileList(int size) { .location(Location.SEOUL) .intraId(user.getIntraId()) .userId(user.getId()) + .fortyTwoId(user.getId()) .build(); agendaProfileList.add(agendaProfile); } diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java index 91405a4ef..09ca8a4fa 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/TicketFixture.java @@ -28,7 +28,7 @@ public Ticket createTicket(AgendaProfile agendaProfile) { return ticketRepository.save(ticket); } - public void createNotApporveTicket(AgendaProfile seoulUserAgendaProfile) { + public Ticket createNotApporveTicket(AgendaProfile seoulUserAgendaProfile) { Ticket ticket = Ticket.builder() .agendaProfile(seoulUserAgendaProfile) .issuedFrom(null) @@ -38,7 +38,7 @@ public void createNotApporveTicket(AgendaProfile seoulUserAgendaProfile) { .isUsed(false) .usedAt(null) .build(); - ticketRepository.save(ticket); + return ticketRepository.save(ticket); } public Ticket createTicket(AgendaProfile agendaProfile, boolean isApproved, boolean isUsed, UUID issuedFrom, From 13e0213e5792b68108def44578c3c9d42ce6e8ce Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:09:06 +0900 Subject: [PATCH 070/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Agenda=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=9D=91=EB=8B=B5=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20#951=20(#955)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/AgendaAdminResDto.java | 19 +++++++++++++--- .../controller/response/AgendaTeamResDto.java | 22 ++++++++++++++----- .../controller/response/AgendaResDto.java | 9 ++++---- .../response/AgendaSimpleResDto.java | 9 +++++++- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java index f0b2fdb3b..9d4027d8d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java @@ -1,7 +1,11 @@ package gg.agenda.api.admin.agenda.controller.response; +import java.net.URL; +import java.util.UUID; + import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.factory.Mappers; import gg.data.agenda.Agenda; @@ -18,6 +22,8 @@ public class AgendaAdminResDto { private Long agendaId; + private UUID agendaKey; + private String agendaTitle; private String agendaDeadLine; @@ -42,11 +48,15 @@ public class AgendaAdminResDto { private AgendaStatus agendaStatus; + private String agendaPosterUrl; + @Builder - public AgendaAdminResDto(Long agendaId, String agendaTitle, String agendaDeadLine, String agendaStartTime, - String agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, - Location agendaLocation, Boolean isRanking, Boolean isOfficial, AgendaStatus agendaStatus) { + public AgendaAdminResDto(Long agendaId, UUID agendaKey, String agendaTitle, String agendaDeadLine, + String agendaStartTime, String agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, + int agendaMaxPeople, Location agendaLocation, Boolean isRanking, Boolean isOfficial, + AgendaStatus agendaStatus, String agendaPosterUrl) { this.agendaId = agendaId; + this.agendaKey = agendaKey; this.agendaTitle = agendaTitle; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; @@ -59,6 +69,7 @@ public AgendaAdminResDto(Long agendaId, String agendaTitle, String agendaDeadLin this.isRanking = isRanking; this.isOfficial = isOfficial; this.agendaStatus = agendaStatus; + this.agendaPosterUrl = agendaPosterUrl; } @Mapper @@ -67,6 +78,7 @@ public interface MapStruct { AgendaAdminResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaAdminResDto.MapStruct.class); @Mapping(target = "agendaId", source = "id") + @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "agendaTitle", source = "title") @Mapping(target = "agendaDeadLine", source = "deadline") @Mapping(target = "agendaStartTime", source = "startTime") @@ -79,6 +91,7 @@ public interface MapStruct { @Mapping(target = "isRanking", source = "isRanking") @Mapping(target = "isOfficial", source = "isOfficial") @Mapping(target = "agendaStatus", source = "status") + @Mapping(target = "agendaPosterUrl", source = "posterUri") AgendaAdminResDto toAgendaAdminResDto(Agenda agenda); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java index 34ca6369e..b756446d4 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java @@ -3,6 +3,7 @@ import java.util.UUID; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import gg.data.agenda.AgendaTeam; @@ -19,8 +20,6 @@ public class AgendaTeamResDto { private String teamStatus; - private int teamScore; - private boolean teamIsPrivate; private String teamLeaderIntraId; @@ -29,16 +28,21 @@ public class AgendaTeamResDto { private UUID teamKey; + private String teamAward; + + private Integer teamAwardPriority; + @Builder - public AgendaTeamResDto(String teamName, String teamStatus, int teamScore, boolean teamIsPrivate, - String teamLeaderIntraId, int teamMateCount, UUID teamKey) { + public AgendaTeamResDto(String teamName, String teamStatus, boolean teamIsPrivate, String teamLeaderIntraId, + int teamMateCount, UUID teamKey, String teamAward, Integer teamAwardPriority) { this.teamName = teamName; this.teamStatus = teamStatus; - this.teamScore = teamScore; this.teamIsPrivate = teamIsPrivate; this.teamLeaderIntraId = teamLeaderIntraId; this.teamMateCount = teamMateCount; this.teamKey = teamKey; + this.teamAward = teamAward; + this.teamAwardPriority = teamAwardPriority; } @Mapper @@ -46,6 +50,14 @@ public interface MapStruct { AgendaTeamResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamResDto.MapStruct.class); + @Mapping(target = "teamName", source = "name") + @Mapping(target = "teamStatus", source = "status") + @Mapping(target = "teamIsPrivate", source = "isPrivate") + @Mapping(target = "teamLeaderIntraId", source = "leaderIntraId") + @Mapping(target = "teamMateCount", source = "mateCount") + @Mapping(target = "teamKey", source = "teamKey") + @Mapping(target = "teamAward", source = "award", defaultValue = "AgendaTeam.DEFAULT_AWARD") + @Mapping(target = "teamAwardPriority", source = "awardPriority", defaultValue = "0") AgendaTeamResDto toDto(AgendaTeam agendaTeam); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java index 6b6373a44..5a1987498 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agenda.controller.response; +import java.net.URL; import java.time.LocalDateTime; import org.mapstruct.Mapper; @@ -38,7 +39,7 @@ public class AgendaResDto { private int agendaMaxPeople; - private String agendaPoster; + private String agendaPosterUrl; private String agendaHost; @@ -57,7 +58,7 @@ public class AgendaResDto { @Builder public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, - int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPoster, String agendaHost, + int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPosterUrl, String agendaHost, Location agendaLocation, AgendaStatus agendaStatus, LocalDateTime createdAt, String announcementTitle, Boolean isOfficial, Boolean isRanking) { this.agendaTitle = agendaTitle; @@ -70,7 +71,7 @@ public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime age this.agendaCurrentTeam = agendaCurrentTeam; this.agendaMinPeople = agendaMinPeople; this.agendaMaxPeople = agendaMaxPeople; - this.agendaPoster = agendaPoster; + this.agendaPosterUrl = agendaPosterUrl; this.agendaHost = agendaHost; this.agendaLocation = agendaLocation; this.agendaStatus = agendaStatus; @@ -95,7 +96,7 @@ public interface MapStruct { @Mapping(target = "agendaCurrentTeam", source = "agenda.currentTeam") @Mapping(target = "agendaMinPeople", source = "agenda.minPeople") @Mapping(target = "agendaMaxPeople", source = "agenda.maxPeople") - @Mapping(target = "agendaPoster", source = "agenda.posterUri") + @Mapping(target = "agendaPosterUrl", source = "agenda.posterUri") @Mapping(target = "agendaHost", source = "agenda.hostIntraId") @Mapping(target = "agendaLocation", source = "agenda.location") @Mapping(target = "agendaStatus", source = "agenda.status") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java index 09da2ec9b..b172c3a82 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaSimpleResDto.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agenda.controller.response; +import java.net.URL; import java.time.LocalDateTime; import java.util.UUID; @@ -17,6 +18,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class AgendaSimpleResDto { + private String agendaTitle; private LocalDateTime agendaDeadLine; @@ -41,10 +43,13 @@ public class AgendaSimpleResDto { private Boolean isRanking; + private String agendaPosterUrl; + @Builder public AgendaSimpleResDto(String agendaTitle, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, - int agendaMaxPeople, Location agendaLocation, UUID agendaKey, Boolean isOfficial, Boolean isRanking) { + int agendaMaxPeople, Location agendaLocation, UUID agendaKey, Boolean isOfficial, Boolean isRanking, + String agendaPosterUrl) { this.agendaTitle = agendaTitle; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; @@ -57,6 +62,7 @@ public AgendaSimpleResDto(String agendaTitle, LocalDateTime agendaDeadLine, Loca this.agendaKey = agendaKey; this.isOfficial = isOfficial; this.isRanking = isRanking; + this.agendaPosterUrl = agendaPosterUrl; } @Mapper @@ -75,6 +81,7 @@ public interface MapStruct { @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "isOfficial", source = "isOfficial") @Mapping(target = "isRanking", source = "isRanking") + @Mapping(target = "agendaPosterUrl", source = "posterUri") AgendaSimpleResDto toDto(Agenda agenda); } } From 362e142eff079c5c82d0951fa067183677e280cd Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:12:09 +0900 Subject: [PATCH 071/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20Admin?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20#954=20(#956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaAdminController.java | 7 +++ .../response/AgendaAdminSimpleResDto.java | 38 ++++++++++++++++ .../agenda/service/AgendaAdminService.java | 9 ++++ .../controller/AgendaAdminControllerTest.java | 43 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminSimpleResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index f56ae339a..bc000da17 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -24,6 +24,7 @@ import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; +import gg.agenda.api.admin.agenda.controller.response.AgendaAdminSimpleResDto; import gg.agenda.api.admin.agenda.service.AgendaAdminService; import gg.utils.dto.PageRequestDto; import gg.utils.exception.custom.InvalidParameterException; @@ -50,6 +51,12 @@ public ResponseEntity> agendaList(@ModelAttribute @Valid return ResponseEntity.ok(agendaDtos); } + @GetMapping("/list") + public ResponseEntity> agendaSimpleList() { + List agendas = agendaAdminService.getAgendaSimpleList(); + return ResponseEntity.status(HttpStatus.OK).body(agendas); + } + @ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Agenda 수정 성공"), @ApiResponse(responseCode = "400", description = "Agenda 수정 요청이 잘못됨"), @ApiResponse(responseCode = "404", description = "Agenda를 찾을 수 없음"), diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminSimpleResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminSimpleResDto.java new file mode 100644 index 000000000..8a2348929 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminSimpleResDto.java @@ -0,0 +1,38 @@ +package gg.agenda.api.admin.agenda.controller.response; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaAdminSimpleResDto { + + UUID agendaKey; + + String agendaTitle; + + @Builder + public AgendaAdminSimpleResDto(UUID agendaKey, String agendaTitle) { + this.agendaKey = agendaKey; + this.agendaTitle = agendaTitle; + } + + @Mapper + public interface MapStruct { + + AgendaAdminSimpleResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaAdminSimpleResDto.MapStruct.class); + + @Mapping(target = "agendaKey", source = "agendaKey") + @Mapping(target = "agendaTitle", source = "title") + AgendaAdminSimpleResDto toDto(Agenda agenda); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index 3fb92b1ad..773ef3fd5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Pageable; @@ -17,6 +18,7 @@ import gg.admin.repo.agenda.AgendaAdminRepository; import gg.admin.repo.agenda.AgendaTeamAdminRepository; import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; +import gg.agenda.api.admin.agenda.controller.response.AgendaAdminSimpleResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; import gg.utils.exception.custom.BusinessException; @@ -70,4 +72,11 @@ public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto, Mult agenda.updateAgendaCapacity(agendaDto.getAgendaMinTeam(), agendaDto.getAgendaMaxTeam(), teams); agenda.updateAgendaTeamCapacity(agendaDto.getAgendaMinPeople(), agendaDto.getAgendaMaxPeople(), teams); } + + @Transactional(readOnly = true) + public List getAgendaSimpleList() { + return agendaAdminRepository.findAll().stream() + .map(AgendaAdminSimpleResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index d2dccf792..07d31cc8b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -41,11 +41,13 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; import gg.data.user.User; +import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; import gg.utils.file.handler.AwsImageHandler; +import gg.utils.fixture.agenda.AgendaFixture; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -66,6 +68,9 @@ public class AgendaAdminControllerTest { @Autowired private AgendaMockData agendaMockData; + @Autowired + private AgendaFixture agendaFixture; + @Autowired EntityManager em; @@ -564,4 +569,42 @@ void updateAgendaAdminFailedWithMinPeople() throws Exception { .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("Admin Agenda 목록 간단 조회") + class GetAgendaAdminSimple { + @Test + @DisplayName("Admin Agenda 목록 간단 조회 성공") + void getAgendaAdminSimpleSuccess() throws Exception { + // given + agendaFixture.createAgenda(OPEN); + agendaFixture.createAgenda(FINISH); + agendaFixture.createAgenda(CANCEL); + + // when + String response = mockMvc.perform(get("/agenda/admin/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + + // then + assertThat(result).hasSize(3); + } + + @Test + @DisplayName("Admin Agenda 목록 간단 조회 성공 - 빈 리스트 반환") + void getAgendaAdminSimpleSuccessWithEmtpyList() throws Exception { + // given + // when + String response = mockMvc.perform(get("/agenda/admin/list") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + + // then + assertThat(result).isEmpty(); + } + } } From fd95078db4b80515588e8113174ebcf0d78697da Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:41:04 +0900 Subject: [PATCH 072/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20SlackSer?= =?UTF-8?q?vice=20=EA=B3=B5=ED=86=B5=20=EB=AA=A8=EB=93=88=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#947=20(#950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +- .../user/noti/service/PartyNotiService.java | 19 +-- .../api/user/noti/service/SnsNotiService.java | 21 ++-- .../user/noti/service/sns/NotiMailSender.java | 44 ++----- .../service/sns/NotiSlackMessageSender.java | 31 +++++ .../service/sns/SlackPartybotService.java | 26 ++-- .../noti/service/sns/SlackbotService.java | 26 ++-- .../user/noti/service/sns/SlackbotUtils.java | 9 -- .../noti/service/SnsNotiServiceUnitTest.java | 17 +-- .../service/sns/NotiMailSenderUnitTest.java | 25 ++-- gg-utils/build.gradle | 4 +- .../main/java/gg/utils/sns/MailSender.java | 6 + .../main/java/gg/utils/sns/MessageSender.java | 10 ++ .../gg/utils/sns/mail}/AsyncMailSender.java | 17 ++- .../sns/slack/AsyncSlackMessageSender.java | 46 +++++++ .../gg/utils/sns/slack/SlackbotApiUtils.java | 116 ++++++++++++++++++ .../sns/slack/constant/SlackConstant.java | 16 +++ .../gg/utils/sns/slack/response/Channel.java | 9 ++ .../slack/response/ConversationResponse.java | 11 ++ .../utils/sns/slack/response/SlackUser.java | 9 ++ .../slack/response/SlackUserInfoResponse.java | 11 ++ 21 files changed, 371 insertions(+), 105 deletions(-) create mode 100644 gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiSlackMessageSender.java delete mode 100644 gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotUtils.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/MailSender.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/MessageSender.java rename {gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils => gg-utils/src/main/java/gg/utils/sns/mail}/AsyncMailSender.java (50%) create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/AsyncSlackMessageSender.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/constant/SlackConstant.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/response/Channel.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/response/ConversationResponse.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUser.java create mode 100644 gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUserInfoResponse.java diff --git a/build.gradle b/build.gradle index e4e230955..8f713003a 100644 --- a/build.gradle +++ b/build.gradle @@ -113,7 +113,8 @@ subprojects { "*NotiMailSender*", '*SlackbotService*', "**/file/*", - "*AwsImageHandler*" + "*AwsImageHandler*", + "*SlackbotApiUtils*" ] //커버리지 리포트 생성 diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/PartyNotiService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/PartyNotiService.java index efbfe9deb..82fa06e0d 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/PartyNotiService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/PartyNotiService.java @@ -1,25 +1,30 @@ package gg.pingpong.api.user.noti.service; import java.util.List; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import gg.data.user.User; -import gg.pingpong.api.user.noti.service.sns.SlackPartybotService; +import gg.utils.sns.MessageSender; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Service @Slf4j +@Service +@RequiredArgsConstructor public class PartyNotiService { - private final SlackPartybotService slackPartybotService; - public PartyNotiService(SlackPartybotService slackPartybotService) { - this.slackPartybotService = slackPartybotService; - } + private static final String PARTY_MESSAGE = "파티요정🧚으로부터 편지가 도착했습니다.\n" + + "장소 및 시간을 상호 협의해서 진행해주세요.\n" + + "파티원이 연락두절이라면 $$마지 못해 신고$$ ----> https://42gg.kr"; + + private final MessageSender messageSender; @Transactional(readOnly = true) public void sendPartyNotifications(List users) { - slackPartybotService.partySend(users); + List intraUserNames = users.stream().map(User::getIntraId).collect(Collectors.toList()); + messageSender.sendGroup(intraUserNames, PARTY_MESSAGE); } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/SnsNotiService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/SnsNotiService.java index 3287ee3e1..7fb65b9c9 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/SnsNotiService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/SnsNotiService.java @@ -8,19 +8,18 @@ import gg.data.user.type.SnsType; import gg.pingpong.api.user.noti.dto.UserNotiDto; import gg.pingpong.api.user.noti.service.sns.NotiMailSender; -import gg.pingpong.api.user.noti.service.sns.SlackbotService; +import gg.pingpong.api.user.noti.service.sns.NotiSlackMessageSender; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Service @Slf4j +@Service +@RequiredArgsConstructor public class SnsNotiService { + private final NotiMailSender notiMailSender; - private final SlackbotService slackbotService; - public SnsNotiService(NotiMailSender notiMailSender, SlackbotService slackbotService) { - this.notiMailSender = notiMailSender; - this.slackbotService = slackbotService; - } + private final NotiSlackMessageSender notiSlackMessageSender; /** * 유저가 설정해둔 알림 옵션(email, slack, both, none)에 따라 알림을 전송합니다. @@ -38,10 +37,10 @@ public void sendSnsNotification(Noti noti, UserNotiDto user) { if (userSnsNotiOpt == SnsType.EMAIL) { notiMailSender.send(user, noti); } else if (userSnsNotiOpt == SnsType.SLACK) { - slackbotService.send(user, noti); + notiSlackMessageSender.send(user, noti); } else if (userSnsNotiOpt == SnsType.BOTH) { notiMailSender.send(user, noti); - slackbotService.send(user, noti); + notiSlackMessageSender.send(user, noti); } } @@ -61,10 +60,10 @@ public void sendSnsNotification(Noti noti, UserDto user) { if (userSnsNotiOpt == SnsType.EMAIL) { notiMailSender.send(user, noti); } else if (userSnsNotiOpt == SnsType.SLACK) { - slackbotService.send(user, noti); + notiSlackMessageSender.send(user, noti); } else if (userSnsNotiOpt == SnsType.BOTH) { notiMailSender.send(user, noti); - slackbotService.send(user, noti); + notiSlackMessageSender.send(user, noti); } } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiMailSender.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiMailSender.java index 3cbc55c50..944b60ac3 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiMailSender.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiMailSender.java @@ -1,28 +1,26 @@ package gg.pingpong.api.user.noti.service.sns; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import gg.auth.UserDto; import gg.data.noti.Noti; -import gg.pingpong.api.global.utils.AsyncMailSender; import gg.pingpong.api.user.noti.dto.UserNotiDto; import gg.pingpong.api.user.noti.service.NotiService; +import gg.utils.sns.MailSender; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +@Slf4j @Component @RequiredArgsConstructor -@Slf4j public class NotiMailSender { - private final JavaMailSender javaMailSender; - private final AsyncMailSender asyncMailSender; + private final NotiService notiService; + private final MailSender mailSender; + + private static final String SUBJECT = "핑퐁요정🧚으로부터 도착한 편지"; + /** * 알림을 전송합니다. * UserNotiDto 이용 @@ -30,18 +28,8 @@ public class NotiMailSender { * @param noti 알림 */ public void send(UserNotiDto user, Noti noti) { - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message); - try { - helper.setSubject("핑퐁요정🧚으로부터 도착한 편지"); - log.info(user.getEmail()); - helper.setTo(user.getEmail()); - helper.setText(notiService.getMessage(noti)); - } catch (MessagingException e) { - log.error("MessagingException message = {}", e.getMessage()); - } - log.info("Email send {}", user.getUserId()); - asyncMailSender.send(message); + String message = notiService.getMessage(noti); + mailSender.send(SUBJECT, user.getEmail(), message); } /** @@ -51,17 +39,7 @@ public void send(UserNotiDto user, Noti noti) { * @param noti 알림 */ public void send(UserDto user, Noti noti) { - MimeMessage message = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message); - try { - helper.setSubject("핑퐁요정🧚으로부터 도착한 편지"); - log.info(user.getEMail()); - helper.setTo(user.getEMail()); - helper.setText(notiService.getMessage(noti)); - } catch (MessagingException e) { - log.error("MessagingException message = {}", e.getMessage()); - } - log.info("Email send {}", user.getId()); - asyncMailSender.send(message); + String message = notiService.getMessage(noti); + mailSender.send(SUBJECT, user.getEMail(), message); } } diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiSlackMessageSender.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiSlackMessageSender.java new file mode 100644 index 000000000..7bcb5233f --- /dev/null +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/NotiSlackMessageSender.java @@ -0,0 +1,31 @@ +package gg.pingpong.api.user.noti.service.sns; + +import org.springframework.stereotype.Component; + +import gg.auth.UserDto; +import gg.data.noti.Noti; +import gg.pingpong.api.user.noti.dto.UserNotiDto; +import gg.pingpong.api.user.noti.service.NotiService; +import gg.utils.sns.MessageSender; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class NotiSlackMessageSender { + + private final NotiService notiService; + + private final MessageSender messageSender; + + public void send(UserDto user, Noti noti) { + String message = notiService.getMessage(noti); + messageSender.send(user.getIntraId(), message); + } + + public void send(UserNotiDto user, Noti noti) { + String message = notiService.getMessage(noti); + messageSender.send(user.getIntraId(), message); + } +} diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java index bf558daff..2e3dbe8ee 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackPartybotService.java @@ -1,7 +1,5 @@ package gg.pingpong.api.user.noti.service.sns; -import static gg.pingpong.api.user.noti.service.sns.SlackbotUtils.*; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,12 +16,14 @@ import gg.data.user.User; import gg.utils.external.ApiUtil; +import gg.utils.sns.slack.constant.SlackConstant; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -@Component @Slf4j +@Deprecated +@Component @RequiredArgsConstructor public class SlackPartybotService { @Value("${slack.xoxbToken}") @@ -32,15 +32,17 @@ public class SlackPartybotService { private final ApiUtil apiUtil; private String getSlackUserId(String intraId) { - String userEmail = intraId + intraEmailSuffix; + String userEmail = intraId + SlackConstant.INTRA_EMAIL_SUFFIX; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.add(HttpHeaders.AUTHORIZATION, authenticationPrefix + authenticationToken); + headers.setBearerAuth(authenticationToken); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.add("email", userEmail); - SlackUserInfoRes res = apiUtil.apiCall(userIdGetUrl, SlackUserInfoRes.class, + SlackUserInfoRes res = apiUtil.apiCall( + SlackConstant.GET_USER_ID_URL.getValue(), + SlackUserInfoRes.class, headers, parameters, HttpMethod.POST); if (res == null || res.getUser() == null) { @@ -52,13 +54,15 @@ private String getSlackUserId(String intraId) { private String createGroupChannelId(List slackUserIds) { HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.AUTHORIZATION, authenticationPrefix + authenticationToken); + httpHeaders.setBearerAuth(authenticationToken); httpHeaders.setContentType(MediaType.APPLICATION_JSON); Map bodyMap = new HashMap<>(); bodyMap.put("users", String.join(",", slackUserIds)); - ConversationRes res = apiUtil.apiCall(conversationsUrl, ConversationRes.class, + ConversationRes res = apiUtil.apiCall( + SlackConstant.CONVERSATION_URL.getValue(), + ConversationRes.class, httpHeaders, bodyMap, HttpMethod.POST); return res.channel.id; @@ -79,14 +83,16 @@ public void partySend(List users) { private void sendGroupMessage(String channelId, String message) { HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.AUTHORIZATION, authenticationPrefix + authenticationToken); + headers.setBearerAuth(authenticationToken); headers.setContentType(MediaType.APPLICATION_JSON); Map bodyMap = new HashMap<>(); bodyMap.put("channel", channelId); bodyMap.put("text", message); - apiUtil.apiCall(sendMessageUrl, String.class, headers, bodyMap, HttpMethod.POST); + apiUtil.apiCall( + SlackConstant.SEND_MESSAGE_URL.getValue(), + String.class, headers, bodyMap, HttpMethod.POST); } @Getter diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java index 5635c41b6..683514502 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotService.java @@ -1,7 +1,5 @@ package gg.pingpong.api.user.noti.service.sns; -import static gg.pingpong.api.user.noti.service.sns.SlackbotUtils.*; - import java.util.HashMap; import java.util.Map; @@ -20,10 +18,12 @@ import gg.pingpong.api.user.noti.service.NotiService; import gg.utils.exception.noti.SlackSendException; import gg.utils.external.ApiUtil; +import gg.utils.sns.slack.constant.SlackConstant; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +@Deprecated @Component @Slf4j @RequiredArgsConstructor @@ -35,15 +35,17 @@ public class SlackbotService { private final ApiUtil apiUtil; private String getSlackUserId(String intraId) { - String userEmail = intraId + intraEmailSuffix; + String userEmail = intraId + SlackConstant.INTRA_EMAIL_SUFFIX; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.add(HttpHeaders.AUTHORIZATION, authenticationPrefix + authenticationToken); + headers.setBearerAuth(authenticationToken); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.add("email", userEmail); - SlackUserInfoResponse res = apiUtil.apiCall(userIdGetUrl, SlackUserInfoResponse.class, + SlackUserInfoResponse res = apiUtil.apiCall( + SlackConstant.GET_USER_ID_URL.getValue(), + SlackUserInfoResponse.class, headers, parameters, HttpMethod.POST); if (res == null || res.getUser() == null) { @@ -55,14 +57,15 @@ private String getSlackUserId(String intraId) { private String getDmChannelId(String slackUserId) { HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.AUTHORIZATION, - authenticationPrefix + authenticationToken); + httpHeaders.setBearerAuth(authenticationToken); httpHeaders.setContentType(MediaType.APPLICATION_JSON); Map bodyMap = new HashMap<>(); bodyMap.put("users", slackUserId); - ConversationResponse res = apiUtil.apiCall(conversationsUrl, ConversationResponse.class, + ConversationResponse res = apiUtil.apiCall( + SlackConstant.CONVERSATION_URL.getValue(), + ConversationResponse.class, httpHeaders, bodyMap, HttpMethod.POST); return res.channel.id; @@ -94,14 +97,15 @@ private void startSendNoti(String intraId, Noti noti) { String message = notiService.getMessage(noti); HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.AUTHORIZATION, - authenticationPrefix + authenticationToken); + httpHeaders.setBearerAuth(authenticationToken); httpHeaders.setContentType(MediaType.APPLICATION_JSON); Map map = new HashMap<>(); map.put("channel", slackChannelId); map.put("text", message); - apiUtil.apiCall(sendMessageUrl, String.class, httpHeaders, map, HttpMethod.POST); + apiUtil.apiCall( + SlackConstant.SEND_MESSAGE_URL.getValue(), + String.class, httpHeaders, map, HttpMethod.POST); } @Getter diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotUtils.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotUtils.java deleted file mode 100644 index 2a271eac5..000000000 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/user/noti/service/sns/SlackbotUtils.java +++ /dev/null @@ -1,9 +0,0 @@ -package gg.pingpong.api.user.noti.service.sns; - -public class SlackbotUtils { - public static String conversationsUrl = "https://slack.com/api/conversations.open"; - public static String sendMessageUrl = "https://slack.com/api/chat.postMessage"; - public static String userIdGetUrl = "https://slack.com/api/users.lookupByEmail"; - public static String authenticationPrefix = "Bearer "; - public static String intraEmailSuffix = "@student.42seoul.kr"; -} diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/SnsNotiServiceUnitTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/SnsNotiServiceUnitTest.java index b06af7597..84d8ddfc8 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/SnsNotiServiceUnitTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/SnsNotiServiceUnitTest.java @@ -15,7 +15,7 @@ import gg.data.user.type.SnsType; import gg.pingpong.api.user.noti.dto.UserNotiDto; import gg.pingpong.api.user.noti.service.sns.NotiMailSender; -import gg.pingpong.api.user.noti.service.sns.SlackbotService; +import gg.pingpong.api.user.noti.service.sns.NotiSlackMessageSender; import gg.repo.game.out.GameUser; import gg.utils.annotation.UnitTest; @@ -23,10 +23,13 @@ @ExtendWith(MockitoExtension.class) @DisplayName("SnsNotiServiceTest") class SnsNotiServiceUnitTest { + @Mock NotiMailSender notiMailSender; + @Mock - SlackbotService slackbotService; + NotiSlackMessageSender notiSlackMessageSender; + @InjectMocks SnsNotiService snsNotiService; @@ -43,7 +46,7 @@ void sendSnsNotificationWithEmail() { //when snsNotiService.sendSnsNotification(noti, userDto); //then - verify(slackbotService, never()).send(any(UserDto.class), any(Noti.class)); + verify(notiSlackMessageSender, never()).send(any(UserDto.class), any(Noti.class)); verify(notiMailSender, times(1)).send(any(UserDto.class), any(Noti.class)); } @@ -58,7 +61,7 @@ void sendSnsNotificationWithSlack() { snsNotiService.sendSnsNotification(noti, userDto); //then verify(notiMailSender, never()).send(any(UserDto.class), any(Noti.class)); - verify(slackbotService, times(1)).send(any(UserDto.class), any(Noti.class)); + verify(notiSlackMessageSender, times(1)).send(any(UserDto.class), any(Noti.class)); } @Test @@ -72,7 +75,7 @@ void sendSnsNotificationWithBoth() { snsNotiService.sendSnsNotification(noti, userDto); //then verify(notiMailSender, times(1)).send(any(UserDto.class), any(Noti.class)); - verify(slackbotService, times(1)).send(any(UserDto.class), any(Noti.class)); + verify(notiSlackMessageSender, times(1)).send(any(UserDto.class), any(Noti.class)); } @Test @@ -86,7 +89,7 @@ void sendSnsNotificationWithNone() { snsNotiService.sendSnsNotification(noti, userDto); //then verify(notiMailSender, never()).send(any(UserDto.class), any(Noti.class)); - verify(slackbotService, never()).send(any(UserDto.class), any(Noti.class)); + verify(notiSlackMessageSender, never()).send(any(UserDto.class), any(Noti.class)); } @Test @@ -101,7 +104,7 @@ void sendSnsNotificationWithUserNotiDto() { snsNotiService.sendSnsNotification(noti, user); //then verify(notiMailSender, times(1)).send(user, noti); - verify(slackbotService, never()).send(any(UserNotiDto.class), any(Noti.class)); + verify(notiSlackMessageSender, never()).send(any(UserNotiDto.class), any(Noti.class)); } } } diff --git a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/sns/NotiMailSenderUnitTest.java b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/sns/NotiMailSenderUnitTest.java index 8df09d2fc..3f55cc074 100644 --- a/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/sns/NotiMailSenderUnitTest.java +++ b/gg-pingpong-api/src/test/java/gg/pingpong/api/user/noti/service/sns/NotiMailSenderUnitTest.java @@ -10,26 +10,26 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mail.javamail.JavaMailSender; import gg.auth.UserDto; import gg.data.noti.Noti; -import gg.pingpong.api.global.utils.AsyncMailSender; import gg.pingpong.api.user.noti.dto.UserNotiDto; import gg.pingpong.api.user.noti.service.NotiService; import gg.repo.game.out.GameUser; import gg.utils.annotation.UnitTest; +import gg.utils.sns.MailSender; @UnitTest @ExtendWith(MockitoExtension.class) @DisplayName("NotiMailSenderUnitTest") class NotiMailSenderUnitTest { + @Mock - JavaMailSender javaMailSender; - @Mock - AsyncMailSender asyncMailSender; + MailSender mailSender; + @Mock NotiService notiService; + @InjectMocks NotiMailSender notiMailSender; @@ -40,13 +40,15 @@ void sendToUserEmailByUserNotiDto() { GameUser gameUser = mock(GameUser.class); MimeMessage mimeMessage = mock(MimeMessage.class); when(gameUser.getEmail()).thenReturn("testEmail"); - when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + doNothing().when(mailSender).send(anyString(), anyString(), anyString()); when(notiService.getMessage(any(Noti.class))).thenReturn("Test message"); + // when notiMailSender.send(new UserNotiDto(gameUser), new Noti()); + // then - verify(javaMailSender).createMimeMessage(); - verify(asyncMailSender).send(mimeMessage); + verify(notiService).getMessage(any(Noti.class)); + verify(mailSender).send(anyString(), anyString(), anyString()); } @Test @@ -56,12 +58,13 @@ void sendToUserEmailByUserDto() { UserDto userDto = mock(UserDto.class); MimeMessage mimeMessage = mock(MimeMessage.class); when(userDto.getEMail()).thenReturn("testEmail"); - when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); + doNothing().when(mailSender).send(anyString(), anyString(), anyString()); when(notiService.getMessage(any(Noti.class))).thenReturn("Test message"); // when notiMailSender.send(userDto, new Noti()); + // then - verify(javaMailSender).createMimeMessage(); - verify(asyncMailSender).send(mimeMessage); + verify(notiService).getMessage(any(Noti.class)); + verify(mailSender).send(anyString(), anyString(), anyString()); } } diff --git a/gg-utils/build.gradle b/gg-utils/build.gradle index 7db24c688..5cd09ebf3 100644 --- a/gg-utils/build.gradle +++ b/gg-utils/build.gradle @@ -73,8 +73,10 @@ unitTestCoverageReport { dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' implementation "com.amazonaws:aws-java-sdk-s3:1.12.281" + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-mail' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' diff --git a/gg-utils/src/main/java/gg/utils/sns/MailSender.java b/gg-utils/src/main/java/gg/utils/sns/MailSender.java new file mode 100644 index 000000000..6171c78c2 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/MailSender.java @@ -0,0 +1,6 @@ +package gg.utils.sns; + +public interface MailSender { + + void send(String emailTo, String subject, String text); +} diff --git a/gg-utils/src/main/java/gg/utils/sns/MessageSender.java b/gg-utils/src/main/java/gg/utils/sns/MessageSender.java new file mode 100644 index 000000000..089f3f465 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/MessageSender.java @@ -0,0 +1,10 @@ +package gg.utils.sns; + +import java.util.List; + +public interface MessageSender { + + void send(String intraUsername, String message); + + void sendGroup(List intraUsernames, String message); +} diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/AsyncMailSender.java b/gg-utils/src/main/java/gg/utils/sns/mail/AsyncMailSender.java similarity index 50% rename from gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/AsyncMailSender.java rename to gg-utils/src/main/java/gg/utils/sns/mail/AsyncMailSender.java index 0eba18d45..bbe275d67 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/utils/AsyncMailSender.java +++ b/gg-utils/src/main/java/gg/utils/sns/mail/AsyncMailSender.java @@ -1,23 +1,32 @@ -package gg.pingpong.api.global.utils; +package gg.utils.sns.mail; import javax.mail.internet.MimeMessage; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import gg.utils.sns.MailSender; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +@Slf4j @Component @AllArgsConstructor -@Slf4j -public class AsyncMailSender { +public class AsyncMailSender implements MailSender { + private final JavaMailSender javaMailSender; @Async("asyncExecutor") - public void send(MimeMessage message) { + public void send(String emailTo, String subject, String text) { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message); try { + helper.setTo(emailTo); + helper.setSubject(subject); + helper.setText(text); + log.info("Send email to {}", emailTo); javaMailSender.send(message); } catch (Exception ex) { log.error(ex.getMessage()); diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/AsyncSlackMessageSender.java b/gg-utils/src/main/java/gg/utils/sns/slack/AsyncSlackMessageSender.java new file mode 100644 index 000000000..a0cb0916e --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/AsyncSlackMessageSender.java @@ -0,0 +1,46 @@ +package gg.utils.sns.slack; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import gg.utils.exception.noti.SlackSendException; +import gg.utils.sns.MessageSender; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AsyncSlackMessageSender implements MessageSender { + + private final SlackbotApiUtils slackbotApiUtils; + + @Async("asyncExecutor") + public void send(String intraUsername, String message) { + try { + String slackUsername = slackbotApiUtils.findSlackUserIdByIntraId(intraUsername); + String slackChannelName = slackbotApiUtils.createChannel(slackUsername); + log.info("slack alarm send to {}:{}", slackChannelName, slackUsername); + slackbotApiUtils.sendSlackMessage(message, slackChannelName); + } catch (SlackSendException e) { + log.error("SlackSendException message = {}", e.getMessage()); + } + } + + @Async("asyncExecutor") + public void sendGroup(List intraUsernames, String message) { + try { + List slackUsernames = intraUsernames.stream() + .map(slackbotApiUtils::findSlackUserIdByIntraId) + .collect(Collectors.toList()); + String channelName = slackbotApiUtils.createGroupChannel(slackUsernames); + log.info("slack alarm send to {}", channelName); + slackbotApiUtils.sendSlackMessage(message, channelName); + } catch (SlackSendException e) { + log.error("SlackSendException message = {}", e.getMessage()); + } + } +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java b/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java new file mode 100644 index 000000000..f86dc718f --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java @@ -0,0 +1,116 @@ +package gg.utils.sns.slack; + +import java.util.List; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import gg.utils.external.ApiUtil; +import gg.utils.sns.slack.constant.SlackConstant; +import gg.utils.sns.slack.response.ConversationResponse; +import gg.utils.sns.slack.response.SlackUserInfoResponse; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class SlackbotApiUtils { + + @Value("${slack.xoxbToken}") + private String authenticationToken; + + private final ApiUtil apiUtil; + + public String findSlackUserIdByIntraId(String intraId) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + httpHeaders.setBearerAuth(authenticationToken); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("email", convertToIntraEmail(intraId)); + + SlackUserInfoResponse res = apiUtil.apiCall( + SlackConstant.GET_USER_ID_URL.getValue(), + SlackUserInfoResponse.class, + httpHeaders, + params, + HttpMethod.POST + ); + + if (Objects.isNull(res) || Objects.isNull(res.getUser())) { + throw new RuntimeException("슬랙 API 고장으로 인한 NULL 참조" + intraId); + } + return res.getUser().getId(); + } + + private String convertToIntraEmail(String intraId) { + return intraId + SlackConstant.INTRA_EMAIL_SUFFIX; + } + + public String createChannel(String slackUser) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBearerAuth(authenticationToken); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("users", slackUser); + + ConversationResponse res = apiUtil.apiCall( + SlackConstant.GET_USER_ID_URL.getValue(), + ConversationResponse.class, + httpHeaders, + params, + HttpMethod.POST + ); + + if (Objects.isNull(res) || Objects.isNull(res.getChannel())) { + throw new RuntimeException("슬랙 API 고장으로 인한 NULL 참조" + slackUser); + } + return res.getChannel().getId(); + } + + public String createGroupChannel(List slackUserNames) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBearerAuth(authenticationToken); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("users", String.join(",", slackUserNames)); + + ConversationResponse res = apiUtil.apiCall( + SlackConstant.CONVERSATION_URL.getValue(), + ConversationResponse.class, + httpHeaders, + params, + HttpMethod.POST + ); + + if (Objects.isNull(res) || Objects.isNull(res.getChannel())) { + throw new RuntimeException("슬랙 API 고장으로 인한 NULL 참조" + slackUserNames); + } + return res.getChannel().getId(); + } + + public void sendSlackMessage(String message, String channelId) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(authenticationToken); + headers.setContentType(MediaType.APPLICATION_JSON); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("channel", channelId); + params.add("text", message); + + apiUtil.apiCall( + SlackConstant.SEND_MESSAGE_URL.getValue(), + String.class, + headers, + params, + HttpMethod.POST + ); + } +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/constant/SlackConstant.java b/gg-utils/src/main/java/gg/utils/sns/slack/constant/SlackConstant.java new file mode 100644 index 000000000..28a1750e1 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/constant/SlackConstant.java @@ -0,0 +1,16 @@ +package gg.utils.sns.slack.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SlackConstant { + + CONVERSATION_URL("https://slack.com/api/conversations.open"), + SEND_MESSAGE_URL("https://slack.com/api/chat.postMessage"), + GET_USER_ID_URL("https://slack.com/api/users.lookupByEmail"), + INTRA_EMAIL_SUFFIX("@student.42seoul.kr"); + + private final String value; +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/response/Channel.java b/gg-utils/src/main/java/gg/utils/sns/slack/response/Channel.java new file mode 100644 index 000000000..7829e5f13 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/response/Channel.java @@ -0,0 +1,9 @@ +package gg.utils.sns.slack.response; + +import lombok.Getter; + +@Getter +public class Channel { + + private String id; +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/response/ConversationResponse.java b/gg-utils/src/main/java/gg/utils/sns/slack/response/ConversationResponse.java new file mode 100644 index 000000000..0468c2b6d --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/response/ConversationResponse.java @@ -0,0 +1,11 @@ +package gg.utils.sns.slack.response; + +import lombok.Getter; + +@Getter +public class ConversationResponse { + + private Boolean ok; + + private Channel channel; +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUser.java b/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUser.java new file mode 100644 index 000000000..ecb317f2c --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUser.java @@ -0,0 +1,9 @@ +package gg.utils.sns.slack.response; + +import lombok.Getter; + +@Getter +public class SlackUser { + + private String id; +} diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUserInfoResponse.java b/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUserInfoResponse.java new file mode 100644 index 000000000..110ce4f2f --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/sns/slack/response/SlackUserInfoResponse.java @@ -0,0 +1,11 @@ +package gg.utils.sns.slack.response; + +import lombok.Getter; + +@Getter +public class SlackUserInfoResponse { + + private Boolean ok; + + private SlackUser user; +} From 59094b42733e23483434699be68492cf2fd6b2db Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:56:01 +0900 Subject: [PATCH 073/103] =?UTF-8?q?[Bug]=20Value=20yml=EC=B0=B8=EC=A1=B0?= =?UTF-8?q?=EA=B0=80=20=EC=95=84=EB=8B=8C=20String=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#958)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gg/agenda/api/user/ticket/service/TicketService.java | 2 +- .../api/global/security/service/CustomOAuth2UserService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 3601c3b1b..8ef66e6a1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -48,7 +48,7 @@ public class TicketService { private final AgendaProfileService agendaProfileService; private final AgendaProfileRepository agendaProfileRepository; - @Value("${info.web.pointHistoryUrl}") + @Value("https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id") private String pointHistoryUrl; private static final String selfDonation = "Provided points to the pool"; diff --git a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java index 349fc5ec5..f183934de 100644 --- a/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java +++ b/gg-pingpong-api/src/main/java/gg/pingpong/api/global/security/service/CustomOAuth2UserService.java @@ -60,7 +60,7 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { @Value("${info.image.defaultUrl}") private String defaultImageUrl; - @Value("${info.web.coalitionUrl}") + @Value("https://api.intra.42.fr/v2/users/{id}/coalitions") private String coalitionUrl; @Override From a72df3166309202db9338d60faf0c2597a2e7972 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:52:04 +0900 Subject: [PATCH 074/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=20=EC=83=9D=EC=84=B1=20API=20#944=20(#959)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/agenda/AgendaAdminRepository.java | 2 + .../repo/agenda/TicketAdminRepository.java | 14 ++ .../controller/TicketAdminController.java | 33 +++++ .../request/TicketAddAdminReqDto.java | 19 +++ .../response/TicketAddAdminResDto.java | 15 ++ .../ticket/service/TicketAdminService.java | 53 +++++++ .../CurrentAttendAgendaListResDto.java | 5 + ... => AgendaProfileAdminControllerTest.java} | 2 +- .../ticket/TicketAdminControllerTest.java | 140 ++++++++++++++++++ .../src/main/java/gg/data/agenda/Ticket.java | 12 ++ 10 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketAddAdminReqDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketAddAdminResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java rename gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/{AgendaProfileControllerAdminTest.java => AgendaProfileAdminControllerTest.java} (99%) create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java index d14c78ac7..2363f2ef6 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaAdminRepository.java @@ -14,4 +14,6 @@ public interface AgendaAdminRepository extends JpaRepository { @Query("SELECT a FROM Agenda a WHERE a.agendaKey = :agendaKey") Optional findByAgendaKey(UUID agendaKey); + + boolean existsByAgendaKey(UUID issuedFromKey); } diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java new file mode 100644 index 000000000..b5a225c98 --- /dev/null +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java @@ -0,0 +1,14 @@ +package gg.admin.repo.agenda; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; + +@Repository +public interface TicketAdminRepository extends JpaRepository { + Optional findByAgendaProfile(AgendaProfile agendaProfile); +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java new file mode 100644 index 000000000..6d9cf6f44 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java @@ -0,0 +1,33 @@ +package gg.agenda.api.admin.ticket.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; +import gg.agenda.api.admin.ticket.service.TicketAdminService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/admin/ticket") +public class TicketAdminController { + private final TicketAdminService ticketAdminService; + + /** + * 티켓 설정 추가 + * @param intraId 사용자 정보 + */ + @PostMapping + public ResponseEntity ticketAdminAdd(@RequestParam String intraId, + @RequestBody TicketAddAdminReqDto ticketAddAdminReqDto) { + Long ticketId = ticketAdminService.addTicket(intraId, ticketAddAdminReqDto); + TicketAddAdminResDto ticketAddAdminResDto = new TicketAddAdminResDto(ticketId); + return ResponseEntity.status(HttpStatus.CREATED).body(ticketAddAdminResDto); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketAddAdminReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketAddAdminReqDto.java new file mode 100644 index 000000000..d931522a5 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketAddAdminReqDto.java @@ -0,0 +1,19 @@ +package gg.agenda.api.admin.ticket.controller.request; + +import java.util.UUID; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TicketAddAdminReqDto { + + private UUID issuedFromKey; + + @Builder + public TicketAddAdminReqDto(UUID issuedFromKey) { + this.issuedFromKey = issuedFromKey; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketAddAdminResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketAddAdminResDto.java new file mode 100644 index 000000000..cf412b27d --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketAddAdminResDto.java @@ -0,0 +1,15 @@ +package gg.agenda.api.admin.ticket.controller.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TicketAddAdminResDto { + private Long ticketId; + + public TicketAddAdminResDto(Long ticketId) { + this.ticketId = ticketId; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java new file mode 100644 index 000000000..bbe1dfc96 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java @@ -0,0 +1,53 @@ +package gg.agenda.api.admin.ticket.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.Objects; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.AgendaProfileAdminRepository; +import gg.admin.repo.agenda.TicketAdminRepository; +import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TicketAdminService { + + private final TicketAdminRepository ticketAdminRepository; + private final AgendaProfileAdminRepository agendaProfileRepository; + private final AgendaAdminRepository agendaAdminRepository; + + /** + * 티켓 설정 추가 + * @param intraId 사용자 정보 + */ + @Transactional + public Long addTicket(String intraId, TicketAddAdminReqDto ticketAddAdminReqDto) { + AgendaProfile profile = agendaProfileRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + + UUID issuedFromKey = ticketAddAdminReqDto.getIssuedFromKey(); + + if (isRefundedTicket(issuedFromKey)) { + boolean result = agendaAdminRepository.existsByAgendaKey(issuedFromKey); + if (!result) { + throw new NotExistException(AGENDA_NOT_FOUND); + } + } + + Ticket ticket = Ticket.createAdminTicket(profile, issuedFromKey); + return ticketAdminRepository.save(ticket).getId(); + } + + private boolean isRefundedTicket(UUID issuedFromKey) { + return Objects.nonNull(issuedFromKey); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java index 9e410a6c1..1670d1be4 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java @@ -1,5 +1,6 @@ package gg.agenda.api.user.agendaprofile.controller.response; +import java.time.LocalDateTime; import java.util.UUID; import gg.data.agenda.AgendaTeamProfile; @@ -15,6 +16,8 @@ public class CurrentAttendAgendaListResDto { private UUID teamKey; private Boolean isOfficial; private String teamName; + private LocalDateTime agendaStartTime; + private String teamStatus; public CurrentAttendAgendaListResDto(AgendaTeamProfile agendaTeamProfile) { this.agendaId = agendaTeamProfile.getAgenda().getId().toString(); @@ -23,5 +26,7 @@ public CurrentAttendAgendaListResDto(AgendaTeamProfile agendaTeamProfile) { this.teamKey = agendaTeamProfile.getAgendaTeam().getTeamKey(); this.isOfficial = agendaTeamProfile.getAgenda().getIsOfficial(); this.teamName = agendaTeamProfile.getAgendaTeam().getName(); + this.agendaStartTime = agendaTeamProfile.getAgenda().getStartTime(); + this.teamStatus = agendaTeamProfile.getAgendaTeam().getStatus().toString(); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileAdminControllerTest.java similarity index 99% rename from gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java rename to gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileAdminControllerTest.java index c86e31f91..ce482e597 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileControllerAdminTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaprofile/AgendaProfileAdminControllerTest.java @@ -31,7 +31,7 @@ @IntegrationTest @Transactional @AutoConfigureMockMvc -public class AgendaProfileControllerAdminTest { +public class AgendaProfileAdminControllerTest { @Autowired private MockMvc mockMvc; @Autowired diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java new file mode 100644 index 000000000..a92830284 --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java @@ -0,0 +1,140 @@ +package gg.agenda.api.admin.ticket; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.UUID; + +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.admin.repo.agenda.TicketAdminRepository; +import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.data.agenda.type.AgendaStatus; +import gg.data.agenda.type.Location; +import gg.data.user.User; +import gg.data.user.type.RoleType; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaProfileFixture; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class TicketAdminControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private TestDataUtils testDataUtils; + @Autowired + private AgendaProfileFixture agendaProfileFixture; + @Autowired + private AgendaFixture agendaFixture; + @Autowired + private TicketAdminRepository ticketAdminRepository; + User user; + String accessToken; + AgendaProfile agendaProfile; + + @Nested + @DisplayName("티켓 발급 요청") + class AddTicket { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewAdminUser(RoleType.ADMIN); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + agendaProfile = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); + } + + @Test + @DisplayName("issuedFromKey = null로 티켓이 잘 생성되어 201 반환 및 ticketId 반환") + void createTicketWithNullIssuedFromKey() throws Exception { + // Given + String content = objectMapper.writeValueAsString(new TicketAddAdminReqDto(null)); + + // When + String responseContent = mockMvc.perform( + post("/agenda/admin/ticket") + .param("intraId", user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + + TicketAddAdminResDto response = objectMapper.readValue(responseContent, TicketAddAdminResDto.class); + Ticket createdTicket = ticketAdminRepository.findByAgendaProfile(agendaProfile) + .orElseThrow(); + // Then + assertThat(response.getTicketId()).isNotNull(); + assertThat(createdTicket.getAgendaProfile().getId()).isEqualTo(agendaProfile.getId()); + assertThat(createdTicket.getIssuedFrom()).isNull(); + assertThat(createdTicket.getIsApproved()).isTrue(); + assertThat(createdTicket.getIsUsed()).isFalse(); + } + + @Test + @DisplayName("존재하는 대회의 issuedFromKey 넣어 티켓(환불티켓생성)이 잘 생성되어 201 반환 및 ticketId 반환") + void createTicketWithNotNullIssuedFromKey() throws Exception { + // Given + User agendaCreateUser = testDataUtils.createNewUser(); + Agenda agenda = agendaFixture.createAgenda(agendaCreateUser.getIntraId(), AgendaStatus.OPEN); + String content = objectMapper.writeValueAsString(new TicketAddAdminReqDto(agenda.getAgendaKey())); + + // When + String responseContent = mockMvc.perform( + post("/agenda/admin/ticket") + .param("intraId", user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + + TicketAddAdminResDto response = objectMapper.readValue(responseContent, TicketAddAdminResDto.class); + Ticket createdTicket = ticketAdminRepository.findByAgendaProfile(agendaProfile) + .orElseThrow(); + // Then + assertThat(response.getTicketId()).isNotNull(); + assertThat(createdTicket.getAgendaProfile().getId()).isEqualTo(agendaProfile.getId()); + assertThat(createdTicket.getIssuedFrom()).isEqualTo(agenda.getAgendaKey()); + assertThat(createdTicket.getIsApproved()).isTrue(); + assertThat(createdTicket.getIsUsed()).isFalse(); + } + + @Test + @DisplayName("존재하지않는 대회의 issuedFromKey 넣어 404 반환") + void createTicketWithAgendaNotExit() throws Exception { + // Given + UUID nonExistentAgendaKey = UUID.randomUUID(); + String content = objectMapper.writeValueAsString(new TicketAddAdminReqDto(nonExistentAgendaKey)); + + // When & Then + mockMvc.perform( + post("/agenda/admin/ticket") + .param("intraId", user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNotFound()); + } + } +} diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index f9ea0e840..54520e976 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -99,6 +99,18 @@ public static Ticket createNotApporveTicket(AgendaProfile agendaProfile) { .build(); } + public static Ticket createAdminTicket(AgendaProfile agendaProfile, UUID issuedFromKey) { + return Ticket.builder() + .agendaProfile(agendaProfile) + .issuedFrom(issuedFromKey) + .usedTo(null) + .isApproved(true) + .approvedAt(LocalDateTime.now()) + .isUsed(false) + .usedAt(null) + .build(); + } + public void useTicket(UUID usedTo) { this.usedTo = usedTo; this.usedAt = LocalDateTime.now(); From 93b12df1e37c8848edcd111a53121ad3a2380920 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:57:30 +0900 Subject: [PATCH 075/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Slack=20?= =?UTF-8?q?=EB=B0=8F=20Mail=20=EB=AA=A8=EB=93=88=20URL=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#961=20(#963)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/utils/sns/slack/SlackbotApiUtils.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java b/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java index f86dc718f..2a954a23b 100644 --- a/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java +++ b/gg-utils/src/main/java/gg/utils/sns/slack/SlackbotApiUtils.java @@ -1,6 +1,8 @@ package gg.utils.sns.slack; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import org.springframework.beans.factory.annotation.Value; @@ -49,7 +51,7 @@ public String findSlackUserIdByIntraId(String intraId) { } private String convertToIntraEmail(String intraId) { - return intraId + SlackConstant.INTRA_EMAIL_SUFFIX; + return intraId + SlackConstant.INTRA_EMAIL_SUFFIX.getValue(); } public String createChannel(String slackUser) { @@ -57,11 +59,11 @@ public String createChannel(String slackUser) { httpHeaders.setBearerAuth(authenticationToken); httpHeaders.setContentType(MediaType.APPLICATION_JSON); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("users", slackUser); + Map params = new HashMap<>(); + params.put("users", slackUser); ConversationResponse res = apiUtil.apiCall( - SlackConstant.GET_USER_ID_URL.getValue(), + SlackConstant.CONVERSATION_URL.getValue(), ConversationResponse.class, httpHeaders, params, @@ -101,9 +103,9 @@ public void sendSlackMessage(String message, String channelId) { headers.setBearerAuth(authenticationToken); headers.setContentType(MediaType.APPLICATION_JSON); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("channel", channelId); - params.add("text", message); + Map params = new HashMap<>(); + params.put("channel", channelId); + params.put("text", message); apiUtil.apiCall( SlackConstant.SEND_MESSAGE_URL.getValue(), From aa7837457a92c81d793f8523cb80c5ea94e99250 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:57:44 +0900 Subject: [PATCH 076/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Agenda=20Admin?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=EC=A1=B0=ED=9A=8C=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#962=20(#964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/response/AgendaAdminResDto.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java index 9d4027d8d..c3e0d45f5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/response/AgendaAdminResDto.java @@ -26,6 +26,8 @@ public class AgendaAdminResDto { private String agendaTitle; + private String agendaContent; + private String agendaDeadLine; private String agendaStartTime; @@ -36,6 +38,8 @@ public class AgendaAdminResDto { private int agendaMaxTeam; + private int agendaMinTeam; + private int agendaMinPeople; private int agendaMaxPeople; @@ -54,15 +58,17 @@ public class AgendaAdminResDto { public AgendaAdminResDto(Long agendaId, UUID agendaKey, String agendaTitle, String agendaDeadLine, String agendaStartTime, String agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, int agendaMinPeople, int agendaMaxPeople, Location agendaLocation, Boolean isRanking, Boolean isOfficial, - AgendaStatus agendaStatus, String agendaPosterUrl) { + AgendaStatus agendaStatus, String agendaPosterUrl, String agendaContent, int agendaMinTeam) { this.agendaId = agendaId; this.agendaKey = agendaKey; this.agendaTitle = agendaTitle; + this.agendaContent = agendaContent; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; this.agendaEndTime = agendaEndTime; this.agendaCurrentTeam = agendaCurrentTeam; this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinTeam = agendaMinTeam; this.agendaMinPeople = agendaMinPeople; this.agendaMaxPeople = agendaMaxPeople; this.agendaLocation = agendaLocation; @@ -80,11 +86,13 @@ public interface MapStruct { @Mapping(target = "agendaId", source = "id") @Mapping(target = "agendaKey", source = "agendaKey") @Mapping(target = "agendaTitle", source = "title") + @Mapping(target = "agendaContent", source = "content") @Mapping(target = "agendaDeadLine", source = "deadline") @Mapping(target = "agendaStartTime", source = "startTime") @Mapping(target = "agendaEndTime", source = "endTime") @Mapping(target = "agendaCurrentTeam", source = "currentTeam") @Mapping(target = "agendaMaxTeam", source = "maxTeam") + @Mapping(target = "agendaMinTeam", source = "minTeam") @Mapping(target = "agendaMinPeople", source = "minPeople") @Mapping(target = "agendaMaxPeople", source = "maxPeople") @Mapping(target = "agendaLocation", source = "location") From c79a4c4923c8368a3d09cca44a252112e5175a3a Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:59:30 +0900 Subject: [PATCH 077/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Confirm?= =?UTF-8?q?=EB=90=9C=20Team=20DeadLine=20=EC=A0=84=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#960=20(#965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 7 +-- .../response/TeamDetailsResDto.java | 2 + .../agendateam/service/AgendaTeamService.java | 2 +- .../agendateam/AgendaTeamControllerTest.java | 62 ++++++++++++++++++- .../main/java/gg/data/agenda/AgendaTeam.java | 6 ++ 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 878fc56c0..9699d1b26 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,6 +1,5 @@ package gg.agenda.api.user.agendateam.controller; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -99,14 +98,14 @@ public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto */ @PatchMapping("/cancel") public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login UserDto user, - @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - UUID teamKey = teamKeyReqDto.getTeamKey(); + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto) { AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); - agendaTeam.agendaTeamStatusMustBeOpen(); agendaTeam.getAgenda().agendaStatusMustBeOpen(); if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + agendaTeam.agendaTeamStatusMustBeOpenAndConfirm(); agendaTeamService.leaveTeamAll(agendaTeam); } else { + agendaTeam.agendaTeamStatusMustBeOpen(); agendaTeamService.leaveTeamMate(agendaTeam, user); } return ResponseEntity.noContent().build(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java index 84953ea22..adc435f15 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/response/TeamDetailsResDto.java @@ -18,6 +18,7 @@ public class TeamDetailsResDto { private AgendaTeamStatus teamStatus; private Location teamLocation; private String teamContent; + private boolean teamIsPrivate; private List teamMates; public TeamDetailsResDto(AgendaTeam agendaTeam, List agendaTeamProfileList) { @@ -26,6 +27,7 @@ public TeamDetailsResDto(AgendaTeam agendaTeam, List agendaTe this.teamStatus = agendaTeam.getStatus(); this.teamLocation = agendaTeam.getLocation(); this.teamContent = agendaTeam.getContent(); + this.teamIsPrivate = agendaTeam.getIsPrivate(); this.teamMates = agendaTeamProfileList.stream() .map(TeamMateDto::new) .collect(Collectors.toList()); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 735febd34..6aeea941b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -194,7 +194,7 @@ public AgendaTeam getAgendaTeam(UUID teamKey) { * 팀장이 팀 나가기 * @param agendaTeam 팀 */ - @Transactional(propagation = Propagation.MANDATORY) + @Transactional public void leaveTeamAll(AgendaTeam agendaTeam) { List agendaTeamProfiles = agendaTeamProfileRepository .findByAgendaTeamAndIsExistTrue(agendaTeam); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index c08bcda35..4b0e72679 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -908,6 +908,44 @@ public void leaveTeamLeaderSuccess() throws Exception { }); } + @Test + @DisplayName("200 Confirm 팀 리더 팀 나가기 성공") + public void leaveTeamLeaderTeamStatusConfirmSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + AgendaTeamProfile atpLeader = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + // when + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; + assertThat(updatedTeam.getMateCount()).isEqualTo(0); + AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assert updatedAtp != null; + assertThat(updatedAtp.getIsExist()).isFalse(); + AgendaTeamProfile updatedAtpLeader = agendaTeamProfileRepository.findById(atpLeader.getId()).orElse(null); + assert updatedAtpLeader != null; + assertThat(updatedAtpLeader.getIsExist()).isFalse(); + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtp.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtpLeader.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + } + @Test @DisplayName("404 agenda 없음으로 인한 실패") public void noAgendaFail() throws Exception { @@ -942,16 +980,34 @@ public void noTeamFail() throws Exception { @Test @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀장의 나가기 실패") public void notValidAgendaTeamStatusWhenTeamLeader() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CANCEL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀원의 나가기 실패") + public void notValidAgendaTeamStatusConfirmWhenTeamMate() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( patch("/agenda/team/cancel") - .header("Authorization", "Bearer " + seoulUserAccessToken) + .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) .contentType(MediaType.APPLICATION_JSON)) @@ -960,7 +1016,7 @@ public void notValidAgendaTeamStatusWhenTeamLeader() throws Exception { @Test @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀원의 나가기 실패") - public void notValidAgendaTeamStatusWhenTeamMate() throws Exception { + public void notValidAgendaTeamStatusCoiWhenTeamMate() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); @@ -998,7 +1054,7 @@ public void notValidAgendaDeadline() throws Exception { } @Test - @DisplayName("403 참여 불가능한 Agenda Status 으로 인한 실패") + @DisplayName("403 탈퇴 불가능한 Agenda Status 으로 인한 실패") public void notValidAgendaStatus() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(FINISH); diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index a3c57c143..b561074fb 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -179,6 +179,12 @@ public void updateLocation(Location location, List profiles) this.location = location; } + public void agendaTeamStatusMustBeOpenAndConfirm() { + if (this.status == CANCEL) { + throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); + } + } + public void agendaTeamStatusMustBeOpen() { if (this.status == CANCEL) { throw new BusinessException(AGENDA_TEAM_ALREADY_CANCEL); From 7e42a1d912203c9a3d3ca8c33315760ee8b75c89 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:27:37 +0900 Subject: [PATCH 078/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Pagenati?= =?UTF-8?q?on=20=EC=9D=91=EB=8B=B5=20totalSize=20=EC=B6=94=EA=B0=80=20#953?= =?UTF-8?q?=20(#966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaAdminController.java | 15 ++++- .../agenda/service/AgendaAdminService.java | 5 +- .../AgendaAnnouncementAdminController.java | 14 +++-- .../AgendaAnnouncementAdminService.java | 5 +- .../controller/AgendaTeamAdminController.java | 14 +++-- .../service/AgendaTeamAdminService.java | 5 +- .../agenda/controller/AgendaController.java | 12 +++- .../user/agenda/service/AgendaService.java | 5 +- .../AgendaAnnouncementController.java | 16 +++-- .../service/AgendaAnnouncementService.java | 3 +- .../controller/AgendaProfileController.java | 27 +++++++-- .../service/AgendaProfileFindService.java | 22 +++---- .../controller/AgendaTeamController.java | 44 +++++++++++--- .../agendateam/service/AgendaTeamService.java | 41 +++---------- .../ticket/controller/TicketController.java | 22 +++++-- .../user/ticket/service/TicketService.java | 45 +++++++------- .../controller/AgendaAdminControllerTest.java | 22 ++++--- .../service/AgendaAdminServiceTest.java | 4 +- ...AgendaAnnouncementAdminControllerTest.java | 22 ++++--- .../AgendaTeamAdminControllerTest.java | 13 ++-- .../service/AgendaTeamAdminServiceTest.java | 3 +- .../controller/AgendaControllerTest.java | 44 +++++++++----- .../agenda/service/AgendaServiceTest.java | 3 +- .../AgendaAnnouncementControllerTest.java | 23 +++++--- .../AgendaAnnouncementServiceTest.java | 3 +- .../AgendaProfileControllerTest.java | 29 +++++---- .../agendateam/AgendaTeamControllerTest.java | 42 +++++++++---- .../api/user/ticket/TicketControllerTest.java | 59 ++++++++++++------- .../agenda/AgendaAnnouncementRepository.java | 5 +- .../java/gg/utils/dto/PageResponseDto.java | 31 ++++++++++ 30 files changed, 385 insertions(+), 213 deletions(-) create mode 100644 gg-utils/src/main/java/gg/utils/dto/PageResponseDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java index bc000da17..fceba9d3e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/AgendaAdminController.java @@ -9,6 +9,7 @@ import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -26,7 +27,9 @@ import gg.agenda.api.admin.agenda.controller.response.AgendaAdminResDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminSimpleResDto; import gg.agenda.api.admin.agenda.service.AgendaAdminService; +import gg.data.agenda.Agenda; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.exception.custom.InvalidParameterException; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -41,14 +44,20 @@ public class AgendaAdminController { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Agenda 요청 리스트 조회 성공")}) @GetMapping("/request/list") - public ResponseEntity> agendaList(@ModelAttribute @Valid PageRequestDto pageDto) { + public ResponseEntity> agendaList( + @ModelAttribute @Valid PageRequestDto pageDto) { int page = pageDto.getPage(); int size = pageDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List agendaDtos = agendaAdminService.getAgendaRequestList(pageable).stream() + + Page agendaRequestList = agendaAdminService.getAgendaRequestList(pageable); + + List agendaDtos = agendaRequestList.stream() .map(AgendaAdminResDto.MapStruct.INSTANCE::toAgendaAdminResDto) .collect(Collectors.toList()); - return ResponseEntity.ok(agendaDtos); + PageResponseDto pageResponseDto = PageResponseDto.of( + agendaRequestList.getTotalElements(), agendaDtos); + return ResponseEntity.ok(pageResponseDto); } @GetMapping("/list") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index 773ef3fd5..e1d610e2c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,8 +43,8 @@ public class AgendaAdminService { private String defaultUri; @Transactional(readOnly = true) - public List getAgendaRequestList(Pageable pageable) { - return agendaAdminRepository.findAll(pageable).getContent(); + public Page getAgendaRequestList(Pageable pageable) { + return agendaAdminRepository.findAll(pageable); } @Transactional diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java index d5d8505c1..1d02ab5ad 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminController.java @@ -6,6 +6,7 @@ import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -23,6 +24,7 @@ import gg.agenda.api.admin.agendaannouncement.service.AgendaAnnouncementAdminService; import gg.data.agenda.AgendaAnnouncement; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import lombok.RequiredArgsConstructor; @RestController @@ -33,17 +35,21 @@ public class AgendaAnnouncementAdminController { private final AgendaAnnouncementAdminService agendaAnnouncementAdminService; @GetMapping() - public ResponseEntity> agendaAnnouncementList( + public ResponseEntity> agendaAnnouncementList( @RequestParam("agenda_key") UUID agendaKey, @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List announcements = agendaAnnouncementAdminService + + Page agendaAnnouncementList = agendaAnnouncementAdminService .getAgendaAnnouncementList(agendaKey, pageable); - List announceDtos = announcements.stream() + + List announceDtos = agendaAnnouncementList.stream() .map(AgendaAnnouncementAdminResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); - return ResponseEntity.ok(announceDtos); + PageResponseDto pageResponseDto = PageResponseDto.of( + agendaAnnouncementList.getTotalElements(), announceDtos); + return ResponseEntity.ok(pageResponseDto); } @PatchMapping() diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java index 1ce2b2cc3..7b4488a2b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendaannouncement/service/AgendaAnnouncementAdminService.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,10 +27,10 @@ public class AgendaAnnouncementAdminService { private final AgendaAnnouncementAdminRepository agendaAnnouncementAdminRepository; @Transactional(readOnly = true) - public List getAgendaAnnouncementList(UUID agendaKey, Pageable pageable) { + public Page getAgendaAnnouncementList(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - return agendaAnnouncementAdminRepository.findAllByAgenda(agenda, pageable).getContent(); + return agendaAnnouncementAdminRepository.findAllByAgenda(agenda, pageable); } @Transactional diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index 833cbd02d..b8b0d7360 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -6,6 +6,7 @@ import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -26,6 +27,7 @@ import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import lombok.RequiredArgsConstructor; @RestController @@ -36,16 +38,20 @@ public class AgendaTeamAdminController { private final AgendaTeamAdminService agendaTeamAdminService; @GetMapping("/list") - public ResponseEntity> agendaTeamList(@RequestParam("agenda_key") UUID agendaKey, + public ResponseEntity> agendaTeamList(@RequestParam("agenda_key") UUID agendaKey, @ModelAttribute @Valid PageRequestDto pageRequestDto) { int page = pageRequestDto.getPage(); int size = pageRequestDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List agendaTeamList = agendaTeamAdminService.getAgendaTeamList(agendaKey, pageable); - List agendaTeamResDtoList = agendaTeamList.stream() + + Page agendaTeamList = agendaTeamAdminService.getAgendaTeamList(agendaKey, pageable); + + List agendaTeamResDtos = agendaTeamList.stream() .map(AgendaTeamResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); - return ResponseEntity.ok(agendaTeamResDtoList); + PageResponseDto pageResponseDto = PageResponseDto.of( + agendaTeamList.getTotalElements(), agendaTeamResDtos); + return ResponseEntity.ok(pageResponseDto); } @GetMapping diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index 93a5abccd..3e336c0fc 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -6,6 +6,7 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,10 +37,10 @@ public class AgendaTeamAdminService { private final AgendaTeamProfileAdminRepository agendaTeamProfileAdminRepository; @Transactional(readOnly = true) - public List getAgendaTeamList(UUID agendaKey, Pageable pageable) { + public Page getAgendaTeamList(UUID agendaKey, Pageable pageable) { Agenda agenda = agendaAdminRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - return agendaTeamAdminRepository.findAllByAgenda(agenda, pageable).getContent(); + return agendaTeamAdminRepository.findAllByAgenda(agenda, pageable); } @Transactional(readOnly = true) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index ab339be3b..d8bb4ad13 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -9,6 +9,7 @@ import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -35,6 +36,7 @@ import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.exception.custom.InvalidParameterException; import gg.utils.exception.user.UserImageLargeException; import io.swagger.v3.oas.annotations.Parameter; @@ -80,16 +82,20 @@ public ResponseEntity agendaAdd(@Login @Parameter(hidden = true } @GetMapping("/history") - public ResponseEntity> agendaListHistory( + public ResponseEntity> agendaListHistory( @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); - List agendas = agendaService.findHistoryAgendaList(pageable); + + Page agendas = agendaService.findHistoryAgendaList(pageable); + List agendaSimpleResDtoList = agendas.stream() .map(AgendaSimpleResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); - return ResponseEntity.ok(agendaSimpleResDtoList); + PageResponseDto pageResponseDto = PageResponseDto.of( + agendas.getTotalElements(), agendaSimpleResDtoList); + return ResponseEntity.ok(pageResponseDto); } @PatchMapping("/finish") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 295b74401..46cbfe25e 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -87,8 +88,8 @@ public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster } @Transactional(readOnly = true) - public List findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, pageable).getContent(); + public Page findHistoryAgendaList(Pageable pageable) { + return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, pageable); } @Transactional diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java index 0696f12c5..0d043718c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java @@ -6,6 +6,7 @@ import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -26,7 +27,9 @@ import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import lombok.RequiredArgsConstructor; @RestController @@ -48,16 +51,21 @@ public ResponseEntity agendaAnnouncementAdd(@Login UserDto user, @RequestP } @GetMapping - public ResponseEntity> agendaAnnouncementList( + public ResponseEntity> agendaAnnouncementList( @RequestParam("agenda_key") UUID agendaKey, @ModelAttribute @Valid PageRequestDto pageRequest) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List announceDto = agendaAnnouncementService - .findAnnouncementListByAgenda(pageable, agenda).stream() + + Page announcementList = agendaAnnouncementService + .findAnnouncementListByAgenda(pageable, agenda); + + List announceDto = announcementList.stream() .map(AgendaAnnouncementResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); - return ResponseEntity.ok(announceDto); + PageResponseDto pageResponseDto = PageResponseDto.of( + announcementList.getTotalElements(), announceDto); + return ResponseEntity.ok(pageResponseDto); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java index c1d44087d..bd17cc9cb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,7 +28,7 @@ public void addAgendaAnnouncement(AgendaAnnouncementCreateReqDto announceCreateD } @Transactional(readOnly = true) - public List findAnnouncementListByAgenda(Pageable pageable, Agenda agenda) { + public Page findAnnouncementListByAgenda(Pageable pageable, Agenda agenda) { return agendaAnnouncementRepository.findListByAgenda(pageable, agenda); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 8299b2353..3ced617e5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -1,9 +1,11 @@ package gg.agenda.api.user.agendaprofile.controller; import java.util.List; +import java.util.stream.Collectors; import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -23,10 +25,13 @@ import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; +import gg.agenda.api.user.agendateam.controller.response.TeamMateDto; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaTeamProfile; import gg.data.user.type.RoleType; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -99,16 +104,26 @@ public ResponseEntity> getCurrentAttendAgend * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ @GetMapping("/history/list") - public ResponseEntity> getAttendedAgendaList( + public ResponseEntity> getAttendedAgendaList( @Login @Parameter(hidden = true) UserDto user, @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); String intraId = user.getIntraId(); - Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List attendedAgendaList = agendaProfileFindService.findAttendedAgenda( - intraId, pageable); - return ResponseEntity.ok(attendedAgendaList); + + Page attendedAgendaList = agendaProfileFindService + .findAttendedAgenda(intraId, pageable); + + List attendedAgendaDtos = attendedAgendaList.stream() + .map(agendaTeamProfile -> { + List teamMates = agendaProfileFindService + .findTeamMatesFromAgendaTeam(agendaTeamProfile.getAgendaTeam()); + return new AttendedAgendaListResDto(agendaTeamProfile, teamMates); + }) + .collect(Collectors.toList()); + + PageResponseDto pageResponseDto = PageResponseDto.of( + attendedAgendaList.getTotalElements(), attendedAgendaDtos); + return ResponseEntity.ok(pageResponseDto); } } - diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index c1b13349c..d87f55c8d 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -14,6 +14,7 @@ import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.AgendaProfile; +import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaProfileRepository; @@ -72,25 +73,16 @@ public List findCurrentAttendAgenda(String intraI .collect(Collectors.toList()); } - /** - * 자기가 참여했던 Agenda 목록 조회하는 메서드 - * @param intraId,pageable 페이지네이션 요청 정보, 로그인한 유저의 id - */ @Transactional(readOnly = true) - public List findAttendedAgenda(String intraId, Pageable pageable) { + public Page findAttendedAgenda(String intraId, Pageable pageable) { AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - - Page agendaTeamProfilePage = - agendaTeamProfileRepository.findByProfileAndIsExistTrueAndAgendaStatus( + return agendaTeamProfileRepository.findByProfileAndIsExistTrueAndAgendaStatus( agendaProfile, AgendaStatus.FINISH, pageable); + } - return agendaTeamProfilePage.getContent().stream() - .map(agendaTeamProfile -> { - List agendaTeamProfiles = agendaTeamProfileRepository - .findByAgendaTeamAndIsExistTrue(agendaTeamProfile.getAgendaTeam()); - return new AttendedAgendaListResDto(agendaTeamProfile, agendaTeamProfiles); - }) - .collect(Collectors.toList()); + @Transactional(readOnly = true) + public List findTeamMatesFromAgendaTeam(AgendaTeam agendaTeam) { + return agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 9699d1b26..35bd35fdd 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,11 +1,15 @@ package gg.agenda.api.user.agendateam.controller; +import static gg.data.agenda.type.AgendaTeamStatus.*; + import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -32,7 +36,9 @@ import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Coalition; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -116,13 +122,24 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ @GetMapping("/open/list") - public ResponseEntity> openTeamList(@ModelAttribute @Valid PageRequestDto pageRequest, - @RequestParam("agenda_key") UUID agendaKey) { + public ResponseEntity> openTeamList( + @ModelAttribute @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List openTeamResDtoList = agendaTeamService.listOpenTeam(agendaKey, pageable); - return ResponseEntity.ok(openTeamResDtoList); + + Page openTeams = agendaTeamService.findAgendaTeamWithStatus(agendaKey, OPEN, pageable); + + List openTeamResDtoList = openTeams.stream() + .map(agendaTeam -> { + List coalitions = agendaTeamService.getCoalitionsFromAgendaTeam(agendaTeam); + return new OpenTeamResDto(agendaTeam, coalitions); + }) + .collect(Collectors.toList()); + + PageResponseDto pageResponseDto = PageResponseDto.of( + openTeams.getTotalElements(), openTeamResDtoList); + return ResponseEntity.ok(pageResponseDto); } /** @@ -130,13 +147,24 @@ public ResponseEntity> openTeamList(@ModelAttribute @Valid * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ @GetMapping("/confirm/list") - public ResponseEntity> confirmTeamList(@ModelAttribute @Valid PageRequestDto pageRequest, - @RequestParam("agenda_key") UUID agendaKey) { + public ResponseEntity> confirmTeamList( + @ModelAttribute @Valid PageRequestDto pageRequest, @RequestParam("agenda_key") UUID agendaKey) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List confirmTeamResDto = agendaTeamService.listConfirmTeam(agendaKey, pageable); - return ResponseEntity.ok(confirmTeamResDto); + + Page confirmTeams = agendaTeamService.findAgendaTeamWithStatus(agendaKey, CONFIRM, pageable); + + List confirmTeamResDtoList = confirmTeams.stream() + .map(agendaTeam -> { + List coalitions = agendaTeamService.getCoalitionsFromAgendaTeam(agendaTeam); + return new ConfirmTeamResDto(agendaTeam, coalitions); + }) + .collect(Collectors.toList()); + + PageResponseDto pageResponseDto = PageResponseDto.of( + confirmTeams.getTotalElements(), confirmTeamResDtoList); + return ResponseEntity.ok(pageResponseDto); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 6aeea941b..18a92cc01 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -9,6 +9,7 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -29,6 +30,7 @@ import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; +import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; import gg.repo.agenda.AgendaProfileRepository; @@ -230,46 +232,17 @@ public void leaveTeam(AgendaTeam agendaTeam, AgendaTeamProfile agendaTeamProfile } } - /** - * 아젠다 팀 공개 모집인 팀 목록 조회 - * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 - */ @Transactional(readOnly = true) - public List listOpenTeam(UUID agendaKey, Pageable pageable) { + public Page findAgendaTeamWithStatus(UUID agendaKey, AgendaTeamStatus status, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - List agendaTeams = agendaTeamRepository.findByAgendaAndStatusAndIsPrivateFalse(agenda, OPEN, - pageable).getContent(); - return agendaTeams.stream() - .map(agendaTeam -> { - List coalitions = agendaTeamProfileRepository - .findByAgendaTeamAndIsExistTrue(agendaTeam).stream() - .map(agendaTeamProfile -> agendaTeamProfile.getProfile().getCoalition()) - .collect(Collectors.toList()); - return new OpenTeamResDto(agendaTeam, coalitions); - }) - .collect(Collectors.toList()); + return agendaTeamRepository.findByAgendaAndStatusAndIsPrivateFalse(agenda, status, pageable); } - /** - * 아젠다 팀 확정된 팀 목록 조회 - * @param pageable 페이지네이션 요청 정보, agendaId 아젠다 아이디 - */ @Transactional(readOnly = true) - public List listConfirmTeam(UUID agendaKey, Pageable pageable) { - Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) - .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - - List agendaTeams = agendaTeamRepository.findByAgendaAndStatus(agenda, CONFIRM, pageable) - .getContent(); - return agendaTeams.stream() - .map(agendaTeam -> { - List coalitions = agendaTeamProfileRepository - .findByAgendaTeamAndIsExistTrue(agendaTeam).stream() - .map(agendaTeamProfile -> agendaTeamProfile.getProfile().getCoalition()) - .collect(Collectors.toList()); - return new ConfirmTeamResDto(agendaTeam, coalitions); - }) + public List getCoalitionsFromAgendaTeam(AgendaTeam agendaTeam) { + return agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam).stream() + .map(agendaTeamProfile -> agendaTeamProfile.getProfile().getCoalition()) .collect(Collectors.toList()); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 3e94f68f4..d6680bc9f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -1,10 +1,12 @@ package gg.agenda.api.user.ticket.controller; import java.util.List; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -20,13 +22,16 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agenda.service.AgendaService; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.Ticket; import gg.utils.cookie.CookieUtil; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.exception.user.TokenNotValidException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -37,6 +42,7 @@ public class TicketController { private final CookieUtil cookieUtil; private final TicketService ticketService; + private final AgendaService agendaService; /** * 티켓 설정 추가 @@ -83,12 +89,20 @@ public ResponseEntity ticketApproveModify(@Parameter(hidden = true) @Login * @param pageRequest 페이지 정보 */ @GetMapping("/history") - public ResponseEntity> ticketHistoryList(@Parameter(hidden = true) @Login UserDto user, - @ModelAttribute @Valid PageRequestDto pageRequest) { + public ResponseEntity> ticketHistoryList( + @Parameter(hidden = true) @Login UserDto user, @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - List tickets = ticketService.listTicketHistory(user, pageable); - return ResponseEntity.ok().body(tickets); + + Page tickets = ticketService.findTicketsByUserId(user.getId(), pageable); + + List ticketDtos = tickets.stream() + .map(ticketService::convertAgendaKeyToTitleWhereIssuedFromAndUsedTo) + .collect(Collectors.toList()); + + PageResponseDto pageResponseDto = PageResponseDto.of( + tickets.getTotalElements(), ticketDtos); + return ResponseEntity.ok(pageResponseDto); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 8ef66e6a1..6da5a8966 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -170,33 +169,29 @@ private void processTicketApproval(AgendaProfile profile, Ticket setUpTicket, ticketRepository.save(setUpTicket); } - /** - * 티켓 이력 조회 - * @param user 사용자 정보 - * @param pageable 페이지 정보 - * @return 티켓 이력 목록 - */ @Transactional(readOnly = true) - public List listTicketHistory(UserDto user, Pageable pageable) { - AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) + public Page findTicketsByUserId(Long userId, Pageable pageable) { + AgendaProfile profile = agendaProfileRepository.findByUserId(userId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + return ticketRepository.findByAgendaProfileId(profile.getId(), pageable); + } - Page tickets = ticketRepository.findByAgendaProfileId(profile.getId(), pageable); - - List ticketHistoryResDtos = tickets.getContent().stream() - .map(TicketHistoryResDto::new) - .collect(Collectors.toList()); - - for (TicketHistoryResDto dto : ticketHistoryResDtos) { - if (dto.getIssuedFromKey() != null) { - Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getIssuedFromKey()).orElse(null); - dto.changeIssuedFrom(agenda); - } - if (dto.getUsedToKey() != null) { - Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getUsedToKey()).orElse(null); - dto.changeUsedTo(agenda); - } + /** + * 티켓 이력 조회 + */ + @Transactional(readOnly = true) + public TicketHistoryResDto convertAgendaKeyToTitleWhereIssuedFromAndUsedTo(Ticket ticket) { + TicketHistoryResDto dto = new TicketHistoryResDto(ticket); + if (dto.getIssuedFromKey() != null) { + Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getIssuedFromKey()) + .orElse(null); + dto.changeIssuedFrom(agenda); + } + if (dto.getUsedToKey() != null) { + Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getUsedToKey()) + .orElse(null); + dto.changeUsedTo(agenda); } - return ticketHistoryResDtos; + return dto; } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index 07d31cc8b..a05f53654 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -27,10 +27,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.MultiValueMap; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.admin.repo.agenda.AgendaAdminRepository; @@ -46,6 +46,7 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; import lombok.extern.slf4j.Slf4j; @@ -117,14 +118,17 @@ void findAgendaByAgendaKeySuccessAdmin(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result).hasSize( + assertThat(result.size()).isEqualTo( ((page - 1) * size) < agendas.size() ? Math.min(size, agendas.size() - (page - 1) * size) : 0); agendas.sort((a, b) -> b.getId().compareTo(a.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getAgendaId()).isEqualTo(agendas.get(i + (page - 1) * size).getId()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaId()).isEqualTo(agendas.get(i + (page - 1) * size).getId()); } } @@ -134,7 +138,6 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { // given int page = 1; int size = 10; - PageRequestDto pageRequestDto = new PageRequestDto(page, size); // when String response = mockMvc.perform(get("/agenda/admin/request/list") @@ -142,10 +145,13 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAdminResDto[] result = objectMapper.readValue(response, AgendaAdminResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result).isEmpty(); + assertThat(result.size()).isEqualTo(0); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java index 1e8f37511..b75cc3155 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -66,7 +66,7 @@ void findAgendaByAgendaKeySuccessAdmin() { when(agendaAdminRepository.findAll(pageable)).thenReturn(page); // when - List result = agendaAdminService.getAgendaRequestList(pageable); + Page result = agendaAdminService.getAgendaRequestList(pageable); // then verify(agendaAdminRepository, times(1)).findAll(pageable); @@ -82,7 +82,7 @@ void findAgendaByAgendaKeySuccessAdminWithNoContent() { when(agendaAdminRepository.findAll(pageable)).thenReturn(page); // when - List result = agendaAdminService.getAgendaRequestList(pageable); + Page result = agendaAdminService.getAgendaRequestList(pageable); // then verify(agendaAdminRepository, times(1)).findAll(pageable); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java index 67cad42dd..25f1049a8 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendaannouncement/controller/AgendaAnnouncementAdminControllerTest.java @@ -21,6 +21,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.admin.repo.agenda.AgendaAdminRepository; @@ -34,6 +35,7 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.fixture.agenda.AgendaAnnouncementFixture; import gg.utils.fixture.agenda.AgendaFixture; @@ -100,15 +102,17 @@ void getAgendaAnnouncementAdminSuccess(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAnnouncementResDto[] result = - objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue( + response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result).hasSize(((page - 1) * size) < announcements.size() + assertThat(result.size()).isEqualTo(((page - 1) * size) < announcements.size() ? Math.min(size, announcements.size() - (page - 1) * size) : 0); announcements.sort((a, b) -> b.getId().compareTo(a.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); } } @@ -127,11 +131,13 @@ void getAgendaAnnouncementAdminSuccessWithNoContent() throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAnnouncementResDto[] result = - objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue( + response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result).isEmpty(); + assertThat(result.size()).isEqualTo(0); } @Test diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index 34f87f7bc..ff6857651 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.admin.repo.agenda.AgendaAdminRepository; @@ -43,6 +44,7 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; @@ -121,15 +123,18 @@ void getAgendaTeamListAdminSuccess(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaTeamResDto[] result = objectMapper.readValue(response, AgendaTeamResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then assertThat(result).isNotNull(); - assertThat(result).hasSize(((page - 1) * size) < teams.size() + assertThat(result.size()).isEqualTo(((page - 1) * size) < teams.size() ? Math.min(size, teams.size() - (page - 1) * size) : 0); teams.sort((a, b) -> b.getId().compareTo(a.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getTeamKey()).isEqualTo(teams.get(i + (page - 1) * size).getTeamKey()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getTeamKey()).isEqualTo(teams.get(i + (page - 1) * size).getTeamKey()); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java index 75c6ac353..03c706703 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -68,7 +69,7 @@ void getAgendaTeamListAdminSuccess() { .thenReturn(new PageImpl<>(announcements)); // when - List result = agendaTeamAdminService.getAgendaTeamList(agenda.getAgendaKey(), pageable); + Page result = agendaTeamAdminService.getAgendaTeamList(agenda.getAgendaKey(), pageable); // then verify(agendaAdminRepository, times(1)).findByAgendaKey(any(UUID.class)); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 3d85cf0ef..8bbaafc00 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -33,6 +33,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; @@ -56,6 +57,7 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.AwsImageHandler; @@ -517,16 +519,20 @@ void getAgendaListHistorySuccess(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result.length).isEqualTo(size * page < totalCount ? size : totalCount % size); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getAgendaTitle()).isEqualTo(agendaHistory.get(size * (page - 1) + i).getTitle()); + assertThat(result.size()).isEqualTo(size * page < totalCount ? size : totalCount % size); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaTitle()) + .isEqualTo(agendaHistory.get(size * (page - 1) + i).getTitle()); if (i == 0) { continue; } - assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + assertThat(result.get(i).getAgendaStartTime()).isBefore(result.get(i - 1).getAgendaStartTime()); } } @@ -543,10 +549,12 @@ void getAgendaListHistoryWithNoContent() throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); - + PageResponseDto pageResponseDto = objectMapper.readValue( + response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result.length).isEqualTo(0); + assertThat(result.size()).isEqualTo(0); } @ParameterizedTest @@ -597,10 +605,13 @@ void getAgendaListHistoryWithExcessPage(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue( + response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result.length).isEqualTo(0); + assertThat(result.size()).isEqualTo(0); } @ParameterizedTest @@ -632,16 +643,19 @@ void getAgendaListHistoryWithoutSize() throws Exception { .header("Authorization", "Bearer " + accessToken) .param("page", String.valueOf(page))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue( + response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then - assertThat(result.length).isEqualTo(20); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getAgendaTitle()).isEqualTo(agendaHistory.get(i).getTitle()); + assertThat(result.size()).isEqualTo(20); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaTitle()).isEqualTo(agendaHistory.get(i).getTitle()); if (i == 0) { continue; } - assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + assertThat(result.get(i).getAgendaStartTime()).isBefore(result.get(i - 1).getAgendaStartTime()); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index a0bb027e8..498359926 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -197,7 +197,8 @@ void getAgendaListHistorySuccess() { .thenReturn(agendaPage); // when - List result = agendaService.findHistoryAgendaList(pageable); + Page res = agendaService.findHistoryAgendaList(pageable); + List result = res.getContent(); // then verify(agendaRepository, times(1)) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java index ed3c63080..be367f70f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; @@ -33,6 +34,7 @@ import gg.repo.agenda.AgendaAnnouncementRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageResponseDto; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -228,19 +230,22 @@ void getAgendaAnnouncementListSuccess(int page) throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then assertThat(result).hasSize(size * page < total ? size : total % size); announcements.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); - assertThat(result[i].getTitle()).isEqualTo(announcements.get(i + (page - 1) * size).getTitle()); - assertThat(result[i].getContent()).isEqualTo(announcements.get(i + (page - 1) * size).getContent()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getId()).isEqualTo(announcements.get(i + (page - 1) * size).getId()); + assertThat(result.get(i).getTitle()).isEqualTo(announcements.get(i + (page - 1) * size).getTitle()); + assertThat(result.get(i).getContent()).isEqualTo(announcements.get(i + (page - 1) * size).getContent()); if (i == 0) { continue; } - assertThat(result[i].getId()).isLessThan(result[i - 1].getId()); + assertThat(result.get(i).getId()).isLessThan(result.get(i - 1).getId()); } } @@ -260,8 +265,10 @@ void getAgendaAnnouncementListSuccessWhenNoEntity() throws Exception { .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AgendaAnnouncementResDto[] result = objectMapper.readValue(response, AgendaAnnouncementResDto[].class); - + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then assertThat(result).hasSize(0); } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java index 373ac6505..feb4b53f9 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementServiceTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; @@ -62,7 +63,7 @@ void getAgendaAnnouncementListSuccess() { Agenda agenda = mock(Agenda.class); Pageable pageable = mock(Pageable.class); when(agendaAnnouncementRepository.findListByAgenda(pageable, agenda)) - .thenReturn(List.of()); + .thenReturn(Page.empty()); // when agendaAnnouncementService.findAnnouncementListByAgenda(pageable, agenda); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index f7e62f5dc..e07cb4596 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; @@ -42,6 +43,7 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; @IntegrationTest @Transactional @@ -377,23 +379,26 @@ void getAttendedAgendaListSuccess(int page) throws Exception { .content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AttendedAgendaListResDto[] result = objectMapper.readValue(response, AttendedAgendaListResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then assertThat(result).hasSize(size * page < total ? size : total % size); attendedAgendas.sort((o1, o2) -> Long.compare(o2.getAgenda().getId(), o1.getAgenda().getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getAgendaId()).isEqualTo( + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaId()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getId().toString()); - assertThat(result[i].getAgendaTitle()).isEqualTo( + assertThat(result.get(i).getAgendaTitle()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getTitle()); - assertThat(result[i].getAgendaLocation()).isEqualTo( + assertThat(result.get(i).getAgendaLocation()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getLocation().toString()); - assertThat(result[i].getTeamKey()).isEqualTo( + assertThat(result.get(i).getTeamKey()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgendaTeam().getTeamKey()); - assertThat(result[i].getIsOfficial()).isEqualTo( + assertThat(result.get(i).getIsOfficial()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getIsOfficial()); - assertThat(result[i].getTeamName()).isEqualTo( + assertThat(result.get(i).getTeamName()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgendaTeam().getName()); } } @@ -415,7 +420,10 @@ void getAttendedAgendaListSuccessNoAgenda() throws Exception { .content(request)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - AttendedAgendaListResDto[] result = objectMapper.readValue(response, AttendedAgendaListResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); // then assertThat(result).isEmpty(); @@ -439,6 +447,3 @@ void getAttendedAgendaListBadRequest() throws Exception { } } } - - - diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 4b0e72679..0aad8599c 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.AgendaMockData; @@ -44,6 +45,7 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; import gg.utils.fixture.agenda.AgendaTeamProfileFixture; @@ -1119,13 +1121,17 @@ public void openTeamGetSuccess(int page) throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + // then - assertThat(result).hasSize(((page - 1) * 5) < teams.size() + assertThat(result.size()).isEqualTo(((page - 1) * 5) < teams.size() ? Math.min(5, teams.size() - (page - 1) * 5) : 0); teams.sort((a, b) -> b.getId().compareTo(a.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); } } @@ -1143,9 +1149,13 @@ public void openTeamGetSuccessNoTeam() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - OpenTeamResDto[] result = objectMapper.readValue(res, OpenTeamResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + // then - assertThat(result).isEmpty(); + assertThat(result.size()).isEqualTo(0); } @Test @@ -1195,13 +1205,17 @@ public void confirmTeamGetSuccess(int page) throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + // then - assertThat(result).hasSize(((page - 1) * 5) < teams.size() + assertThat(result.size()).isEqualTo(((page - 1) * 5) < teams.size() ? Math.min(5, teams.size() - (page - 1) * 5) : 0); teams.sort((a, b) -> b.getId().compareTo(a.getId())); - for (int i = 0; i < result.length; i++) { - assertThat(result[i].getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getTeamName()).isEqualTo(teams.get((page - 1) * 5 + i).getName()); } } @@ -1220,9 +1234,13 @@ public void confirmTeamGetSuccessNoTeam() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - ConfirmTeamResDto[] result = objectMapper.readValue(res, ConfirmTeamResDto[].class); + PageResponseDto pageResponseDto = objectMapper + .readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + // then - assertThat(result).isEmpty(); + assertThat(result.size()).isEqualTo(0); } @Test diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index 5f5e9e15e..f42d21372 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -5,6 +5,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -18,6 +20,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; @@ -30,6 +33,7 @@ import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.external.ApiUtil; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; @@ -215,9 +219,12 @@ void findTicketHistorySuccess(int page) throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + //then - assertThat(result).hasSize(((page - 1) * 5) < 23 + assertThat(result.size()).isEqualTo(((page - 1) * 5) < 23 ? Math.min(5, 23 - (page - 1) * 5) : 0); } @@ -234,11 +241,14 @@ void findTicketHistorySuccessToNotApprove() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + //then - assertThat(result).hasSize(1); - assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); - assertThat(result[0].getUsedTo()).isEqualTo("NotApproved"); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotApproved"); } @Test @@ -256,11 +266,14 @@ void findTicketHistorySuccessToUsed() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + //then - assertThat(result).hasSize(1); - assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); - assertThat(result[0].getUsedTo()).isEqualTo(seoulAgenda.getTitle()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo(seoulAgenda.getTitle()); } @Test @@ -279,11 +292,13 @@ void findTicketHistorySuccessToNotUsed() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); //then - assertThat(result).hasSize(1); - assertThat(result[0].getIssuedFrom()).isEqualTo("42Intra"); - assertThat(result[0].getUsedTo()).isEqualTo("NotUsed"); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotUsed"); } @Test @@ -301,11 +316,13 @@ void findTicketHistorySuccessToRefund() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); //then - assertThat(result).hasSize(1); - assertThat(result[0].getIssuedFrom()).isEqualTo(seoulAgenda.getTitle()); - assertThat(result[0].getUsedTo()).isEqualTo("NotUsed"); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo(seoulAgenda.getTitle()); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotUsed"); } @Test @@ -320,9 +337,11 @@ void findTicketHistorySuccessToEmptyTicket() throws Exception { .param("page", String.valueOf(req.getPage())) .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - TicketHistoryResDto[] result = objectMapper.readValue(res, TicketHistoryResDto[].class); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); //then - assertThat(result).isEmpty(); + assertThat(result.size()).isEqualTo(0); } @Test diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java index 26a3111fd..2ad1c5eed 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaAnnouncementRepository.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -14,13 +15,13 @@ public interface AgendaAnnouncementRepository extends JpaRepository findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(Agenda agenda); - List findAllByAgendaAndIsShowIsTrueOrderByIdDesc(Pageable pageable, Agenda agenda); + Page findAllByAgendaAndIsShowIsTrueOrderByIdDesc(Pageable pageable, Agenda agenda); default Optional findLatestByAgenda(Agenda agenda) { return findFirstByAgendaAndIsShowIsTrueOrderByIdDesc(agenda); } - default List findListByAgenda(Pageable pageable, Agenda agenda) { + default Page findListByAgenda(Pageable pageable, Agenda agenda) { return findAllByAgendaAndIsShowIsTrueOrderByIdDesc(pageable, agenda); } } diff --git a/gg-utils/src/main/java/gg/utils/dto/PageResponseDto.java b/gg-utils/src/main/java/gg/utils/dto/PageResponseDto.java new file mode 100644 index 000000000..c8395b339 --- /dev/null +++ b/gg-utils/src/main/java/gg/utils/dto/PageResponseDto.java @@ -0,0 +1,31 @@ +package gg.utils.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PageResponseDto { + + private Long totalSize; + + private List content; + + @Builder + public PageResponseDto(Long totalSize, List content) { + this.totalSize = totalSize; + this.content = content; + } + + public static PageResponseDto of(Long totalSize, List content) { + return PageResponseDto.builder() + .totalSize(totalSize) + .content(content) + .build(); + } +} From d24516db3410fd5e55d8c5802fea461c138e76db Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:32:06 +0900 Subject: [PATCH 079/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Agenda?= =?UTF-8?q?=20DateTimeFormat=20=EB=B3=80=EA=B2=BD=20#967=20(#968)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/request/AgendaAdminUpdateReqDto.java | 6 +++--- .../user/agenda/controller/request/AgendaCreateReqDto.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java index 692339c82..cbb94284c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/controller/request/AgendaAdminUpdateReqDto.java @@ -28,13 +28,13 @@ public class AgendaAdminUpdateReqDto { private AgendaStatus agendaStatus; - @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime agendaDeadLine; - @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime agendaStartTime; - @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime agendaEndTime; private Location agendaLocation; diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java index 9fb013196..44c7c81bb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/AgendaCreateReqDto.java @@ -14,8 +14,6 @@ import org.mapstruct.factory.Mappers; import org.springframework.format.annotation.DateTimeFormat; -import com.fasterxml.jackson.annotation.JsonFormat; - import gg.agenda.api.user.agenda.controller.request.validator.AgendaCapacityValid; import gg.agenda.api.user.agenda.controller.request.validator.AgendaScheduleValid; import gg.data.agenda.Agenda; From 316883ea4c576842e06863c32f852fbad2a9ce3c Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:19:15 +0900 Subject: [PATCH 080/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Agenda=20current?= =?UTF-8?q?=20Team=20count=EA=B0=80=20=EC=9D=8C=EC=88=98=EA=B0=80=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20#969=20(#970)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/validator/AgendaCapacityValidator.java | 4 ++-- .../user/agendateam/service/AgendaTeamService.java | 4 +--- .../user/ticket/controller/TicketController.java | 12 ++++++++---- .../controller/response/TicketCountResDto.java | 4 +++- .../api/user/ticket/service/TicketService.java | 13 ++++++------- .../src/test/java/gg/agenda/api/AgendaMockData.java | 2 +- .../user/agendateam/AgendaTeamControllerTest.java | 3 +++ gg-data/src/main/java/gg/data/agenda/Agenda.java | 6 ++++-- .../src/main/java/gg/data/agenda/AgendaTeam.java | 4 ++-- .../main/java/gg/repo/agenda/TicketRepository.java | 2 ++ .../java/gg/utils/AgendaTestDataUtils.java | 2 +- 11 files changed, 33 insertions(+), 23 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java index 1f330e629..923dbd7f9 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/request/validator/AgendaCapacityValidator.java @@ -23,7 +23,7 @@ public boolean isValid(AgendaCreateReqDto value, ConstraintValidatorContext cont } private boolean mustHaveValidTeam(AgendaCreateReqDto value) { - return value.getAgendaMinTeam() < value.getAgendaMaxTeam() - && value.getAgendaMinPeople() < value.getAgendaMaxPeople(); + return value.getAgendaMinTeam() <= value.getAgendaMaxTeam() + && value.getAgendaMinPeople() <= value.getAgendaMaxPeople(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 18a92cc01..c72fd7db1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -18,9 +18,7 @@ import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamUpdateReqDto; -import gg.agenda.api.user.agendateam.controller.response.ConfirmTeamResDto; import gg.agenda.api.user.agendateam.controller.response.MyTeamSimpleResDto; -import gg.agenda.api.user.agendateam.controller.response.OpenTeamResDto; import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.ticket.service.TicketService; @@ -201,7 +199,7 @@ public void leaveTeamAll(AgendaTeam agendaTeam) { List agendaTeamProfiles = agendaTeamProfileRepository .findByAgendaTeamAndIsExistTrue(agendaTeam); agendaTeamProfiles.forEach(agendaTeamProfile -> leaveTeam(agendaTeam, agendaTeamProfile)); - agendaTeam.cancelTeam(); + agendaTeam.cancelTeam(agendaTeam.getStatus()); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index d6680bc9f..4598c1639 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import gg.agenda.api.user.agenda.service.AgendaService; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; @@ -42,7 +41,6 @@ public class TicketController { private final CookieUtil cookieUtil; private final TicketService ticketService; - private final AgendaService agendaService; /** * 티켓 설정 추가 @@ -56,12 +54,18 @@ public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login User /** * 티켓 수 조회 + * boolean setupTicket = tickets.size() > approvedCount; setupTicket이 있는지 확인하는 부분 * @param user 사용자 정보 */ @GetMapping public ResponseEntity ticketCountFind(@Parameter(hidden = true) @Login UserDto user) { - int ticketCount = ticketService.findTicketCount(user); - return ResponseEntity.ok().body(new TicketCountResDto(ticketCount)); + List tickets = ticketService.findTicketList(user); + long approvedCount = tickets.stream() + .filter(Ticket::getIsApproved) + .count(); + boolean setupTicket = tickets.size() > approvedCount; + + return ResponseEntity.ok(new TicketCountResDto(tickets.size(), setupTicket)); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java index 876eca768..60d153d73 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java @@ -7,8 +7,10 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class TicketCountResDto { private int ticketCount; + private boolean setupTicket; - public TicketCountResDto(int ticketCount) { + public TicketCountResDto(int ticketCount, boolean setupTicket) { this.ticketCount = ticketCount; + this.setupTicket = setupTicket; } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 6da5a8966..4efec89ab 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -79,11 +79,10 @@ public void addTicketSetup(UserDto user) { * @return 티켓 수 */ @Transactional(readOnly = true) - public int findTicketCount(UserDto user) { + public List findTicketList(UserDto user) { AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - List tickets = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(profile.getId()); - return tickets.size(); + return ticketRepository.findByAgendaProfileAndIsUsedFalse(profile); } /** @@ -108,7 +107,7 @@ public void modifyTicketApprove(UserDto user, Authentication authentication) { * @param profile AgendaProfile * @return 티켓 설정 */ - private Ticket getSetUpTicket(AgendaProfile profile) { + public Ticket getSetUpTicket(AgendaProfile profile) { return ticketRepository.findByAgendaProfileAndIsApprovedFalse(profile) .orElseThrow(() -> new NotExistException(NOT_SETUP_TICKET)); } @@ -176,9 +175,9 @@ public Page findTicketsByUserId(Long userId, Pageable pageable) { return ticketRepository.findByAgendaProfileId(profile.getId(), pageable); } - /** - * 티켓 이력 조회 - */ + /** + * 티켓 이력 조회 + */ @Transactional(readOnly = true) public TicketHistoryResDto convertAgendaKeyToTitleWhereIssuedFromAndUsedTo(Ticket ticket) { TicketHistoryResDto dto = new TicketHistoryResDto(ticket); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java index 22005c090..3072f4402 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/AgendaMockData.java @@ -410,7 +410,7 @@ public Agenda createAgenda(Location location) { .endTime(LocalDateTime.now().plusDays(3)) .minTeam(2) .maxTeam(20) - .currentTeam(0) + .currentTeam(1) .minPeople(1) .maxPeople(20) .posterUri("posterUri") diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 0aad8599c..8f972d817 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -862,6 +862,7 @@ public void leaveTeamMateSuccess() throws Exception { AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); assert updatedTeam != null; assertThat(updatedTeam.getMateCount()).isEqualTo(1); + assertThat(agenda.getCurrentTeam()).isEqualTo(1); AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); @@ -892,6 +893,7 @@ public void leaveTeamLeaderSuccess() throws Exception { AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); assert updatedTeam != null; assertThat(updatedTeam.getMateCount()).isEqualTo(0); + assertThat(agenda.getCurrentTeam()).isEqualTo(1); AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); @@ -930,6 +932,7 @@ public void leaveTeamLeaderTeamStatusConfirmSuccess() throws Exception { AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); assert updatedTeam != null; assertThat(updatedTeam.getMateCount()).isEqualTo(0); + assertThat(agenda.getCurrentTeam()).isEqualTo(0); AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); assert updatedAtp != null; assertThat(updatedAtp.getIsExist()).isFalse(); diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 71ca03055..8eaeb2158 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -249,10 +249,12 @@ public void updateTeam(Location location, LocalDateTime now) { mustBeforeDeadline(now); } - public void leaveTeam(LocalDateTime now) { + public void leaveTeam(LocalDateTime now, AgendaTeamStatus status) { mustStatusOpen(); mustBeforeDeadline(now); - this.currentTeam--; + if (status == AgendaTeamStatus.CONFIRM) { + this.currentTeam--; + } } private void mustBeWithinLocation(Location location) { diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index b561074fb..5cc557360 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -108,10 +108,10 @@ public void confirm() { this.status = CONFIRM; } - public void cancelTeam() { + public void cancelTeam(AgendaTeamStatus status) { + this.agenda.leaveTeam(LocalDateTime.now(), status); this.status = CANCEL; this.mateCount = 0; - this.agenda.leaveTeam(LocalDateTime.now()); } public void leaveTeamMate() { diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 46f851650..0c35fffec 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -21,4 +21,6 @@ Optional findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByC Optional findByAgendaProfileId(Long agendaProfileId); Page findByAgendaProfileId(Long agendaProfileId, Pageable pageable); + + List findByAgendaProfileAndIsUsedFalse(AgendaProfile agendaProfile); } diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java index 505e4de61..3c1c35422 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -72,7 +72,7 @@ public Agenda createAgendaTeamProfiles(User user, AgendaStatus status) { List mates = agendaProfileFixture.createAgendaProfileList(3); AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, leader, AgendaTeamStatus.OPEN); agendaTeamProfileFixture.createAgendaTeamProfileList(agenda, team, mates); - team.cancelTeam(); + team.cancelTeam(team.getStatus()); } return agenda; } From eb67a56a6383dd77b55fb2869db9e8b0b6eda841 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:14:42 +0900 Subject: [PATCH 081/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=20=EC=88=98=EC=A0=95=20API=20#946=20(#975)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/AgendaTeamDetailResDto.java | 6 +- .../controller/response/AgendaTeamResDto.java | 6 +- .../controller/TicketAdminController.java | 19 +++++ .../request/TicketChangeAdminReqDto.java | 36 ++++++++ .../ticket/service/TicketAdminService.java | 30 ++++++- .../controller/AgendaProfileController.java | 4 +- .../response/AttendedAgendaListResDto.java | 2 + .../CurrentAttendAgendaListResDto.java | 2 + .../ticket/TicketAdminControllerTest.java | 83 +++++++++++++++++++ .../AgendaProfileControllerTest.java | 3 + .../src/main/java/gg/data/agenda/Ticket.java | 15 ++++ .../java/gg/utils/exception/ErrorCode.java | 1 + 12 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketChangeAdminReqDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java index 6862b87d0..784595040 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamDetailResDto.java @@ -21,6 +21,8 @@ public class AgendaTeamDetailResDto { private String teamName; + private String teamContent; + private String teamLeaderIntraId; private String teamStatus; @@ -35,8 +37,9 @@ public class AgendaTeamDetailResDto { @Builder public AgendaTeamDetailResDto(String teamName, String teamLeaderIntraId, String teamStatus, String teamAward, - int teamAwardPriority, boolean teamIsPrivate, List teamMates) { + int teamAwardPriority, boolean teamIsPrivate, List teamMates, String teamContent) { this.teamName = teamName; + this.teamContent = teamContent; this.teamLeaderIntraId = teamLeaderIntraId; this.teamStatus = teamStatus; this.teamAward = teamAward; @@ -51,6 +54,7 @@ public interface MapStruct { AgendaTeamDetailResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamDetailResDto.MapStruct.class); @Mapping(target = "teamName", source = "team.name") + @Mapping(target = "teamContent", source = "team.content") @Mapping(target = "teamLeaderIntraId", source = "team.leaderIntraId") @Mapping(target = "teamStatus", source = "team.status") @Mapping(target = "teamAward", source = "team.award") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java index b756446d4..939864087 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java @@ -18,6 +18,8 @@ public class AgendaTeamResDto { private String teamName; + private String teamContent; + private String teamStatus; private boolean teamIsPrivate; @@ -34,8 +36,9 @@ public class AgendaTeamResDto { @Builder public AgendaTeamResDto(String teamName, String teamStatus, boolean teamIsPrivate, String teamLeaderIntraId, - int teamMateCount, UUID teamKey, String teamAward, Integer teamAwardPriority) { + int teamMateCount, UUID teamKey, String teamAward, Integer teamAwardPriority, String teamContent) { this.teamName = teamName; + this.teamContent = teamContent; this.teamStatus = teamStatus; this.teamIsPrivate = teamIsPrivate; this.teamLeaderIntraId = teamLeaderIntraId; @@ -51,6 +54,7 @@ public interface MapStruct { AgendaTeamResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaTeamResDto.MapStruct.class); @Mapping(target = "teamName", source = "name") + @Mapping(target = "teamContent", source = "content") @Mapping(target = "teamStatus", source = "status") @Mapping(target = "teamIsPrivate", source = "isPrivate") @Mapping(target = "teamLeaderIntraId", source = "leaderIntraId") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java index 6d9cf6f44..6ee0cda2b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java @@ -1,7 +1,10 @@ package gg.agenda.api.admin.ticket.controller; +import javax.validation.Valid; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -9,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController; import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.agenda.api.admin.ticket.controller.request.TicketChangeAdminReqDto; import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; import gg.agenda.api.admin.ticket.service.TicketAdminService; import lombok.RequiredArgsConstructor; @@ -30,4 +34,19 @@ public ResponseEntity ticketAdminAdd(@RequestParam String TicketAddAdminResDto ticketAddAdminResDto = new TicketAddAdminResDto(ticketId); return ResponseEntity.status(HttpStatus.CREATED).body(ticketAddAdminResDto); } + + /** + * 티켓 변경(관리자) API + * + * @param ticketId 수정할 ticketId + * @param ticketChangeAdminReqDto 변경할 프로필 정보 + * @return HTTP 상태 코드와 빈 응답 + */ + @PatchMapping + public ResponseEntity agendaProfileModify( + @RequestParam Long ticketId, + @RequestBody @Valid TicketChangeAdminReqDto ticketChangeAdminReqDto) { + ticketAdminService.modifyTicket(ticketId, ticketChangeAdminReqDto); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketChangeAdminReqDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketChangeAdminReqDto.java new file mode 100644 index 000000000..e809d7b51 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/request/TicketChangeAdminReqDto.java @@ -0,0 +1,36 @@ +package gg.agenda.api.admin.ticket.controller.request; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.springframework.format.annotation.DateTimeFormat; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TicketChangeAdminReqDto { + private UUID issuedFromKey; + private UUID usedToKey; + private Boolean isApproved; + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + private LocalDateTime approvedAt; + private Boolean isUsed; + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + private LocalDateTime usedAt; + + @Builder + public TicketChangeAdminReqDto(UUID issuedFromKey, UUID usedToKey, Boolean isApproved, + LocalDateTime approvedAt, + Boolean isUsed, LocalDateTime usedAt) { + this.issuedFromKey = issuedFromKey; + this.usedToKey = usedToKey; + this.isApproved = isApproved; + this.approvedAt = approvedAt; + this.isUsed = isUsed; + this.usedAt = usedAt; + + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java index bbe1dfc96..5502443b0 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminService.java @@ -12,6 +12,7 @@ import gg.admin.repo.agenda.AgendaProfileAdminRepository; import gg.admin.repo.agenda.TicketAdminRepository; import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.agenda.api.admin.ticket.controller.request.TicketChangeAdminReqDto; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.utils.exception.custom.NotExistException; @@ -22,7 +23,7 @@ public class TicketAdminService { private final TicketAdminRepository ticketAdminRepository; - private final AgendaProfileAdminRepository agendaProfileRepository; + private final AgendaProfileAdminRepository agendaProfileAdminRepository; private final AgendaAdminRepository agendaAdminRepository; /** @@ -31,7 +32,7 @@ public class TicketAdminService { */ @Transactional public Long addTicket(String intraId, TicketAddAdminReqDto ticketAddAdminReqDto) { - AgendaProfile profile = agendaProfileRepository.findByIntraId(intraId) + AgendaProfile profile = agendaProfileAdminRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); UUID issuedFromKey = ticketAddAdminReqDto.getIssuedFromKey(); @@ -50,4 +51,29 @@ public Long addTicket(String intraId, TicketAddAdminReqDto ticketAddAdminReqDto) private boolean isRefundedTicket(UUID issuedFromKey) { return Objects.nonNull(issuedFromKey); } + + /** + * AgendaProfile 변경 메서드 + * @param ticketId 로그인한 유저의 id + * @param reqDto 변경할 프로필 정보 + */ + @Transactional + public void modifyTicket(Long ticketId, TicketChangeAdminReqDto reqDto) { + Ticket ticket = ticketAdminRepository.findById(ticketId) + .orElseThrow(() -> new NotExistException(TICKET_NOT_FOUND)); + + UUID issuedFromKey = reqDto.getIssuedFromKey(); + if (Objects.nonNull(issuedFromKey) && !agendaAdminRepository.existsByAgendaKey(issuedFromKey)) { + throw new NotExistException(AGENDA_NOT_FOUND); + } + + UUID usedToKey = reqDto.getUsedToKey(); + if (Objects.nonNull(usedToKey) && !agendaAdminRepository.existsByAgendaKey(usedToKey)) { + throw new NotExistException(AGENDA_NOT_FOUND); + } + + ticket.updateTicketAdmin(reqDto.getIssuedFromKey(), reqDto.getUsedToKey(), + reqDto.getIsApproved(), reqDto.getApprovedAt(), reqDto.getIsUsed(), reqDto.getUsedAt()); + ticketAdminRepository.save(ticket); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 3ced617e5..697fad220 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -25,7 +25,6 @@ import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; -import gg.agenda.api.user.agendateam.controller.response.TeamMateDto; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.AgendaTeamProfile; @@ -69,9 +68,8 @@ public ResponseEntity agendaProfileModify(@Login @Parameter(hidden = true) } /** - * AgendaProfile 상세 조회 API + * AgendaProfile admin 여부 조회 API * @param user 로그인한 사용자 정보 - * @return AgendaProfileDetailsResDto 객체와 HTTP 상태 코드를 포함한 ResponseEntity */ @GetMapping("/info") public ResponseEntity myAgendaProfileInfoDetails( diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java index bce775205..138bdd549 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AttendedAgendaListResDto.java @@ -14,6 +14,7 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class AttendedAgendaListResDto { private String agendaId; + private UUID agendaKey; private String agendaTitle; private LocalDateTime agendaStartTime; private LocalDateTime agendaEndTime; @@ -28,6 +29,7 @@ public class AttendedAgendaListResDto { public AttendedAgendaListResDto(AgendaTeamProfile agendaTeamProfile, List agendaTeamProfileList) { this.agendaId = agendaTeamProfile.getAgenda().getId().toString(); + this.agendaKey = agendaTeamProfile.getAgenda().getAgendaKey(); this.agendaTitle = agendaTeamProfile.getAgenda().getTitle(); this.agendaStartTime = agendaTeamProfile.getAgenda().getStartTime(); this.agendaEndTime = agendaTeamProfile.getAgenda().getEndTime(); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java index 1670d1be4..6d20c6651 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/CurrentAttendAgendaListResDto.java @@ -11,6 +11,7 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class CurrentAttendAgendaListResDto { private String agendaId; + private UUID agendaKey; private String agendaTitle; private String agendaLocation; private UUID teamKey; @@ -21,6 +22,7 @@ public class CurrentAttendAgendaListResDto { public CurrentAttendAgendaListResDto(AgendaTeamProfile agendaTeamProfile) { this.agendaId = agendaTeamProfile.getAgenda().getId().toString(); + this.agendaKey = agendaTeamProfile.getAgenda().getAgendaKey(); this.agendaTitle = agendaTeamProfile.getAgenda().getTitle(); this.agendaLocation = agendaTeamProfile.getAgenda().getLocation().toString(); this.teamKey = agendaTeamProfile.getAgendaTeam().getTeamKey(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java index a92830284..60a35189b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java @@ -4,6 +4,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.time.LocalDateTime; import java.util.UUID; import javax.transaction.Transactional; @@ -21,6 +22,7 @@ import gg.admin.repo.agenda.TicketAdminRepository; import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; +import gg.agenda.api.admin.ticket.controller.request.TicketChangeAdminReqDto; import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -33,6 +35,7 @@ import gg.utils.annotation.IntegrationTest; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; +import gg.utils.fixture.agenda.TicketFixture; @IntegrationTest @Transactional @@ -49,6 +52,8 @@ public class TicketAdminControllerTest { @Autowired private AgendaFixture agendaFixture; @Autowired + private TicketFixture ticketFixture; + @Autowired private TicketAdminRepository ticketAdminRepository; User user; String accessToken; @@ -137,4 +142,82 @@ void createTicketWithAgendaNotExit() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("티켓 정보 변경") + class UpdateTicket { + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewAdminUser(RoleType.ADMIN); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + agendaProfile = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); + } + + @Test + @DisplayName("유효한 정보로 티켓 정보를 변경합니다.") + void updateTicketWithValidData() throws Exception { + // Given + User agendaCreateUser = testDataUtils.createNewUser(); + Ticket ticket = ticketFixture.createNotApporveTicket(agendaProfile); + Agenda usedAgenda = agendaFixture.createAgenda(agendaCreateUser.getIntraId(), AgendaStatus.OPEN); + Agenda refundedAgenda = agendaFixture.createAgenda(agendaCreateUser.getIntraId(), AgendaStatus.OPEN); + String content = objectMapper.writeValueAsString( + new TicketChangeAdminReqDto(refundedAgenda.getAgendaKey(), usedAgenda.getAgendaKey(), Boolean.TRUE, + LocalDateTime.now(), Boolean.TRUE, LocalDateTime.now())); + + // When + mockMvc.perform( + patch("/agenda/admin/ticket") + .param("ticketId", String.valueOf(ticket.getId())) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNoContent()); + + // Then + Ticket updatedTicket = ticketAdminRepository.findById(ticket.getId()).orElseThrow(); + assertThat(updatedTicket.getIssuedFrom()).isEqualTo(refundedAgenda.getAgendaKey()); + assertThat(updatedTicket.getUsedTo()).isEqualTo(usedAgenda.getAgendaKey()); + assertThat(updatedTicket.getIsApproved()).isTrue(); + assertThat(updatedTicket.getIsUsed()).isTrue(); + } + + @Test + @DisplayName("존재하지않는 대회의 issuedFromKey 넣어 404 반환") + void updateTicketWithInvalidIssuedFromKey() throws Exception { + // Given + Ticket ticket = ticketFixture.createNotApporveTicket(agendaProfile); + String content = objectMapper.writeValueAsString( + new TicketChangeAdminReqDto(UUID.randomUUID(), null, Boolean.TRUE, + LocalDateTime.now(), Boolean.FALSE, LocalDateTime.now())); + + // When & Then + mockMvc.perform( + patch("/agenda/admin/ticket") + .param("ticketId", String.valueOf(ticket.getId())) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("존재하지않는 대회의 usedToKey 넣어 404 반환") + void updateTicketWithInvalidUsedToKey() throws Exception { + // Given + Ticket ticket = ticketFixture.createNotApporveTicket(agendaProfile); + String content = objectMapper.writeValueAsString( + new TicketChangeAdminReqDto(null, UUID.randomUUID(), Boolean.TRUE, + LocalDateTime.now(), Boolean.TRUE, LocalDateTime.now())); + + // When & Then + mockMvc.perform( + patch("/agenda/admin/ticket") + .param("ticketId", String.valueOf(ticket.getId())) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index e07cb4596..8e80d6bd0 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -287,6 +287,7 @@ public void getCurrentAttendAgendaListSuccess() throws Exception { assertThat(result.length).isEqualTo(agendaTeamList.size()); for (int i = 0; i < result.length; i++) { assertThat(result[i].getAgendaId()).isEqualTo(agendaTeamList.get(i).getAgenda().getId().toString()); + assertThat(result[i].getAgendaKey()).isEqualTo(agendaTeamList.get(i).getAgenda().getAgendaKey()); assertThat(result[i].getAgendaTitle()).isEqualTo(agendaTeamList.get(i).getAgenda().getTitle()); assertThat(result[i].getAgendaLocation()).isEqualTo( agendaTeamList.get(i).getAgenda().getLocation().toString()); @@ -390,6 +391,8 @@ void getAttendedAgendaListSuccess(int page) throws Exception { for (int i = 0; i < result.size(); i++) { assertThat(result.get(i).getAgendaId()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getId().toString()); + assertThat(result.get(i).getAgendaKey()).isEqualTo( + attendedAgendas.get(i + (page - 1) * size).getAgenda().getAgendaKey()); assertThat(result.get(i).getAgendaTitle()).isEqualTo( attendedAgendas.get(i + (page - 1) * size).getAgenda().getTitle()); assertThat(result.get(i).getAgendaLocation()).isEqualTo( diff --git a/gg-data/src/main/java/gg/data/agenda/Ticket.java b/gg-data/src/main/java/gg/data/agenda/Ticket.java index 54520e976..7249c0ea2 100644 --- a/gg-data/src/main/java/gg/data/agenda/Ticket.java +++ b/gg-data/src/main/java/gg/data/agenda/Ticket.java @@ -1,6 +1,7 @@ package gg.data.agenda; import java.time.LocalDateTime; +import java.util.Objects; import java.util.UUID; import javax.persistence.Column; @@ -121,4 +122,18 @@ public void changeIsApproved() { this.isApproved = true; this.approvedAt = LocalDateTime.now(); } + + public void updateTicketAdmin(UUID issuedFrom, UUID usedTo, Boolean isApproved, LocalDateTime approvedAt, + Boolean isUsed, LocalDateTime usedAt) { + this.issuedFrom = issuedFrom; + this.usedTo = usedTo; + this.approvedAt = approvedAt; + this.usedAt = usedAt; + if (Objects.nonNull(isUsed)) { + this.isUsed = isUsed; + } + if (Objects.nonNull(isApproved)) { + this.isApproved = isApproved; + } + } } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 9c46586f2..59374b432 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -219,6 +219,7 @@ public enum ErrorCode { NOT_TEAM_MATE(403, "AG", "팀원이 아닙니다."), TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), + TICKET_NOT_FOUND(404, "AG", "해당 티켓이 존재하지 않습니다."), AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), ALREADY_TICKET_SETUP(409, "AG", "이미 티켓 신청이 되어있습니다."), From d62dce7ce01849d49930ce9fec9a7c0c715cccbe Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:41:15 +0900 Subject: [PATCH 082/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EB=82=B4?= =?UTF-8?q?=EA=B0=80=20=EA=B0=9C=EC=B5=9C=ED=95=9C=20=EB=8C=80=ED=9A=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20#952=20(#973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/response/AgendaResDto.java | 8 +- .../controller/AgendaHostController.java | 70 +++++++ .../response/HostedAgendaResDto.java | 76 +++++++ .../service/AgendaProfileFindService.java | 17 +- .../AgendaHostControllerTest.java | 190 ++++++++++++++++++ .../java/gg/repo/agenda/AgendaRepository.java | 5 + .../java/gg/utils/AgendaTestDataUtils.java | 14 ++ 7 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/HostedAgendaResDto.java create mode 100644 gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java index 5a1987498..ba54e4475 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/response/AgendaResDto.java @@ -21,7 +21,7 @@ public class AgendaResDto { private String agendaTitle; - private String agendaContents; + private String agendaContent; private LocalDateTime agendaDeadLine; @@ -56,13 +56,13 @@ public class AgendaResDto { private String announcementTitle; @Builder - public AgendaResDto(String agendaTitle, String agendaContents, LocalDateTime agendaDeadLine, + public AgendaResDto(String agendaTitle, String agendaContent, LocalDateTime agendaDeadLine, LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaMinTeam, int agendaMaxTeam, int agendaCurrentTeam, int agendaMinPeople, int agendaMaxPeople, String agendaPosterUrl, String agendaHost, Location agendaLocation, AgendaStatus agendaStatus, LocalDateTime createdAt, String announcementTitle, Boolean isOfficial, Boolean isRanking) { this.agendaTitle = agendaTitle; - this.agendaContents = agendaContents; + this.agendaContent = agendaContent; this.agendaDeadLine = agendaDeadLine; this.agendaStartTime = agendaStartTime; this.agendaEndTime = agendaEndTime; @@ -87,7 +87,7 @@ public interface MapStruct { AgendaResDto.MapStruct INSTANCE = Mappers.getMapper(AgendaResDto.MapStruct.class); @Mapping(target = "agendaTitle", source = "agenda.title") - @Mapping(target = "agendaContents", source = "agenda.content") + @Mapping(target = "agendaContent", source = "agenda.content") @Mapping(target = "agendaDeadLine", source = "agenda.deadline") @Mapping(target = "agendaStartTime", source = "agenda.startTime") @Mapping(target = "agendaEndTime", source = "agenda.endTime") diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java new file mode 100644 index 000000000..a0b873cfb --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java @@ -0,0 +1,70 @@ +package gg.agenda.api.user.agendaprofile.controller; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gg.agenda.api.user.agendaprofile.controller.response.HostedAgendaResDto; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; +import gg.auth.UserDto; +import gg.auth.argumentresolver.Login; +import gg.data.agenda.Agenda; +import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/agenda/host") +public class AgendaHostController { + + private final AgendaProfileFindService agendaProfileFindService; + + @GetMapping("/history/list") + public ResponseEntity> hostedAgendaList( + @Login @Parameter(hidden = true) UserDto user, + @ModelAttribute @Valid PageRequestDto pageRequestDto) { + int page = pageRequestDto.getPage(); + int size = pageRequestDto.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + + Page hostedAgendas = agendaProfileFindService.findHostedAgenda(user.getIntraId(), pageable); + + List agendaResDtos = hostedAgendas.stream() + .map(HostedAgendaResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + PageResponseDto pageResponseDto = PageResponseDto.of( + hostedAgendas.getTotalElements(), agendaResDtos); + return ResponseEntity.ok(pageResponseDto); + } + + @GetMapping("/current/list") + public ResponseEntity> hostingAgendaList( + @Login @Parameter(hidden = true) UserDto user, + @ModelAttribute @Valid PageRequestDto pageRequestDto) { + int page = pageRequestDto.getPage(); + int size = pageRequestDto.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + + Page hostedAgendas = agendaProfileFindService.findHostingAgenda(user.getIntraId(), pageable); + + List agendaResDtos = hostedAgendas.stream() + .map(HostedAgendaResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + PageResponseDto pageResponseDto = PageResponseDto.of( + hostedAgendas.getTotalElements(), agendaResDtos); + return ResponseEntity.ok(pageResponseDto); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/HostedAgendaResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/HostedAgendaResDto.java new file mode 100644 index 000000000..3ac8534b6 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/HostedAgendaResDto.java @@ -0,0 +1,76 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HostedAgendaResDto { + private String agendaKey; + private String agendaTitle; + private LocalDateTime agendaDeadLine; + private LocalDateTime agendaStartTime; + private LocalDateTime agendaEndTime; + private int agendaCurrentTeam; + private int agendaMaxTeam; + private int agendaMinTeam; + private int agendaMinPeople; + private int agendaMaxPeople; + private String agendaLocation; + private Boolean isRanking; + private Boolean isOfficial; + private AgendaStatus agendaStatus; + + @Builder + public HostedAgendaResDto(String agendaKey, String agendaTitle, LocalDateTime agendaDeadLine, + LocalDateTime agendaStartTime, LocalDateTime agendaEndTime, int agendaCurrentTeam, int agendaMaxTeam, + int agendaMinTeam, int agendaMinPeople, int agendaMaxPeople, String agendaLocation, Boolean isRanking, + Boolean isOfficial, AgendaStatus agendaStatus) { + this.agendaKey = agendaKey; + this.agendaTitle = agendaTitle; + this.agendaDeadLine = agendaDeadLine; + this.agendaStartTime = agendaStartTime; + this.agendaEndTime = agendaEndTime; + this.agendaCurrentTeam = agendaCurrentTeam; + this.agendaMaxTeam = agendaMaxTeam; + this.agendaMinTeam = agendaMinTeam; + this.agendaMinPeople = agendaMinPeople; + this.agendaMaxPeople = agendaMaxPeople; + this.agendaLocation = agendaLocation; + this.isRanking = isRanking; + this.isOfficial = isOfficial; + this.agendaStatus = agendaStatus; + } + + @Mapper + public interface MapStruct { + HostedAgendaResDto.MapStruct INSTANCE = Mappers.getMapper(HostedAgendaResDto.MapStruct.class); + + @Mapping(target = "agendaKey", source = "agendaKey") + @Mapping(target = "agendaTitle", source = "title") + @Mapping(target = "agendaDeadLine", source = "deadline") + @Mapping(target = "agendaStartTime", source = "startTime") + @Mapping(target = "agendaEndTime", source = "endTime") + @Mapping(target = "agendaCurrentTeam", source = "currentTeam") + @Mapping(target = "agendaMaxTeam", source = "maxTeam") + @Mapping(target = "agendaMinTeam", source = "minTeam") + @Mapping(target = "agendaMinPeople", source = "minPeople") + @Mapping(target = "agendaMaxPeople", source = "maxPeople") + @Mapping(target = "agendaLocation", source = "location") + @Mapping(target = "isRanking", source = "isRanking") + @Mapping(target = "isOfficial", source = "isOfficial") + @Mapping(target = "agendaStatus", source = "status") + HostedAgendaResDto toDto(Agenda agenda); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index d87f55c8d..67c4416c4 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -13,11 +13,13 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; import gg.repo.agenda.AgendaProfileRepository; +import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.repo.agenda.TicketRepository; @@ -29,11 +31,10 @@ @RequiredArgsConstructor public class AgendaProfileFindService { - private final UserRepository userRepository; private final AgendaProfileRepository agendaProfileRepository; private final TicketRepository ticketRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; - private final AgendaTeamRepository agendaTeamRepository; + private final AgendaRepository agendaRepository; /** * AgendaProfile 상세 정보를 조회하는 메서드 @@ -85,4 +86,16 @@ public Page findAttendedAgenda(String intraId, Pageable pagea public List findTeamMatesFromAgendaTeam(AgendaTeam agendaTeam) { return agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue(agendaTeam); } + + @Transactional(readOnly = true) + public Page findHostedAgenda(String intraId, Pageable pageable) { + return agendaRepository.findAllByHostIntraIdAndStatus( + intraId, AgendaStatus.FINISH, AgendaStatus.CANCEL, pageable); + } + + @Transactional(readOnly = true) + public Page findHostingAgenda(String intraId, Pageable pageable) { + return agendaRepository.findAllByHostIntraIdAndStatus( + intraId, AgendaStatus.OPEN, AgendaStatus.CONFIRM, pageable); + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java new file mode 100644 index 000000000..d2499409f --- /dev/null +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java @@ -0,0 +1,190 @@ +package gg.agenda.api.user.agendaprofile; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.transaction.Transactional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import gg.agenda.api.user.agendaprofile.controller.response.HostedAgendaResDto; +import gg.data.agenda.Agenda; +import gg.data.agenda.type.AgendaStatus; +import gg.data.user.User; +import gg.utils.AgendaTestDataUtils; +import gg.utils.TestDataUtils; +import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageResponseDto; + +@IntegrationTest +@Transactional +@AutoConfigureMockMvc +public class AgendaHostControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private TestDataUtils testDataUtils; + + @Autowired + private AgendaTestDataUtils agendaTestDataUtils; + + User user; + + String accessToken; + + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Nested + @DisplayName("내가 주최했던 Agenda 목록 조회") + class HostedAgendaList { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("내가 주최했던 Agenda 목록 조회 성공") + void hostedAgendaListSuccess(int page) throws Exception { + // given + int size = 10; + int eachCount = 10; + List agendas = agendaTestDataUtils.createAgendasWithAllStatus(user, eachCount).stream() + .filter(agenda -> + agenda.getStatus() == AgendaStatus.FINISH || agenda.getStatus() == AgendaStatus.CANCEL) + .sorted((a1, a2) -> a2.getId().compareTo(a1.getId())) + .collect(Collectors.toList()); + + // when + String response = mockMvc.perform(get("/agenda/host/history/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // then + assertThat(pageResponseDto.getTotalSize()).isEqualTo(agendas.size()); + assertThat(result.size()).isEqualTo(size * page <= agendas.size() ? size : agendas.size() % size); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaTitle()) + .isEqualTo(agendas.get(size * (page - 1) + i).getTitle()); + assertThat(result.get(i).getAgendaStatus()).isNotEqualTo(AgendaStatus.OPEN); + assertThat(result.get(i).getAgendaStatus()).isNotEqualTo(AgendaStatus.CONFIRM); + } + } + + @Test + @DisplayName("내가 주최했던 Agenda 목록 조회 성공 - 빈 리스트인 경우") + void hostedAgendaListSuccessWithEmptyAgenda() throws Exception { + // given + + // when + String response = mockMvc.perform(get("/agenda/host/history/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", "1") + .param("size", "10")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // then + assertThat(pageResponseDto.getTotalSize()).isEqualTo(0); + assertThat(result.size()).isEqualTo(0); + } + } + + @Nested + @DisplayName("내가 주최하고 있는 Agenda 목록 조회") + class HostingAgendaList { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("내가 주최하고 있는 Agenda 목록 조회 성공") + void hostingAgendaListSuccess(int page) throws Exception { + // given + int size = 10; + int eachCount = 10; + List agendas = agendaTestDataUtils.createAgendasWithAllStatus(user, eachCount).stream() + .filter(agenda -> + agenda.getStatus() == AgendaStatus.OPEN || agenda.getStatus() == AgendaStatus.CONFIRM) + .sorted((a1, a2) -> a2.getId().compareTo(a1.getId())) + .collect(Collectors.toList()); + + // when + String response = mockMvc.perform(get("/agenda/host/current/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // then + assertThat(pageResponseDto.getTotalSize()).isEqualTo(agendas.size()); + assertThat(result.size()).isEqualTo(size * page <= agendas.size() ? size : agendas.size() % size); + for (int i = 0; i < result.size(); i++) { + assertThat(result.get(i).getAgendaTitle()) + .isEqualTo(agendas.get(size * (page - 1) + i).getTitle()); + assertThat(result.get(i).getAgendaStatus()).isNotEqualTo(AgendaStatus.FINISH); + assertThat(result.get(i).getAgendaStatus()).isNotEqualTo(AgendaStatus.CANCEL); + } + } + + @Test + @DisplayName("내가 주최하고 있는 Agenda 목록 조회 성공 - 빈 리스트인 경우") + void hostingAgendaListSuccessWithEmptyList() throws Exception { + // given + + // when + String response = mockMvc.perform(get("/agenda/host/current/list") + .header("Authorization", "Bearer " + accessToken) + .param("page", "1") + .param("size", "10")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper + .readValue(response, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // then + assertThat(pageResponseDto.getTotalSize()).isEqualTo(0); + assertThat(result.size()).isEqualTo(0); + } + } +} diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index f95370b56..d0d678b26 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.jpa.repository.Query; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; public interface AgendaRepository extends JpaRepository { @@ -22,4 +23,8 @@ public interface AgendaRepository extends JpaRepository { Page findAllByStatusIs(AgendaStatus status, Pageable pageable); Optional findAgendaByAgendaKey(UUID usedTo); + + @Query("SELECT a FROM Agenda a WHERE a.hostIntraId = :intraId AND (a.status = :status1 OR a.status = :status2)") + Page findAllByHostIntraIdAndStatus( + String intraId, AgendaStatus status1, AgendaStatus status2, Pageable pageable); } diff --git a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java index 3c1c35422..6f81ff646 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java +++ b/gg-utils/src/testFixtures/java/gg/utils/AgendaTestDataUtils.java @@ -1,5 +1,6 @@ package gg.utils; +import java.util.ArrayList; import java.util.List; import javax.persistence.EntityManager; @@ -76,4 +77,17 @@ public Agenda createAgendaTeamProfiles(User user, AgendaStatus status) { } return agenda; } + + public List createAgendasWithAllStatus(User user, int size) { + List agendas = new ArrayList<>(); + for (int i = 0; i < size; i++) { + agendas.add(agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.OPEN)); + agendas.add(agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.CONFIRM)); + agendas.add(agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.FINISH)); + agendas.add(agendaFixture.createAgenda(user.getIntraId(), AgendaStatus.CANCEL)); + } + em.flush(); + em.clear(); + return agendas; + } } From ef9383544079a22ddaf1a01b9aaa0d1bfb4ca898 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:13:52 +0900 Subject: [PATCH 083/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20AgendaProfile?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20-=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=20=EB=B0=8F=20=EC=97=85=EC=A0=81=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20#940=20(#976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 17 ++- .../response/AgendaProfileDetailsResDto.java | 30 +++- .../service/AgendaProfileFindService.java | 22 +-- .../service/AgendaProfileService.java | 6 - .../service/IntraProfileUtils.java | 68 +++++++++ .../intraprofile/IntraAchievement.java | 53 +++++++ .../service/intraprofile/IntraImage.java | 23 +++ .../intraprofile/IntraImageVersion.java | 29 ++++ .../service/intraprofile/IntraProfile.java | 24 +++ .../intraprofile/IntraProfileResponse.java | 23 +++ .../ticket/controller/TicketController.java | 22 +-- .../user/ticket/service/TicketService.java | 40 ++--- .../AgendaProfileControllerTest.java | 12 ++ .../api/user/ticket/TicketControllerTest.java | 2 + .../main/java/gg/auth/FortyTwoAuthUtil.java | 138 ++++++++++-------- .../java/gg/repo/agenda/TicketRepository.java | 4 +- .../java/gg/utils/exception/ErrorCode.java | 1 + .../custom/AuthenticationException.java | 4 + 18 files changed, 385 insertions(+), 133 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 697fad220..0d127b76f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -25,21 +25,29 @@ import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; +import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; import gg.data.user.type.RoleType; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/agenda/profile") public class AgendaProfileController { private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileService agendaProfileService; + private final TicketService ticketService; + private final IntraProfileUtils intraProfileUtils; /** * AgendaProfile 상세 조회 API @@ -49,9 +57,12 @@ public class AgendaProfileController { @GetMapping public ResponseEntity myAgendaProfileDetails( @Login @Parameter(hidden = true) UserDto user) { - AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.detailsAgendaProfile( - user.getIntraId()); - return ResponseEntity.status(HttpStatus.OK).body(agendaProfileDetails); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + int ticketCount = ticketService.findTicketList(profile).size(); + IntraProfile intraProfile = intraProfileUtils.getIntraProfile(); + AgendaProfileDetailsResDto agendaProfileDetails = AgendaProfileDetailsResDto.toDto( + profile, ticketCount, intraProfile); + return ResponseEntity.ok(agendaProfileDetails); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java index 024c41fa2..fe479063c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -1,8 +1,10 @@ package gg.agenda.api.user.agendaprofile.controller.response; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,13 +18,29 @@ public class AgendaProfileDetailsResDto { private Coalition userCoalition; private Location userLocation; private int ticketCount; + private IntraProfile intraProfile; - public AgendaProfileDetailsResDto(String intraId, AgendaProfile entity, int ticketCount) { - this.userIntraId = intraId; - this.userContent = entity.getContent(); - this.userGithub = entity.getGithubUrl(); - this.userCoalition = entity.getCoalition(); - this.userLocation = entity.getLocation(); + @Builder + public AgendaProfileDetailsResDto(String userIntraId, String userContent, String userGithub, + Coalition userCoalition, Location userLocation, int ticketCount, IntraProfile intraProfile) { + this.userIntraId = userIntraId; + this.userContent = userContent; + this.userGithub = userGithub; + this.userCoalition = userCoalition; + this.userLocation = userLocation; this.ticketCount = ticketCount; + this.intraProfile = intraProfile; + } + + public static AgendaProfileDetailsResDto toDto(AgendaProfile profile, int ticketCount, IntraProfile intraProfile) { + return AgendaProfileDetailsResDto.builder() + .userIntraId(profile.getIntraId()) + .userContent(profile.getContent()) + .userGithub(profile.getGithubUrl()) + .userCoalition(profile.getCoalition()) + .userLocation(profile.getLocation()) + .ticketCount(ticketCount) + .intraProfile(intraProfile) + .build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 67c4416c4..41b546b91 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -10,8 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; -import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -21,35 +19,23 @@ import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; -import gg.repo.agenda.AgendaTeamRepository; -import gg.repo.agenda.TicketRepository; -import gg.repo.user.UserRepository; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class AgendaProfileFindService { private final AgendaProfileRepository agendaProfileRepository; - private final TicketRepository ticketRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaRepository agendaRepository; - /** - * AgendaProfile 상세 정보를 조회하는 메서드 - * @param intraId 로그인한 유저의 id - * @return AgendaProfileDetailsResDto 객체 - */ @Transactional(readOnly = true) - public AgendaProfileDetailsResDto detailsAgendaProfile(String intraId) { - AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) + public AgendaProfile findAgendaProfileByIntraId(String intraId) { + return agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - - int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(agendaProfile.getId()) - .size(); - - return new AgendaProfileDetailsResDto(intraId, agendaProfile, ticketCount); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java index 2f30b1d8a..6fffc7363 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java @@ -35,10 +35,4 @@ public void modifyAgendaProfile(Long userId, AgendaProfileChangeReqDto reqDto) { agendaProfile.updateProfile(reqDto.getUserContent(), reqDto.getUserGithub()); agendaProfileRepository.save(agendaProfile); } - - @Transactional(readOnly = true) - public AgendaProfile getAgendaProfile(Long userId) { - return agendaProfileRepository.findByUserId(userId) - .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java new file mode 100644 index 000000000..890d2980f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java @@ -0,0 +1,68 @@ +package gg.agenda.api.user.agendaprofile.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.List; +import java.util.Objects; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraImage; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfileResponse; +import gg.auth.FortyTwoAuthUtil; +import gg.utils.exception.custom.AuthenticationException; +import gg.utils.external.ApiUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class IntraProfileUtils { + private static final String INTRA_PROFILE_URL = "https://api.intra.42.fr/v2/me"; + + private final FortyTwoAuthUtil fortyTwoAuthUtil; + + private final ApiUtil apiUtil; + + public IntraProfile getIntraProfile() { + IntraProfileResponse intraProfileResponse = requestIntraProfile(); + intraProfileResponseValidation(intraProfileResponse); + IntraImage intraImage = intraProfileResponse.getImage(); + List intraAchievements = intraProfileResponse.getAchievements(); + return new IntraProfile(intraImage.getLink(), intraAchievements); + } + + private IntraProfileResponse requestIntraProfile() { + try { + String accessToken = fortyTwoAuthUtil.getAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + } catch (Exception e) { + String accessToken = fortyTwoAuthUtil.refreshAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + } + } + + private void intraProfileResponseValidation(IntraProfileResponse intraProfileResponse) { + if (Objects.isNull(intraProfileResponse)) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getImage())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getAchievements())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getImage().getLink())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java new file mode 100644 index 000000000..ade777add --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java @@ -0,0 +1,53 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraAchievement { + + private static final String IMAGE_URL = "https://cdn.intra.42.fr"; + + private Long id; + + private String name; + + private String description; + + private String tier; + + private String kind; + + private boolean visible; + + private String image; + + @JsonProperty("nbr_of_success") + private String nbrOfSuccess; + + @JsonProperty("users_url") + private URL usersUrl; + + @Builder + public IntraAchievement(Long id, String name, String description, String tier, String kind, boolean visible, + String image, String nbrOfSuccess, URL usersUrl) { + this.id = id; + this.name = name; + this.description = description; + this.tier = tier; + this.kind = kind; + this.visible = visible; + this.image = image; + this.nbrOfSuccess = nbrOfSuccess; + this.usersUrl = usersUrl; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java new file mode 100644 index 000000000..1f17a0a2f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java @@ -0,0 +1,23 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraImage { + + private URL link; + + private IntraImageVersion versions; + + @Builder + public IntraImage(URL link, IntraImageVersion versions) { + this.link = link; + this.versions = versions; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java new file mode 100644 index 000000000..f5b00d2ce --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraImageVersion { + + private URL large; + + private URL medium; + + private URL small; + + private URL micro; + + @Builder + public IntraImageVersion(URL large, URL medium, URL small, URL micro) { + this.large = large; + this.medium = medium; + this.small = small; + this.micro = micro; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java new file mode 100644 index 000000000..6ed2b7862 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java @@ -0,0 +1,24 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; +import java.util.List; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraProfile { + + private URL imageUrl; + + private List achievements; + + @Builder + public IntraProfile(URL imageUrl, List achievements) { + this.imageUrl = imageUrl; + this.achievements = achievements; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java new file mode 100644 index 000000000..8031e3561 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java @@ -0,0 +1,23 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.util.List; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraProfileResponse { + + IntraImage image; + + List achievements; + + @Builder + public IntraProfileResponse(IntraImage image, List achievements) { + this.image = image; + this.achievements = achievements; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 4598c1639..5e30ce829 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -1,5 +1,7 @@ package gg.agenda.api.user.ticket.controller; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; import java.util.stream.Collectors; @@ -12,9 +14,6 @@ import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; @@ -22,15 +21,18 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.utils.cookie.CookieUtil; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; +import gg.utils.exception.custom.AuthenticationException; import gg.utils.exception.user.TokenNotValidException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -41,6 +43,7 @@ public class TicketController { private final CookieUtil cookieUtil; private final TicketService ticketService; + private final AgendaProfileFindService agendaProfileFindService; /** * 티켓 설정 추가 @@ -59,7 +62,8 @@ public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login User */ @GetMapping public ResponseEntity ticketCountFind(@Parameter(hidden = true) @Login UserDto user) { - List tickets = ticketService.findTicketList(user); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + List tickets = ticketService.findTicketList(profile); long approvedCount = tickets.stream() .filter(Ticket::getIsApproved) .count(); @@ -75,16 +79,14 @@ public ResponseEntity ticketCountFind(@Parameter(hidden = tru @PatchMapping public ResponseEntity ticketApproveModify(@Parameter(hidden = true) @Login UserDto user, HttpServletResponse response) { - SecurityContext context = SecurityContextHolder.getContext(); - Authentication authentication = context.getAuthentication(); - try { - ticketService.modifyTicketApprove(user, authentication); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + ticketService.modifyTicketApprove(profile); + return ResponseEntity.noContent().build(); } catch (TokenNotValidException e) { cookieUtil.deleteCookie(response, "refresh_token"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + throw new AuthenticationException(REFRESH_TOKEN_EXPIRED); } - return ResponseEntity.noContent().build(); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 4efec89ab..371d86b73 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -12,15 +12,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.HttpClientErrorException; -import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.auth.FortyTwoAuthUtil; import gg.auth.UserDto; @@ -44,7 +41,7 @@ public class TicketService { private final FortyTwoAuthUtil fortyTwoAuthUtil; private final TicketRepository ticketRepository; private final AgendaRepository agendaRepository; - private final AgendaProfileService agendaProfileService; + private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileRepository agendaProfileRepository; @Value("https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id") @@ -75,30 +72,22 @@ public void addTicketSetup(UserDto user) { /** * 티켓 수 조회 - * @param user 사용자 정보 + * @param profile AgendaProfile * @return 티켓 수 */ @Transactional(readOnly = true) - public List findTicketList(UserDto user) { - AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) - .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - return ticketRepository.findByAgendaProfileAndIsUsedFalse(profile); + public List findTicketList(AgendaProfile profile) { + return ticketRepository.findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(profile); } /** * 티켓 승인/거절 - * @param user 사용자 정보 + * @param profile 사용자 정보 */ @Transactional - public void modifyTicketApprove(UserDto user, Authentication authentication) { - AgendaProfile profile = agendaProfileService.getAgendaProfile(user.getId()); + public void modifyTicketApprove(AgendaProfile profile) { Ticket setUpTicket = getSetUpTicket(profile); - - OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken)authentication; - OAuth2AuthorizedClient oAuth2AuthorizedClient = fortyTwoAuthUtil.getOAuth2AuthorizedClient(oauthToken); - - List> pointHistory = getPointHistory(profile, oAuth2AuthorizedClient, authentication); - + List> pointHistory = getPointHistory(profile); processTicketApproval(profile, setUpTicket, pointHistory); } @@ -115,22 +104,19 @@ public Ticket getSetUpTicket(AgendaProfile profile) { /** * 포인트 이력 조회 * @param profile AgendaProfile - * @param client OAuth2AuthorizedClient - * @param authentication Authentication * @return 포인트 이력 */ - private List> getPointHistory(AgendaProfile profile, OAuth2AuthorizedClient client, - Authentication authentication) { + private List> getPointHistory(AgendaProfile profile) { String url = pointHistoryUrl.replace("{id}", profile.getFortyTwoId().toString()); ParameterizedTypeReference>> responseType = new ParameterizedTypeReference<>() { }; - try { - return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + String accessToken = fortyTwoAuthUtil.getAccessToken(); + return apiUtil.callApiWithAccessToken(url, accessToken, responseType); } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { - client = fortyTwoAuthUtil.refreshAccessToken(client, authentication); - return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + String accessToken = fortyTwoAuthUtil.refreshAccessToken(); + return apiUtil.callApiWithAccessToken(url, accessToken, responseType); } throw e; } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 8e80d6bd0..477028add 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -5,6 +5,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -17,8 +18,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -31,6 +34,8 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; @@ -59,6 +64,10 @@ public class AgendaProfileControllerTest { private AgendaMockData agendaMockData; @Autowired private AgendaProfileRepository agendaProfileRepository; + + @MockBean + private IntraProfileUtils intraProfileUtils; + User user; String accessToken; AgendaProfile agendaProfile; @@ -77,6 +86,9 @@ void beforeEach() { @DisplayName("로그인된 유저에 해당하는 Agenda profile를 상세 조회합니다.") void test() throws Exception { //given + URL url = new URL("http://localhost:8080"); + IntraProfile intraProfile = new IntraProfile(url, List.of()); + Mockito.when(intraProfileUtils.getIntraProfile()).thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); // when diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index f42d21372..dcfcb2c59 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -146,6 +146,8 @@ void findTicketCountSuccess() throws Exception { //given ticketFixture.createTicket(seoulUserAgendaProfile); ticketFixture.createTicket(seoulUserAgendaProfile); + ticketFixture.createNotApporveTicket(seoulUserAgendaProfile); + //when String res = mockMvc.perform( get("/agenda/ticket") diff --git a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java index 22a614fc0..e0f851f4d 100644 --- a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java +++ b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java @@ -5,11 +5,14 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.Objects; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; @@ -19,7 +22,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestClientException; import gg.utils.exception.ErrorCode; import gg.utils.exception.custom.NotExistException; @@ -32,72 +34,88 @@ public class FortyTwoAuthUtil { private final ApiUtil apiUtil; private final OAuth2AuthorizedClientService authorizedClientService; - /** - * OAuth2AuthorizedClient 조회 - * @param oauthToken OAuth2AuthenticationToken - * @return OAuth2AuthorizedClient - */ - public OAuth2AuthorizedClient getOAuth2AuthorizedClient(OAuth2AuthenticationToken oauthToken) { - String registrationId = oauthToken.getAuthorizedClientRegistrationId(); - OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(registrationId, - oauthToken.getName()); - if (client.getRefreshToken() == null) { - throw new NotExistException(AUTH_NOT_FOUND); - } - return client; + public String getAccessToken() { + Authentication authentication = getAuthenticationFromContext(); + OAuth2AuthorizedClient client = getClientFromAuthentication(authentication); + return client.getAccessToken().getTokenValue(); } /** * 토큰 갱신 - * @param client OAuth2AuthorizedClient - * @param authentication Authentication * @return 갱신된 OAuth2AuthorizedClient */ - public OAuth2AuthorizedClient refreshAccessToken(OAuth2AuthorizedClient client, Authentication authentication) { - try { - ClientRegistration registration = client.getClientRegistration(); - if (client.getRefreshToken() == null) { - throw new NotExistException(AUTH_NOT_FOUND); - } - - String tokenUri = registration.getProviderDetails().getTokenUri(); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "refresh_token"); - params.add("refresh_token", client.getRefreshToken().getTokenValue()); - params.add("client_id", registration.getClientId()); - params.add("client_secret", registration.getClientSecret()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - List> responseBody = apiUtil.apiCall(tokenUri, List.class, headers, params, - HttpMethod.POST); - if (responseBody == null || responseBody.isEmpty()) { - throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); - } - Map map = responseBody.get(0); - - OAuth2AccessToken newAccessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - (String)map.get("access_token"), - Instant.now(), - Instant.now().plusSeconds((Integer)map.get("expires_in")) - ); - - OAuth2RefreshToken newRefreshToken = new OAuth2RefreshToken( - (String)map.get("refresh_token"), - Instant.now() - ); - - OAuth2AuthorizedClient newClient = new OAuth2AuthorizedClient( - registration, client.getPrincipalName(), newAccessToken, newRefreshToken); - - String principalName = authentication.getName(); - authorizedClientService.removeAuthorizedClient(registration.getRegistrationId(), principalName); - authorizedClientService.saveAuthorizedClient(newClient, authentication); - - return newClient; - } catch (RestClientException e) { + public String refreshAccessToken() { + Authentication authentication = getAuthenticationFromContext(); + OAuth2AuthorizedClient client = getClientFromAuthentication(authentication); + ClientRegistration registration = client.getClientRegistration(); + + OAuth2AuthorizedClient newClient = requestNewClient(client, registration); + + authorizedClientService.removeAuthorizedClient( + registration.getRegistrationId(), client.getPrincipalName()); + authorizedClientService.saveAuthorizedClient(newClient, authentication); + + return newClient.getAccessToken().getTokenValue(); + } + + private Authentication getAuthenticationFromContext() { + SecurityContext context = SecurityContextHolder.getContext(); + return context.getAuthentication(); + } + + private OAuth2AuthorizedClient getClientFromAuthentication(Authentication authentication) { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken)authentication; + String registrationId = oauthToken.getAuthorizedClientRegistrationId(); + return authorizedClientService.loadAuthorizedClient(registrationId, oauthToken.getName()); + } + + private OAuth2AuthorizedClient requestNewClient(OAuth2AuthorizedClient client, ClientRegistration registration) { + if (Objects.isNull(client.getRefreshToken())) { throw new NotExistException(AUTH_NOT_FOUND); } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "refresh_token"); + params.add("refresh_token", client.getRefreshToken().getTokenValue()); + params.add("client_id", registration.getClientId()); + params.add("client_secret", registration.getClientSecret()); + + List> responseBody = apiUtil.apiCall( + registration.getProviderDetails().getTokenUri(), + List.class, + headers, + params, + HttpMethod.POST + ); + if (Objects.isNull(responseBody) || responseBody.isEmpty()) { + throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); + } + return createNewClientFromApiResponse(responseBody.get(0), client); + } + + private OAuth2AuthorizedClient createNewClientFromApiResponse( + Map response, OAuth2AuthorizedClient client) { + + OAuth2AccessToken newAccessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + (String)response.get("access_token"), + Instant.now(), + Instant.now().plusSeconds((Integer)response.get("expires_in")) + ); + + OAuth2RefreshToken newRefreshToken = new OAuth2RefreshToken( + (String)response.get("refresh_token"), + Instant.now() + ); + + return new OAuth2AuthorizedClient( + client.getClientRegistration(), + client.getPrincipalName(), + newAccessToken, + newRefreshToken + ); } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 0c35fffec..3c9c9b957 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -14,13 +14,11 @@ public interface TicketRepository extends JpaRepository { Optional findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( AgendaProfile agendaProfile); - List findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(Long agendaProfileId); - Optional findByAgendaProfileAndIsApprovedFalse(AgendaProfile agendaProfile); Optional findByAgendaProfileId(Long agendaProfileId); Page findByAgendaProfileId(Long agendaProfileId, Pageable pageable); - List findByAgendaProfileAndIsUsedFalse(AgendaProfile agendaProfile); + List findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(AgendaProfile agendaProfile); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 59374b432..0d3f14e08 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -129,6 +129,7 @@ public enum ErrorCode { UNREADABLE_HTTP_MESSAGE(400, "CM008", "유효하지 않은 HTTP 메시지입니다."), CONFLICT(409, "CM009", "CONFLICT"), FORBIDDEN(403, "CM010", "접근이 금지된 요청입니다."), + REFRESH_TOKEN_EXPIRED(401, "CM011", "토큰이 만료되었습니다. 다시 로그인해주세요."), //Feedback FEEDBACK_NOT_FOUND(404, "FB100", "FB NOT FOUND"), diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java b/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java index 6fb9ac499..107d3b307 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java @@ -8,4 +8,8 @@ public class AuthenticationException extends CustomRuntimeException { public AuthenticationException(String message, ErrorCode errorCode) { super(message, errorCode); } + + public AuthenticationException(ErrorCode errorCode) { + super(errorCode.getMessage(), errorCode); + } } From 9d4cb34a34c0798038be85263ec948f40ab0ff44 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:20:21 +0900 Subject: [PATCH 084/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EB=8B=A4?= =?UTF-8?q?=EB=A5=B8=20=EC=82=AC=EC=9A=A9=EC=9E=90=20AgendaProfile=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20#977=20(#978)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/response/AgendaTeamResDto.java | 8 ++- .../controller/AgendaHostController.java | 14 ++-- .../controller/AgendaProfileController.java | 55 +++++++++------- .../response/AgendaProfileDetailsResDto.java | 27 +++++--- .../MyAgendaProfileDetailsResDto.java | 55 ++++++++++++++++ .../service/IntraProfileUtils.java | 37 ++++++++--- .../AgendaHostControllerTest.java | 8 +-- .../AgendaProfileControllerTest.java | 65 +++++++++++++++++-- 8 files changed, 209 insertions(+), 60 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/MyAgendaProfileDetailsResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java index 939864087..850b2e1b6 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/response/AgendaTeamResDto.java @@ -7,6 +7,7 @@ import org.mapstruct.factory.Mappers; import gg.data.agenda.AgendaTeam; +import gg.data.agenda.type.Location; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -32,11 +33,14 @@ public class AgendaTeamResDto { private String teamAward; + private Location teamLocation; + private Integer teamAwardPriority; @Builder public AgendaTeamResDto(String teamName, String teamStatus, boolean teamIsPrivate, String teamLeaderIntraId, - int teamMateCount, UUID teamKey, String teamAward, Integer teamAwardPriority, String teamContent) { + int teamMateCount, UUID teamKey, String teamAward, Integer teamAwardPriority, String teamContent, + Location teamLocation) { this.teamName = teamName; this.teamContent = teamContent; this.teamStatus = teamStatus; @@ -46,6 +50,7 @@ public AgendaTeamResDto(String teamName, String teamStatus, boolean teamIsPrivat this.teamKey = teamKey; this.teamAward = teamAward; this.teamAwardPriority = teamAwardPriority; + this.teamLocation = teamLocation; } @Mapper @@ -60,6 +65,7 @@ public interface MapStruct { @Mapping(target = "teamLeaderIntraId", source = "leaderIntraId") @Mapping(target = "teamMateCount", source = "mateCount") @Mapping(target = "teamKey", source = "teamKey") + @Mapping(target = "teamLocation", source = "location") @Mapping(target = "teamAward", source = "award", defaultValue = "AgendaTeam.DEFAULT_AWARD") @Mapping(target = "teamAwardPriority", source = "awardPriority", defaultValue = "0") AgendaTeamResDto toDto(AgendaTeam agendaTeam); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java index a0b873cfb..87062f5c8 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaHostController.java @@ -12,6 +12,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,15 +33,14 @@ public class AgendaHostController { private final AgendaProfileFindService agendaProfileFindService; - @GetMapping("/history/list") + @GetMapping("/history/list/{intraId}") public ResponseEntity> hostedAgendaList( - @Login @Parameter(hidden = true) UserDto user, - @ModelAttribute @Valid PageRequestDto pageRequestDto) { + @PathVariable String intraId, @ModelAttribute @Valid PageRequestDto pageRequestDto) { int page = pageRequestDto.getPage(); int size = pageRequestDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - Page hostedAgendas = agendaProfileFindService.findHostedAgenda(user.getIntraId(), pageable); + Page hostedAgendas = agendaProfileFindService.findHostedAgenda(intraId, pageable); List agendaResDtos = hostedAgendas.stream() .map(HostedAgendaResDto.MapStruct.INSTANCE::toDto) @@ -50,15 +50,15 @@ public ResponseEntity> hostedAgendaList( return ResponseEntity.ok(pageResponseDto); } - @GetMapping("/current/list") + @GetMapping("/current/list/{intraId}") public ResponseEntity> hostingAgendaList( - @Login @Parameter(hidden = true) UserDto user, + @PathVariable String intraId, @ModelAttribute @Valid PageRequestDto pageRequestDto) { int page = pageRequestDto.getPage(); int size = pageRequestDto.getSize(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); - Page hostedAgendas = agendaProfileFindService.findHostingAgenda(user.getIntraId(), pageable); + Page hostedAgendas = agendaProfileFindService.findHostingAgenda(intraId, pageable); List agendaResDtos = hostedAgendas.stream() .map(HostedAgendaResDto.MapStruct.INSTANCE::toDto) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 0d127b76f..0e5755efe 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -23,6 +24,7 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.agenda.api.user.agendaprofile.controller.response.MyAgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; @@ -49,18 +51,33 @@ public class AgendaProfileController { private final TicketService ticketService; private final IntraProfileUtils intraProfileUtils; + /** + * AgendaProfile admin 여부 조회 API + * @param user 로그인한 사용자 정보 + */ + @GetMapping("/info") + public ResponseEntity myAgendaProfileInfoDetails( + @Login @Parameter(hidden = true) UserDto user) { + String intraId = user.getIntraId(); + Boolean isAdmin = user.getRoleType() == RoleType.ADMIN; + + AgendaProfileInfoDetailsResDto agendaProfileInfoDetails = new AgendaProfileInfoDetailsResDto(intraId, isAdmin); + + return ResponseEntity.ok(agendaProfileInfoDetails); + } + /** * AgendaProfile 상세 조회 API * @param user 로그인한 사용자 정보 * @return AgendaProfileDetailsResDto 객체와 HTTP 상태 코드를 포함한 ResponseEntity */ @GetMapping - public ResponseEntity myAgendaProfileDetails( + public ResponseEntity myAgendaProfileDetails( @Login @Parameter(hidden = true) UserDto user) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); int ticketCount = ticketService.findTicketList(profile).size(); IntraProfile intraProfile = intraProfileUtils.getIntraProfile(); - AgendaProfileDetailsResDto agendaProfileDetails = AgendaProfileDetailsResDto.toDto( + MyAgendaProfileDetailsResDto agendaProfileDetails = MyAgendaProfileDetailsResDto.toDto( profile, ticketCount, intraProfile); return ResponseEntity.ok(agendaProfileDetails); } @@ -78,21 +95,6 @@ public ResponseEntity agendaProfileModify(@Login @Parameter(hidden = true) return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } - /** - * AgendaProfile admin 여부 조회 API - * @param user 로그인한 사용자 정보 - */ - @GetMapping("/info") - public ResponseEntity myAgendaProfileInfoDetails( - @Login @Parameter(hidden = true) UserDto user) { - String intraId = user.getIntraId(); - Boolean isAdmin = user.getRoleType() == RoleType.ADMIN; - - AgendaProfileInfoDetailsResDto agendaProfileInfoDetails = new AgendaProfileInfoDetailsResDto(intraId, isAdmin); - - return ResponseEntity.ok(agendaProfileInfoDetails); - } - /** * 현재 참여중인 Agenda 목록 조회하는 메서드 * @param user 로그인한 유저의 id @@ -101,23 +103,28 @@ public ResponseEntity myAgendaProfileInfoDetails @GetMapping("/current/list") public ResponseEntity> getCurrentAttendAgendaList( @Login @Parameter(hidden = true) UserDto user) { - String intraId = user.getIntraId(); - - List currentAttendAgendaList = agendaProfileFindService.findCurrentAttendAgenda( - intraId); + List currentAttendAgendaList = agendaProfileFindService + .findCurrentAttendAgenda(user.getIntraId()); return ResponseEntity.ok(currentAttendAgendaList); } + @GetMapping("/{intraId}") + public ResponseEntity agendaProfileDetails(@PathVariable String intraId) { + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(intraId); + IntraProfile intraProfile = intraProfileUtils.getIntraProfile(intraId); + AgendaProfileDetailsResDto resDto = AgendaProfileDetailsResDto.toDto(profile, intraProfile); + return ResponseEntity.ok(resDto); + } + /** * 과거에 참여했던 Agenda 목록 조회하는 메서드 * @param pageRequest 페이지네이션 요청 정보, agendaId 아젠다 아이디 */ - @GetMapping("/history/list") + @GetMapping("/history/list/{intraId}") public ResponseEntity> getAttendedAgendaList( - @Login @Parameter(hidden = true) UserDto user, @ModelAttribute @Valid PageRequestDto pageRequest) { + @PathVariable String intraId, @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); - String intraId = user.getIntraId(); Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); Page attendedAgendaList = agendaProfileFindService diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java index fe479063c..c7f224121 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -1,46 +1,53 @@ package gg.agenda.api.user.agendaprofile.controller.response; +import java.net.URL; +import java.util.List; + +import org.mapstruct.Mapper; + +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AgendaProfileDetailsResDto { - private String userIntraId; private String userContent; private String userGithub; private Coalition userCoalition; private Location userLocation; - private int ticketCount; - private IntraProfile intraProfile; + private URL imageUrl; + private List achievements; @Builder public AgendaProfileDetailsResDto(String userIntraId, String userContent, String userGithub, - Coalition userCoalition, Location userLocation, int ticketCount, IntraProfile intraProfile) { + Coalition userCoalition, Location userLocation, URL imageUrl, + List achievements) { this.userIntraId = userIntraId; this.userContent = userContent; this.userGithub = userGithub; this.userCoalition = userCoalition; this.userLocation = userLocation; - this.ticketCount = ticketCount; - this.intraProfile = intraProfile; + this.imageUrl = imageUrl; + this.achievements = achievements; } - public static AgendaProfileDetailsResDto toDto(AgendaProfile profile, int ticketCount, IntraProfile intraProfile) { + public static AgendaProfileDetailsResDto toDto(AgendaProfile profile, IntraProfile intraProfile) { return AgendaProfileDetailsResDto.builder() .userIntraId(profile.getIntraId()) .userContent(profile.getContent()) .userGithub(profile.getGithubUrl()) .userCoalition(profile.getCoalition()) .userLocation(profile.getLocation()) - .ticketCount(ticketCount) - .intraProfile(intraProfile) + .imageUrl(intraProfile.getImageUrl()) + .achievements(intraProfile.getAchievements()) .build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/MyAgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/MyAgendaProfileDetailsResDto.java new file mode 100644 index 000000000..abb0d37fa --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/MyAgendaProfileDetailsResDto.java @@ -0,0 +1,55 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import java.net.URL; +import java.util.List; + +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.type.Coalition; +import gg.data.agenda.type.Location; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class MyAgendaProfileDetailsResDto { + + private String userIntraId; + private String userContent; + private String userGithub; + private Coalition userCoalition; + private Location userLocation; + private int ticketCount; + private URL imageUrl; + private List achievements; + + @Builder + public MyAgendaProfileDetailsResDto(String userIntraId, String userContent, String userGithub, + Coalition userCoalition, Location userLocation, int ticketCount, URL imageUrl, + List achievements) { + this.userIntraId = userIntraId; + this.userContent = userContent; + this.userGithub = userGithub; + this.userCoalition = userCoalition; + this.userLocation = userLocation; + this.ticketCount = ticketCount; + this.imageUrl = imageUrl; + this.achievements = achievements; + } + + public static MyAgendaProfileDetailsResDto toDto(AgendaProfile profile, int ticketCount, + IntraProfile intraProfile) { + return MyAgendaProfileDetailsResDto.builder() + .userIntraId(profile.getIntraId()) + .userContent(profile.getContent()) + .userGithub(profile.getGithubUrl()) + .userCoalition(profile.getCoalition()) + .userLocation(profile.getLocation()) + .ticketCount(ticketCount) + .imageUrl(intraProfile.getImageUrl()) + .achievements(intraProfile.getAchievements()) + .build(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java index 890d2980f..6e7c39e95 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java @@ -23,34 +23,55 @@ @Component @RequiredArgsConstructor public class IntraProfileUtils { + private static final String INTRA_PROFILE_URL = "https://api.intra.42.fr/v2/me"; + private static final String INTRA_USERS_URL = "https://api.intra.42.fr/v2/users/"; private final FortyTwoAuthUtil fortyTwoAuthUtil; private final ApiUtil apiUtil; public IntraProfile getIntraProfile() { - IntraProfileResponse intraProfileResponse = requestIntraProfile(); - intraProfileResponseValidation(intraProfileResponse); - IntraImage intraImage = intraProfileResponse.getImage(); - List intraAchievements = intraProfileResponse.getAchievements(); - return new IntraProfile(intraImage.getLink(), intraAchievements); + try { + IntraProfileResponse intraProfileResponse = requestIntraProfile(INTRA_PROFILE_URL); + intraProfileResponseValidation(intraProfileResponse); + IntraImage intraImage = intraProfileResponse.getImage(); + List intraAchievements = intraProfileResponse.getAchievements(); + return new IntraProfile(intraImage.getLink(), intraAchievements); + } catch (Exception e) { + log.error("42 Intra Profile API 호출 실패", e); + throw new AuthenticationException(AUTH_NOT_FOUND); + } + } + + public IntraProfile getIntraProfile(String intraId) { + try { + IntraProfileResponse intraProfileResponse = requestIntraProfile(INTRA_USERS_URL + intraId); + intraProfileResponseValidation(intraProfileResponse); + IntraImage intraImage = intraProfileResponse.getImage(); + List intraAchievements = intraProfileResponse.getAchievements(); + return new IntraProfile(intraImage.getLink(), intraAchievements); + } catch (Exception e) { + log.error("42 Intra Profile API 호출 실패", e); + throw new AuthenticationException(AUTH_NOT_FOUND); + } } - private IntraProfileResponse requestIntraProfile() { + private IntraProfileResponse requestIntraProfile(String requestUrl) { try { String accessToken = fortyTwoAuthUtil.getAccessToken(); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); - return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + return apiUtil.apiCall(requestUrl, IntraProfileResponse.class, headers, HttpMethod.GET); } catch (Exception e) { String accessToken = fortyTwoAuthUtil.refreshAccessToken(); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); - return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + return apiUtil.apiCall(requestUrl, IntraProfileResponse.class, headers, HttpMethod.GET); } } + private void intraProfileResponseValidation(IntraProfileResponse intraProfileResponse) { if (Objects.isNull(intraProfileResponse)) { throw new AuthenticationException(AUTH_NOT_FOUND); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java index d2499409f..4dbe4935f 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaHostControllerTest.java @@ -76,7 +76,7 @@ void hostedAgendaListSuccess(int page) throws Exception { .collect(Collectors.toList()); // when - String response = mockMvc.perform(get("/agenda/host/history/list") + String response = mockMvc.perform(get("/agenda/host/history/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) @@ -105,7 +105,7 @@ void hostedAgendaListSuccessWithEmptyAgenda() throws Exception { // given // when - String response = mockMvc.perform(get("/agenda/host/history/list") + String response = mockMvc.perform(get("/agenda/host/history/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", "1") .param("size", "10")) @@ -141,7 +141,7 @@ void hostingAgendaListSuccess(int page) throws Exception { .collect(Collectors.toList()); // when - String response = mockMvc.perform(get("/agenda/host/current/list") + String response = mockMvc.perform(get("/agenda/host/current/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", String.valueOf(page)) .param("size", String.valueOf(size))) @@ -170,7 +170,7 @@ void hostingAgendaListSuccessWithEmptyList() throws Exception { // given // when - String response = mockMvc.perform(get("/agenda/host/current/list") + String response = mockMvc.perform(get("/agenda/host/current/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", "1") .param("size", "10")) diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 477028add..c04c000fc 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -34,6 +34,7 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.agenda.api.user.agendaprofile.controller.response.MyAgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.Agenda; @@ -73,8 +74,8 @@ public class AgendaProfileControllerTest { AgendaProfile agendaProfile; @Nested - @DisplayName("agenda profile 상세 조회") - class GetAgendaProfile { + @DisplayName("나의 agenda profile 상세 조회") + class GetMyAgendaProfile { @BeforeEach void beforeEach() { @@ -96,7 +97,7 @@ void test() throws Exception { .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - AgendaProfileDetailsResDto result = objectMapper.readValue(response, AgendaProfileDetailsResDto.class); + MyAgendaProfileDetailsResDto result = objectMapper.readValue(response, MyAgendaProfileDetailsResDto.class); // then assertThat(result.getUserIntraId()).isEqualTo(user.getIntraId()); assertThat(result.getUserContent()).isEqualTo(agendaProfile.getContent()); @@ -129,6 +130,58 @@ void testAgendaProfileNotFound() throws Exception { } } + @Nested + @DisplayName("agenda profile 상세 조회") + class GetAgendaProfile { + + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewUser(); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + } + + @Test + @DisplayName("agenda profile 상세 조회 성공") + void getAgendaProfileSuccess() throws Exception { + //given + URL url = new URL("http://localhost:8080"); + IntraProfile intraProfile = new IntraProfile(url, List.of()); + Mockito.when(intraProfileUtils.getIntraProfile(user.getIntraId())).thenReturn(intraProfile); + AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + agendaMockData.createTicket(agendaProfile); + // when + String response = mockMvc.perform(get("/agenda/profile/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaProfileDetailsResDto result = objectMapper.readValue(response, + AgendaProfileDetailsResDto.class); + + // then + assertThat(result.getUserIntraId()).isEqualTo(user.getIntraId()); + assertThat(result.getUserContent()).isEqualTo(agendaProfile.getContent()); + assertThat(result.getUserGithub()).isEqualTo(agendaProfile.getGithubUrl()); + assertThat(result.getUserCoalition()).isEqualTo(agendaProfile.getCoalition()); + assertThat(result.getUserLocation()).isEqualTo(agendaProfile.getLocation()); + } + + @Test + @DisplayName("agenda profile 상세 조회 성공 - 존재하지 않는 사용자인 경우") + void getAgendaProfileFailedWithInvalidIntraId() throws Exception { + //given + URL url = new URL("http://localhost:8080"); + IntraProfile intraProfile = new IntraProfile(url, List.of()); + Mockito.when(intraProfileUtils.getIntraProfile()).thenReturn(intraProfile); + AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); + agendaMockData.createTicket(agendaProfile); + + // when + mockMvc.perform(get("/agenda/profile/" + "invalidIntraId") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + } + @Nested @DisplayName("개인 프로필 정보 변경") class UpdateAgendaProfile { @@ -384,7 +437,7 @@ void getAttendedAgendaListSuccess(int page) throws Exception { String request = objectMapper.writeValueAsString(pageRequest); // when - String response = mockMvc.perform(get("/agenda/profile/history/list") + String response = mockMvc.perform(get("/agenda/profile/history/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", String.valueOf(page)) .param("size", String.valueOf(size)) @@ -427,7 +480,7 @@ void getAttendedAgendaListSuccessNoAgenda() throws Exception { String request = objectMapper.writeValueAsString(pageRequest); // when - String response = mockMvc.perform(get("/agenda/profile/history/list") + String response = mockMvc.perform(get("/agenda/profile/history/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", "1") .param("size", "10") @@ -452,7 +505,7 @@ void getAttendedAgendaListBadRequest() throws Exception { String request = objectMapper.writeValueAsString(pageRequest); // when & then - mockMvc.perform(get("/agenda/profile/history/list") + mockMvc.perform(get("/agenda/profile/history/list/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken) .param("page", "0") .param("size", "10") From 499dcf655f57370f754caa937c364641aab66315 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:59:18 +0900 Subject: [PATCH 085/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=EC=83=81?= =?UTF-8?q?=ED=99=A9=EC=97=90=20=EB=A7=9E=EA=B2=8C=20slack=20API=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B4=EB=8A=94=20API=20=EC=9E=91=EC=84=B1=20#971?= =?UTF-8?q?=20(#979)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamAdminController.java | 7 ++ .../service/AgendaTeamAdminService.java | 8 ++ .../agenda/controller/AgendaController.java | 13 ++- .../user/agenda/service/AgendaService.java | 12 ++- .../AgendaAnnouncementController.java | 7 +- .../service/AgendaAnnouncementService.java | 11 ++- .../controller/AgendaTeamController.java | 16 ++- .../agendateam/service/AgendaTeamService.java | 21 ++-- .../agenda/api/utils/AgendaSlackService.java | 97 +++++++++++++++++++ .../gg/agenda/api/utils/SnsMessageUtil.java | 91 +++++++++++++++++ .../AgendaTeamAdminControllerTest.java | 42 +++++++- .../agendateam/AgendaTeamControllerTest.java | 2 +- .../main/java/gg/data/agenda/AgendaTeam.java | 5 + .../agenda/AgendaTeamProfileRepository.java | 5 + 14 files changed, 305 insertions(+), 32 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java index b8b0d7360..9beaa7816 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminController.java @@ -68,4 +68,11 @@ public ResponseEntity agendaTeamUpdate(@RequestBody @Valid AgendaTeamUpdat agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + @PatchMapping("/cancel") + public ResponseEntity agendaTeamCancel(@RequestParam("team_key") UUID teamKey) { + AgendaTeam agendaTeam = agendaTeamAdminService.getAgendaTeamByTeamKey(teamKey); + agendaTeamAdminService.cancelAgendaTeam(agendaTeam); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index 3e336c0fc..0c90a5bfe 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -91,4 +91,12 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { agendaTeamProfileAdminRepository.save(agendaTeamProfile); }); } + + @Transactional + public void cancelAgendaTeam(AgendaTeam agendaTeam) { + List agendaTeamProfiles = agendaTeamProfileAdminRepository + .findAllByAgendaTeamAndIsExistIsTrue(agendaTeam); + agendaTeamProfiles.forEach(AgendaTeamProfile::changeExistFalse); + agendaTeam.adminCancelTeam(); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index d8bb4ad13..d2efc4b12 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -32,13 +32,14 @@ import gg.agenda.api.user.agenda.controller.response.AgendaSimpleResDto; import gg.agenda.api.user.agenda.service.AgendaService; import gg.agenda.api.user.agendaannouncement.service.AgendaAnnouncementService; +import gg.agenda.api.utils.AgendaSlackService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaTeam; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; import gg.utils.exception.custom.InvalidParameterException; -import gg.utils.exception.user.UserImageLargeException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -48,7 +49,7 @@ public class AgendaController { private final AgendaService agendaService; - + private final AgendaSlackService agendaSlackService; private final AgendaAnnouncementService agendaAnnouncementService; @GetMapping @@ -73,7 +74,7 @@ public ResponseEntity> agendaListCurrent() { public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, @ModelAttribute @Valid AgendaCreateReqDto agendaCreateReqDto, @RequestParam(required = false) MultipartFile agendaPoster) { - if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024) { // 1MB + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024) { // 1MB throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); } UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, agendaPoster, user).getAgendaKey(); @@ -107,6 +108,7 @@ public ResponseEntity agendaEndWithAwards(@RequestParam("agenda_key") UUID agendaService.awardAgenda(agendaAwardsReqDto, agenda); } agendaService.finishAgenda(agenda); + agendaSlackService.slackFinishAgenda(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @@ -115,7 +117,9 @@ public ResponseEntity agendaConfirm(@RequestParam("agenda_key") UUID agend @Login @Parameter(hidden = true) UserDto user) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); - agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); + List failTeam = agendaService.confirmAgendaAndRefundTicketForOpenTeam(agenda); + agendaSlackService.slackConfirmAgenda(agenda); + agendaSlackService.slackCancelByAgendaConfirm(agenda, failTeam); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @@ -125,6 +129,7 @@ public ResponseEntity agendaCancel(@RequestParam("agenda_key") UUID agenda Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); agendaService.cancelAgenda(agenda); + agendaSlackService.slackCancelAgenda(agenda); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 46cbfe25e..1ceeb7494 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -20,7 +20,7 @@ import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; import gg.agenda.api.user.agendateam.service.AgendaTeamService; -import gg.agenda.api.user.ticket.service.TicketService; +import gg.agenda.api.utils.SnsMessageUtil; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; @@ -33,6 +33,7 @@ import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.ImageHandler; +import gg.utils.sns.MessageSender; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -47,12 +48,14 @@ public class AgendaService { private final AgendaTeamProfileRepository agendaTeamProfileRepository; - private final TicketService ticketService; - private final AgendaTeamService agendaTeamService; private final ImageHandler imageHandler; + private final MessageSender messageSender; + + private final SnsMessageUtil snsMessageUtil; + @Value("${info.image.defaultUrl}") private String defaultUri; @@ -110,7 +113,7 @@ public void awardAgenda(AgendaAwardsReqDto agendaAwardsReqDto, Agenda agenda) { } @Transactional - public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { + public List confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { if (agenda.getCurrentTeam() < agenda.getMinTeam()) { throw new ForbiddenException("팀이 모두 구성되지 않았습니다."); } @@ -120,6 +123,7 @@ public void confirmAgendaAndRefundTicketForOpenTeam(Agenda agenda) { agendaTeamService.leaveTeamAll(openTeam); } agenda.confirmAgenda(); + return openTeams; } @Transactional diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java index 0d043718c..c67941230 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/controller/AgendaAnnouncementController.java @@ -24,6 +24,7 @@ import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; import gg.agenda.api.user.agendaannouncement.controller.response.AgendaAnnouncementResDto; import gg.agenda.api.user.agendaannouncement.service.AgendaAnnouncementService; +import gg.agenda.api.utils.AgendaSlackService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; import gg.data.agenda.Agenda; @@ -38,7 +39,7 @@ public class AgendaAnnouncementController { private final AgendaService agendaService; - + private final AgendaSlackService agendaSlackService; private final AgendaAnnouncementService agendaAnnouncementService; @PostMapping @@ -46,7 +47,9 @@ public ResponseEntity agendaAnnouncementAdd(@Login UserDto user, @RequestP @RequestBody @Valid AgendaAnnouncementCreateReqDto agendaAnnouncementCreateReqDto) { Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); agenda.mustModifiedByHost(user.getIntraId()); - agendaAnnouncementService.addAgendaAnnouncement(agendaAnnouncementCreateReqDto, agenda); + AgendaAnnouncement newAnnounce = agendaAnnouncementService + .addAgendaAnnouncement(agendaAnnouncementCreateReqDto, agenda); + agendaSlackService.slackAddAgendaAnnouncement(agenda, newAnnounce); return ResponseEntity.status(HttpStatus.CREATED).build(); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java index bd17cc9cb..e34e789d1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaannouncement/service/AgendaAnnouncementService.java @@ -1,6 +1,5 @@ package gg.agenda.api.user.agendaannouncement.service; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -9,22 +8,28 @@ import org.springframework.transaction.annotation.Transactional; import gg.agenda.api.user.agendaannouncement.controller.request.AgendaAnnouncementCreateReqDto; +import gg.agenda.api.utils.SnsMessageUtil; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaAnnouncement; import gg.repo.agenda.AgendaAnnouncementRepository; +import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.utils.sns.MessageSender; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class AgendaAnnouncementService { + private final MessageSender messageSender; + private final SnsMessageUtil snsMessageUtil; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaAnnouncementRepository agendaAnnouncementRepository; @Transactional - public void addAgendaAnnouncement(AgendaAnnouncementCreateReqDto announceCreateDto, Agenda agenda) { + public AgendaAnnouncement addAgendaAnnouncement(AgendaAnnouncementCreateReqDto announceCreateDto, Agenda agenda) { AgendaAnnouncement newAnnounce = AgendaAnnouncementCreateReqDto .MapStruct.INSTANCE.toEntity(announceCreateDto, agenda); - agendaAnnouncementRepository.save(newAnnounce); + return agendaAnnouncementRepository.save(newAnnounce); } @Transactional(readOnly = true) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 35bd35fdd..6cea4f418 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agenda.service.AgendaService; import gg.agenda.api.user.agendateam.controller.request.TeamCreateReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamKeyReqDto; import gg.agenda.api.user.agendateam.controller.request.TeamUpdateReqDto; @@ -33,8 +34,10 @@ import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.agendateam.service.AgendaTeamService; +import gg.agenda.api.utils.AgendaSlackService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.Coalition; import gg.utils.dto.PageRequestDto; @@ -46,7 +49,9 @@ @RequiredArgsConstructor @RequestMapping("/agenda/team") public class AgendaTeamController { + private final AgendaService agendaService; private final AgendaTeamService agendaTeamService; + private final AgendaSlackService agendaSlackService; /** * 내 팀 간단 정보 조회 @@ -94,7 +99,9 @@ public ResponseEntity agendaTeamAdd(@Parameter(hidden = true) @Lo @PatchMapping("/confirm") public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto user, @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - agendaTeamService.confirmTeam(user, agendaKey, teamKeyReqDto.getTeamKey()); + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + AgendaTeam agendaTeam = agendaTeamService.confirmTeam(user, agenda, teamKeyReqDto.getTeamKey()); + agendaSlackService.slackConfirmAgendaTeam(agenda, agendaTeam); return ResponseEntity.ok().build(); } @@ -110,9 +117,11 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { agendaTeam.agendaTeamStatusMustBeOpenAndConfirm(); agendaTeamService.leaveTeamAll(agendaTeam); + agendaSlackService.slackCancelAgendaTeam(agendaTeam.getAgenda(), agendaTeam); } else { agendaTeam.agendaTeamStatusMustBeOpen(); agendaTeamService.leaveTeamMate(agendaTeam, user); + agendaSlackService.slackLeaveTeamMate(agendaTeam.getAgenda(), agendaTeam, user.getIntraId()); } return ResponseEntity.noContent().build(); } @@ -174,7 +183,10 @@ public ResponseEntity> confirmTeamList( @PostMapping("/join") public ResponseEntity attendTeamModify(@Parameter(hidden = true) @Login UserDto user, @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto, @RequestParam("agenda_key") UUID agendaKey) { - agendaTeamService.modifyAttendTeam(user, teamKeyReqDto, agendaKey); + Agenda agenda = agendaService.findAgendaByAgendaKey(agendaKey); + AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); + agendaTeamService.modifyAttendTeam(user, agendaTeam, agenda); + agendaSlackService.slackAttendTeamMate(agenda, agendaTeam, user.getIntraId()); return ResponseEntity.status(HttpStatus.CREATED).build(); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index c72fd7db1..7dcfb9ffe 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -22,6 +22,7 @@ import gg.agenda.api.user.agendateam.controller.response.TeamDetailsResDto; import gg.agenda.api.user.agendateam.controller.response.TeamKeyResDto; import gg.agenda.api.user.ticket.service.TicketService; +import gg.agenda.api.utils.SnsMessageUtil; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -40,12 +41,15 @@ import gg.utils.exception.custom.DuplicationException; import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.NotExistException; +import gg.utils.sns.MessageSender; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class AgendaTeamService { private final TicketService ticketService; + private final MessageSender messageSender; + private final SnsMessageUtil snsMessageUtil; private final AgendaRepository agendaRepository; private final TicketRepository ticketRepository; private final AgendaTeamRepository agendaTeamRepository; @@ -161,10 +165,7 @@ public TeamKeyResDto addAgendaTeam(UserDto user, TeamCreateReqDto teamCreateReqD * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 */ @Transactional - public void confirmTeam(UserDto user, UUID agendaKey, UUID teamKey) { - Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) - .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - + public AgendaTeam confirmTeam(UserDto user, Agenda agenda, UUID teamKey) { AgendaTeam agendaTeam = agendaTeamRepository .findByAgendaAndTeamKeyAndStatus(agenda, teamKey, OPEN, CONFIRM) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); @@ -177,7 +178,7 @@ public void confirmTeam(UserDto user, UUID agendaKey, UUID teamKey) { } agenda.confirmTeam(agendaTeam.getLocation(), LocalDateTime.now()); agendaTeam.confirm(); - agendaTeamRepository.save(agendaTeam); + return agendaTeamRepository.save(agendaTeam); } /** @@ -246,20 +247,12 @@ public List getCoalitionsFromAgendaTeam(AgendaTeam agendaTeam) { /** * 아젠다 팀 참여하기 - * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 */ @Transactional - public void modifyAttendTeam(UserDto user, TeamKeyReqDto teamKeyReqDto, UUID agendaKey) { + public void modifyAttendTeam(UserDto user, AgendaTeam agendaTeam, Agenda agenda) { AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) - .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - - AgendaTeam agendaTeam = agendaTeamRepository - .findByAgendaAndTeamKeyAndStatus(agenda, teamKeyReqDto.getTeamKey(), OPEN, CONFIRM) - .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); - agendaTeamProfileRepository.findByAgendaAndProfileAndIsExistTrue(agenda, agendaProfile) .ifPresent(profile -> { throw new ForbiddenException(AGENDA_TEAM_FORBIDDEN); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java b/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java new file mode 100644 index 000000000..70158dcad --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java @@ -0,0 +1,97 @@ +package gg.agenda.api.utils; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaTeam; +import gg.data.agenda.AgendaTeamProfile; +import gg.repo.agenda.AgendaTeamProfileRepository; +import gg.utils.sns.MessageSender; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaSlackService { + private final MessageSender messageSender; + private final SnsMessageUtil snsMessageUtil; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; + + public void slackAddAgendaAnnouncement(Agenda agenda, AgendaAnnouncement newAnnounce) { + List agendaTeamProfiles = agendaTeamProfileRepository.findAllByAgendaAndIsExistTrue(agenda); + String message = snsMessageUtil.addAgendaAnnouncementMessage(agenda, newAnnounce); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackCancelAgenda(Agenda agenda) { + List agendaTeamProfiles = agendaTeamProfileRepository.findAllByAgendaAndIsExistTrue(agenda); + String message = snsMessageUtil.cancelAgendaMessage(agenda); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackFinishAgenda(Agenda agenda) { + List agendaTeamProfiles = agendaTeamProfileRepository.findAllByAgendaAndIsExistTrue(agenda); + String message = snsMessageUtil.finishAgendaMessage(agenda); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackConfirmAgenda(Agenda agenda) { + List agendaTeamProfiles = agendaTeamProfileRepository.findAllByAgendaAndIsExistTrue(agenda); + String message = snsMessageUtil.confirmAgendaMessage(agenda); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackConfirmAgendaTeam(Agenda agenda, AgendaTeam newTeam) { + List agendaTeamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue( + newTeam); + String message = snsMessageUtil.confirmTeamMessage(agenda, newTeam); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + if (agenda.getMaxTeam() == agenda.getCurrentTeam()) { + String toHostMessage = snsMessageUtil.agendaHostMinTeamSatisfiedMessage(agenda); + messageSender.send(agenda.getHostIntraId(), toHostMessage); + } else if (agenda.getMinTeam() == agenda.getCurrentTeam()) { + String toHostMessage = snsMessageUtil.agendaHostMaxTeamSatisfiedMessage(agenda); + messageSender.send(agenda.getHostIntraId(), toHostMessage); + } + } + + public void slackCancelAgendaTeam(Agenda agenda, AgendaTeam newTeam) { + List agendaTeamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue( + newTeam); + String message = snsMessageUtil.cancelTeamMessage(agenda, newTeam); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackCancelByAgendaConfirm(Agenda agenda, List failTeam) { + List agendaTeamProfiles = agendaTeamProfileRepository.findByAgendaTeamInAndIsExistTrue( + failTeam); + String message = snsMessageUtil.failTeamMessage(agenda); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackAttendTeamMate(Agenda agenda, AgendaTeam agendaTeam, String userIntraId) { + List agendaTeamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue( + agendaTeam); + String message = snsMessageUtil.attendTeamMateMessage(agenda, agendaTeam, userIntraId); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .filter(intraId -> !intraId.equals(userIntraId)) + .forEach(intraId -> messageSender.send(intraId, message)); + } + + public void slackLeaveTeamMate(Agenda agenda, AgendaTeam agendaTeam, String userIntraId) { + List agendaTeamProfiles = agendaTeamProfileRepository.findByAgendaTeamAndIsExistTrue( + agendaTeam); + String message = snsMessageUtil.leaveTeamMateMessage(agenda, agendaTeam, userIntraId); + agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) + .forEach(intraId -> messageSender.send(intraId, message)); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java b/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java new file mode 100644 index 000000000..290355125 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java @@ -0,0 +1,91 @@ +package gg.agenda.api.utils; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaAnnouncement; +import gg.data.agenda.AgendaTeam; + +@Component +public class SnsMessageUtil { + private static final String URL = "https://gg.42seoul.kr/agenda/"; + private static final String SUBJECT = "행사요정🧚으로부터 도착한 편지"; + + public String addAgendaAnnouncementMessage(Agenda agenda, AgendaAnnouncement newAnnounce) { + String link = URL + "agenda_key=" + agenda.getAgendaKey() + "/announcement/" + newAnnounce.getId(); + return SUBJECT + + "\n" + agenda.getTitle() + "의 새로운 공지사항이 도착했습니다." + + "\n" + newAnnounce.getTitle() + + "\n" + "$$" + link + "$$"; + } + + public String confirmAgendaMessage(Agenda agenda) { + String link = URL + "agenda_key=" + agenda.getAgendaKey(); + return SUBJECT + + "\n" + agenda.getTitle() + "이 확정되었습니다." + + "\n" + "행사가 확정되었습니다. 시작일자와 장소를 확인해주세요!" + + "\n" + "$$" + link + "$$"; + } + + public String cancelAgendaMessage(Agenda agenda) { + return SUBJECT + + "\n" + agenda.getTitle() + "이 취소되었습니다." + + "\n" + "아쉽게도 행사가 취소되었습니다. 다음에 다시 만나요!"; + } + + public String finishAgendaMessage(Agenda agenda) { + String link = URL + "agenda_key=" + agenda.getAgendaKey(); + if (agenda.getIsRanking()) { + return SUBJECT + + "\n" + agenda.getTitle() + "이 종료되었습니다." + + "\n" + "행사가 성공적으로 종료되었습니다. 수고하셨습니다!" + + "\n" + "결과 확인 $$" + link + "$$"; + } else { + return SUBJECT + + "\n" + agenda.getTitle() + "이 종료되었습니다." + + "\n" + "행사가 성공적으로 종료되었습니다. 수고하셨습니다!"; + } + } + + public String confirmTeamMessage(Agenda agenda, AgendaTeam agendaTeam) { + return SUBJECT + + "\n" + agenda.getTitle() + "의" + agendaTeam.getName() + "팀이 확정되었습니다." + + "\n" + "행사 확정을 기다려주세요!"; + } + + public String cancelTeamMessage(Agenda agenda, AgendaTeam agendaTeam) { + return SUBJECT + + "\n" + agenda.getTitle() + "의" + agendaTeam.getName() + "팀이 취소되었습니다."; + } + + public String failTeamMessage(Agenda agenda) { + return SUBJECT + + "\n" + agenda.getTitle() + "의 팀이 취소되었습니다." + + "\n" + "행사가 확정되어 확정되지 않은 팀은 취소됩니다."; + } + + public String attendTeamMateMessage(Agenda agenda, AgendaTeam agendaTeam, String intraId) { + return SUBJECT + + "\n" + agenda.getTitle() + "의" + agendaTeam.getName() + "팀에" + intraId + "님이 참가했습니다."; + } + + public String leaveTeamMateMessage(Agenda agenda, AgendaTeam agendaTeam, String intraId) { + return SUBJECT + + "\n" + agenda.getTitle() + "의" + agendaTeam.getName() + "팀에서" + intraId + "님이 탈퇴했습니다."; + } + + public String agendaHostMinTeamSatisfiedMessage(Agenda agenda) { + return SUBJECT + + "\n" + agenda.getTitle() + "행사가 최소 팀 개수를 충족했습니다." + + "\n" + "행사를 확정할 수 있습니다." + + "\n" + "확정시엔 다른 팀들이 참가 할 수 없으니, 주의하세요!" + + "\n" + "$$" + URL + "agenda_key=" + agenda.getAgendaKey() + "$$"; + } + + public String agendaHostMaxTeamSatisfiedMessage(Agenda agenda) { + return SUBJECT + + "\n" + agenda.getTitle() + "행사가 최대 팀 개수를 충족했습니다." + + "\n" + "행사를 확정하고 진행 시간과 장소를 공지사항으로 전달해주세요." + + "\n" + "$$" + URL + "agenda_key=" + agenda.getAgendaKey() + "$$"; + } +} diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index ff6857651..dd9433a51 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -43,7 +43,6 @@ import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; -import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; @@ -341,7 +340,7 @@ void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { AgendaTeam updatedAgendaTeam = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) .orElseThrow(() -> new AssertionError("AgendaTeam not found")); List participants = agendaTeamProfileAdminRepository - .findAllByAgendaTeamAndIsExistIsTrue(updatedAgendaTeam); + .findAllByAgendaTeamAndIsExistIsTrue(updatedAgendaTeam); // then assertThat(updatedAgendaTeam.getName()).isEqualTo(updateDto.getTeamName()); @@ -535,4 +534,43 @@ void updateAgendaTeamAdminFailedWithInvalidIntraId() throws Exception { assertThat(result.getAwardPriority()).isEqualTo(team.getAwardPriority()); } } + + @Nested + @DisplayName("Admin AgendaTeam 취소") + class CancelAgendaTeamAdmin { + @Test + @DisplayName("Admin AgendaTeam 취소 성공") + void cancelAgendaTeamAdminSuccess() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + // when + mockMvc.perform(patch("/agenda/admin/team/cancel") + .header("Authorization", "Bearer " + accessToken) + .param("team_key", team.getTeamKey().toString())) + .andExpect(status().isNoContent()); + AgendaTeam result = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + + // then + assertThat(result.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); + assertThat(agendaTeamProfileAdminRepository + .findAllByAgendaTeamAndIsExistIsTrue(result).size()).isEqualTo(0); + } + + @Test + @DisplayName("Admin AgendaTeam 취소 실패 - 존재하지 않는 Team Key") + void cancelAgendaTeamAdminFailedWithInvalidTeamKey() throws Exception { + // given + // expected + mockMvc.perform(patch("/agenda/admin/team/cancel") + .header("Authorization", "Bearer " + accessToken) + .param("team_key", UUID.randomUUID().toString())) + .andExpect(status().isNotFound()); + } + } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index 8f972d817..eaeec8f1e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -1475,7 +1475,7 @@ public void notValidTeamStatus() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString()) .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); + .andExpect(status().isBadRequest()); } @Test diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index 5cc557360..c740ed875 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -114,6 +114,11 @@ public void cancelTeam(AgendaTeamStatus status) { this.mateCount = 0; } + public void adminCancelTeam() { + this.status = CANCEL; + this.mateCount = 0; + } + public void leaveTeamMate() { this.mateCount--; } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index b41f2f702..f24a1555e 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -47,4 +47,9 @@ public interface AgendaTeamProfileRepository extends JpaRepository findByProfileAndIsExistTrueAndAgendaStatus( @Param("agendaProfile") AgendaProfile agendaProfile, @Param("status") AgendaStatus status, Pageable pageable); + + List findAllByAgendaAndIsExistTrue(Agenda agenda); + + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam IN :agendaTeams AND atp.isExist = true") + List findByAgendaTeamInAndIsExistTrue(List agendaTeams); } From 9dc51966365372a0aa807fcb1bb011f02f94ec3e Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:37:17 +0900 Subject: [PATCH 086/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Auth=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EB=90=9C=20=EB=B6=80=EB=B6=84=20Cookie=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#980=20(#981)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 10 ++- .../service/IntraProfileUtils.java | 12 ++- .../controller/AgendaAdminControllerTest.java | 2 - .../AgendaProfileControllerTest.java | 11 ++- .../main/java/gg/auth/FortyTwoAuthUtil.java | 4 + .../java/gg/utils/exception/ErrorCode.java | 79 +++++++++---------- 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 0e5755efe..4f3abac15 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import org.springframework.data.domain.Page; @@ -73,10 +74,10 @@ public ResponseEntity myAgendaProfileInfoDetails */ @GetMapping public ResponseEntity myAgendaProfileDetails( - @Login @Parameter(hidden = true) UserDto user) { + @Login @Parameter(hidden = true) UserDto user, HttpServletResponse response) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); int ticketCount = ticketService.findTicketList(profile).size(); - IntraProfile intraProfile = intraProfileUtils.getIntraProfile(); + IntraProfile intraProfile = intraProfileUtils.getIntraProfile(response); MyAgendaProfileDetailsResDto agendaProfileDetails = MyAgendaProfileDetailsResDto.toDto( profile, ticketCount, intraProfile); return ResponseEntity.ok(agendaProfileDetails); @@ -109,9 +110,10 @@ public ResponseEntity> getCurrentAttendAgend } @GetMapping("/{intraId}") - public ResponseEntity agendaProfileDetails(@PathVariable String intraId) { + public ResponseEntity agendaProfileDetails(@PathVariable String intraId, + HttpServletResponse response) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(intraId); - IntraProfile intraProfile = intraProfileUtils.getIntraProfile(intraId); + IntraProfile intraProfile = intraProfileUtils.getIntraProfile(intraId, response); AgendaProfileDetailsResDto resDto = AgendaProfileDetailsResDto.toDto(profile, intraProfile); return ResponseEntity.ok(resDto); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java index 6e7c39e95..610c16db5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Objects; +import javax.servlet.http.HttpServletResponse; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; @@ -14,6 +16,7 @@ import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfileResponse; import gg.auth.FortyTwoAuthUtil; +import gg.utils.cookie.CookieUtil; import gg.utils.exception.custom.AuthenticationException; import gg.utils.external.ApiUtil; import lombok.RequiredArgsConstructor; @@ -31,7 +34,9 @@ public class IntraProfileUtils { private final ApiUtil apiUtil; - public IntraProfile getIntraProfile() { + private final CookieUtil cookieUtil; + + public IntraProfile getIntraProfile(HttpServletResponse response) { try { IntraProfileResponse intraProfileResponse = requestIntraProfile(INTRA_PROFILE_URL); intraProfileResponseValidation(intraProfileResponse); @@ -40,11 +45,12 @@ public IntraProfile getIntraProfile() { return new IntraProfile(intraImage.getLink(), intraAchievements); } catch (Exception e) { log.error("42 Intra Profile API 호출 실패", e); + cookieUtil.deleteCookie(response, "refresh_token"); throw new AuthenticationException(AUTH_NOT_FOUND); } } - public IntraProfile getIntraProfile(String intraId) { + public IntraProfile getIntraProfile(String intraId, HttpServletResponse response) { try { IntraProfileResponse intraProfileResponse = requestIntraProfile(INTRA_USERS_URL + intraId); intraProfileResponseValidation(intraProfileResponse); @@ -53,6 +59,7 @@ public IntraProfile getIntraProfile(String intraId) { return new IntraProfile(intraImage.getLink(), intraAchievements); } catch (Exception e) { log.error("42 Intra Profile API 호출 실패", e); + cookieUtil.deleteCookie(response, "refresh_token"); throw new AuthenticationException(AUTH_NOT_FOUND); } } @@ -71,7 +78,6 @@ private IntraProfileResponse requestIntraProfile(String requestUrl) { } } - private void intraProfileResponseValidation(IntraProfileResponse intraProfileResponse) { if (Objects.isNull(intraProfileResponse)) { throw new AuthenticationException(AUTH_NOT_FOUND); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index a05f53654..d07adb842 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -41,11 +41,9 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; import gg.data.user.User; -import gg.utils.AgendaTestDataUtils; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; -import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index c04c000fc..a01fc39ec 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -2,6 +2,7 @@ import static gg.data.agenda.type.Location.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -10,6 +11,7 @@ import java.util.ArrayList; import java.util.List; +import javax.servlet.http.HttpServletResponse; import javax.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; @@ -89,7 +91,8 @@ void test() throws Exception { //given URL url = new URL("http://localhost:8080"); IntraProfile intraProfile = new IntraProfile(url, List.of()); - Mockito.when(intraProfileUtils.getIntraProfile()).thenReturn(intraProfile); + Mockito.when(intraProfileUtils.getIntraProfile(any(HttpServletResponse.class))) + .thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); // when @@ -146,9 +149,10 @@ void getAgendaProfileSuccess() throws Exception { //given URL url = new URL("http://localhost:8080"); IntraProfile intraProfile = new IntraProfile(url, List.of()); - Mockito.when(intraProfileUtils.getIntraProfile(user.getIntraId())).thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); + Mockito.when(intraProfileUtils.getIntraProfile(any(String.class), any(HttpServletResponse.class))) + .thenReturn(intraProfile); // when String response = mockMvc.perform(get("/agenda/profile/" + user.getIntraId()) .header("Authorization", "Bearer " + accessToken)) @@ -171,7 +175,8 @@ void getAgendaProfileFailedWithInvalidIntraId() throws Exception { //given URL url = new URL("http://localhost:8080"); IntraProfile intraProfile = new IntraProfile(url, List.of()); - Mockito.when(intraProfileUtils.getIntraProfile()).thenReturn(intraProfile); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + Mockito.when(intraProfileUtils.getIntraProfile(res)).thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); diff --git a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java index e0f851f4d..b83615326 100644 --- a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java +++ b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java @@ -25,6 +25,7 @@ import gg.utils.exception.ErrorCode; import gg.utils.exception.custom.NotExistException; +import gg.utils.exception.user.TokenNotValidException; import gg.utils.external.ApiUtil; import lombok.RequiredArgsConstructor; @@ -37,6 +38,9 @@ public class FortyTwoAuthUtil { public String getAccessToken() { Authentication authentication = getAuthenticationFromContext(); OAuth2AuthorizedClient client = getClientFromAuthentication(authentication); + if (Objects.isNull(client)) { + throw new TokenNotValidException(); + } return client.getAccessToken().getTokenValue(); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 0d3f14e08..b3c68558c 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -193,46 +193,45 @@ public enum ErrorCode { INVALID_CHECKLIST(400, "RE001", "잘못된 요청 데이터입니다."), // agenda - AUTH_NOT_FOUND(404, "AG", "42 정보가 만료되었습니다."), - - AGENDA_NOT_FOUND(404, "AG", "해당 일정이 존재하지 않습니다."), - AGENDA_CREATE_FAILED(500, "AG", "일정 생성에 실패했습니다."), - AGENDA_UPDATE_FAILED(500, "AG", "일정 수정에 실패했습니다."), - AGENDA_NOT_OPEN(400, "AG", "마감된 일정에는 팀을 생성할 수 없습니다."), - AGENDA_TEAM_ALREADY_CONFIRM(400, "AG", "이미 확정된 팀입니다."), - AGENDA_TEAM_ALREADY_CANCEL(400, "AG", "이미 취소된 팀입니다."), - NOT_ENOUGH_TEAM_MEMBER(400, "AG", "팀원이 부족합니다."), - AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG", "공지사항이 존재하지 않습니다."), - AGENDA_TEAM_FULL(400, "AG", "팀이 꽉 찼습니다."), - AGENDA_NO_CAPACITY(403, "AG", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), - AGENDA_POSTER_SIZE_TOO_LARGE(400, "AG", "포스터 사이즈가 너무 큽니다."), - AGENDA_INVALID_SCHEDULE(400, "AG", "유효하지 않은 일정입니다."), - AGENDA_INVALID_PARAM(400, "AG", "유효하지 않은 파라미터입니다."), - UPDATE_LOCATION_NOT_VALID(400, "AG", "지역을 변경할 수 없습니다."), - AGENDA_CAPACITY_CONFLICT(409, "AG", "팀 제한을 변경할 수 없습니다."), - AGENDA_TEAM_CAPACITY_CONFLICT(409, "AG", "팀 인원 제한을 변경할 수 없습니다."), - HOST_FORBIDDEN(403, "AG", "개최자는 팀을 생성할 수 없습니다."), - TEAM_LEADER_FORBIDDEN(403, "AG", "팀장만 팀을 확정할 수 있습니다."), - CONFIRM_FORBIDDEN(403, "AG", "개최자만 일정을 종료할 수 있습니다."), - AGENDA_MODIFICATION_FORBIDDEN(403, "AG", "개최자만 일정을 수정할 수 있습니다."), - LOCATION_NOT_VALID(400, "AG", "유효하지 않은 지역입니다."), - AGENDA_TEAM_FORBIDDEN(403, "AG", "일정에 참여한 팀이 있습니다."), - NOT_TEAM_MATE(403, "AG", "팀원이 아닙니다."), - TEAM_NAME_EXIST(409, "AG", "이미 존재하는 팀 이름입니다."), - TICKET_NOT_EXIST(403, "AG", "보유한 티켓이 부족합니다."), - TICKET_NOT_FOUND(404, "AG", "해당 티켓이 존재하지 않습니다."), - AGENDA_TEAM_NOT_FOUND(404, "AG", "팀이 존재하지 않습니다."), - AGENDA_PROFILE_NOT_FOUND(404, "AG", "프로필이 존재하지 않습니다."), - ALREADY_TICKET_SETUP(409, "AG", "이미 티켓 신청이 되어있습니다."), - NOT_SETUP_TICKET(404, "AG", "티켓 신청이 되어있지 않습니다."), - POINT_HISTORY_NOT_FOUND(404, "AG", "기부 내역이 존재하지 않습니다."), - TICKET_FORBIDDEN(403, "AG", "티켓 신청은 1분의 대기시간이 있습니다."), - AGENDA_ALREADY_FINISHED(409, "AG", "이미 종료된 일정입니다."), - AGENDA_ALREADY_CONFIRMED(409, "AG", "이미 확정된 일정입니다."), - AGENDA_ALREADY_CANCELED(409, "AG", "이미 취소된 일정입니다."), - AGENDA_DOES_NOT_CONFIRM(409, "AG", "확정되지 않은 일정입니다."), - AGENDA_AWARD_EMPTY(400, "AG", "시상 정보가 없습니다."), - AGENDA_AWARD_PRIORITY_DUPLICATE(400, "AG", "시상 우선순위가 중복됩니다."); + AGENDA_TEAM_FULL(400, "AG101", "팀이 꽉 찼습니다."), + LOCATION_NOT_VALID(400, "AG102", "유효하지 않은 지역입니다."), + AGENDA_AWARD_EMPTY(400, "AG103", "시상 정보가 없습니다."), + AGENDA_INVALID_PARAM(400, "AG104", "유효하지 않은 파라미터입니다."), + AGENDA_NOT_OPEN(400, "AG105", "마감된 일정에는 팀을 생성할 수 없습니다."), + AGENDA_CREATE_FAILED(400, "AG106", "일정 생성에 실패했습니다."), + AGENDA_UPDATE_FAILED(400, "AG107", "일정 수정에 실패했습니다."), + AGENDA_INVALID_SCHEDULE(400, "AG108", "유효하지 않은 일정입니다."), + NOT_ENOUGH_TEAM_MEMBER(400, "AG109", "팀원이 부족합니다."), + UPDATE_LOCATION_NOT_VALID(400, "AG110", "지역을 변경할 수 없습니다."), + AGENDA_TEAM_ALREADY_CANCEL(400, "AG111", "이미 취소된 팀입니다."), + AGENDA_TEAM_ALREADY_CONFIRM(400, "AG112", "이미 확정된 팀입니다."), + AGENDA_POSTER_SIZE_TOO_LARGE(400, "AG113", "포스터 사이즈가 너무 큽니다."), + AGENDA_AWARD_PRIORITY_DUPLICATE(400, "AG114", "시상 우선순위가 중복됩니다."), + AGENDA_NO_CAPACITY(403, "AG201", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), + HOST_FORBIDDEN(403, "AG202", "개최자는 팀을 생성할 수 없습니다."), + TICKET_NOT_EXIST(403, "AG203", "보유한 티켓이 부족합니다."), + NOT_TEAM_MATE(403, "AG204", "팀원이 아닙니다."), + CONFIRM_FORBIDDEN(403, "AG205", "개최자만 일정을 종료할 수 있습니다."), + TICKET_FORBIDDEN(403, "AG206", "티켓 신청은 1분의 대기시간이 있습니다."), + TEAM_LEADER_FORBIDDEN(403, "AG207", "팀장만 팀을 확정할 수 있습니다."), + AGENDA_TEAM_FORBIDDEN(403, "AG208", "일정에 참여한 팀이 있습니다."), + AGENDA_MODIFICATION_FORBIDDEN(403, "AG209", "개최자만 일정을 수정할 수 있습니다."), + AUTH_NOT_FOUND(404, "AG301", "42 정보가 만료되었습니다."), + TICKET_NOT_FOUND(404, "AG302", "해당 티켓이 존재하지 않습니다."), + AGENDA_NOT_FOUND(404, "AG303", "해당 일정이 존재하지 않습니다."), + NOT_SETUP_TICKET(404, "AG304", "티켓 신청이 되어있지 않습니다."), + AGENDA_TEAM_NOT_FOUND(404, "AG305", "팀이 존재하지 않습니다."), + AGENDA_PROFILE_NOT_FOUND(404, "AG306", "프로필이 존재하지 않습니다."), + POINT_HISTORY_NOT_FOUND(404, "AG307", "기부 내역이 존재하지 않습니다."), + AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG308", "공지사항이 존재하지 않습니다."), + TEAM_NAME_EXIST(409, "AG401", "이미 존재하는 팀 이름입니다."), + ALREADY_TICKET_SETUP(409, "AG402", "이미 티켓 신청이 되어있습니다."), + AGENDA_DOES_NOT_CONFIRM(409, "AG403", "확정되지 않은 일정입니다."), + AGENDA_ALREADY_FINISHED(409, "AG404", "이미 종료된 일정입니다."), + AGENDA_ALREADY_CANCELED(409, "AG405", "이미 취소된 일정입니다."), + AGENDA_ALREADY_CONFIRMED(409, "AG406", "이미 확정된 일정입니다."), + AGENDA_CAPACITY_CONFLICT(409, "AG407", "팀 제한을 변경할 수 없습니다."), + AGENDA_TEAM_CAPACITY_CONFLICT(409, "AG408", "팀 인원 제한을 변경할 수 없습니다."); private final int status; private final String errCode; From bf6fc925eb1ece30bb72b7ed04c089f65ce2c3c1 Mon Sep 17 00:00:00 2001 From: jkim3 <62086003+kimjieun0301@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:03:39 +0900 Subject: [PATCH 087/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Admin=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=20=EC=A1=B0=ED=9A=8C=20API=20#945=20(#982)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repo/agenda/TicketAdminRepository.java | 4 + .../agenda/service/AgendaAdminService.java | 6 + .../controller/TicketAdminController.java | 55 +++++ .../controller/response/TicketFindResDto.java | 61 +++++ .../service/TicketAdminFindService.java | 31 +++ .../user/agenda/service/AgendaService.java | 6 + .../ticket/controller/TicketController.java | 19 +- .../user/ticket/service/TicketService.java | 21 -- .../ticket/TicketAdminControllerTest.java | 211 +++++++++++++++++- 9 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketFindResDto.java create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminFindService.java diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java index b5a225c98..ed49c744e 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/TicketAdminRepository.java @@ -2,6 +2,8 @@ import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +13,6 @@ @Repository public interface TicketAdminRepository extends JpaRepository { Optional findByAgendaProfile(AgendaProfile agendaProfile); + + Page findByAgendaProfile(AgendaProfile agendaProfile, Pageable pageable); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index e1d610e2c..3b837d276 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -6,6 +6,7 @@ import java.net.URL; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -80,4 +81,9 @@ public List getAgendaSimpleList() { .map(AgendaAdminSimpleResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); } + + @Transactional(readOnly = true) + public Optional getAgenda(UUID agendaKey) { + return agendaAdminRepository.findByAgendaKey(agendaKey); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java index 6ee0cda2b..73d3675b9 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/TicketAdminController.java @@ -1,20 +1,37 @@ package gg.agenda.api.admin.ticket.controller; +import java.util.List; +import java.util.stream.Collectors; + import javax.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; +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.RequestParam; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.admin.agenda.service.AgendaAdminService; import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; import gg.agenda.api.admin.ticket.controller.request.TicketChangeAdminReqDto; import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; +import gg.agenda.api.admin.ticket.controller.response.TicketFindResDto; +import gg.agenda.api.admin.ticket.service.TicketAdminFindService; import gg.agenda.api.admin.ticket.service.TicketAdminService; +import gg.data.agenda.Agenda; +import gg.data.agenda.Ticket; +import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import lombok.RequiredArgsConstructor; @RestController @@ -22,6 +39,8 @@ @RequestMapping("/agenda/admin/ticket") public class TicketAdminController { private final TicketAdminService ticketAdminService; + private final TicketAdminFindService ticketAdminFindService; + private final AgendaAdminService agendaAdminService; /** * 티켓 설정 추가 @@ -49,4 +68,40 @@ public ResponseEntity agendaProfileModify( ticketAdminService.modifyTicket(ticketId, ticketChangeAdminReqDto); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + /** + * 티켓 목록 조회하는 메서드 + * @param pageRequest 페이지네이션 요청 정보 + */ + @GetMapping("/list/{intraId}") + public ResponseEntity> getTicketList( + @PathVariable String intraId, @ModelAttribute @Valid PageRequestDto pageRequest) { + int page = pageRequest.getPage(); + int size = pageRequest.getSize(); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending()); + + Page ticketList = ticketAdminFindService.findTicket(intraId, pageable); + + List ticketFindResDto = ticketList.stream() + .map(ticket -> { + TicketFindResDto dto = new TicketFindResDto(ticket); + + if (dto.getIssuedFromKey() != null) { + Agenda agendaIssuedFrom = agendaAdminService.getAgenda(dto.getIssuedFromKey()).orElse(null); + dto.changeIssuedFrom(agendaIssuedFrom); + } + + if (dto.getUsedToKey() != null) { + Agenda agendaUsedTo = agendaAdminService.getAgenda(dto.getUsedToKey()).orElse(null); + dto.changeUsedTo(agendaUsedTo); + } + + return dto; + }) + .collect(Collectors.toList()); + + PageResponseDto pageResponseDto = PageResponseDto.of( + ticketList.getTotalElements(), ticketFindResDto); + return ResponseEntity.ok(pageResponseDto); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketFindResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketFindResDto.java new file mode 100644 index 000000000..9be123d29 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/controller/response/TicketFindResDto.java @@ -0,0 +1,61 @@ +package gg.agenda.api.admin.ticket.controller.response; + +import java.time.LocalDateTime; +import java.util.UUID; + +import gg.data.agenda.Agenda; +import gg.data.agenda.Ticket; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class TicketFindResDto { + private Long ticketId; + private LocalDateTime createdAt; + private String issuedFrom; + private UUID issuedFromKey; + private String usedTo; + private UUID usedToKey; + private Boolean isApproved; + private LocalDateTime approvedAt; + private Boolean isUsed; + private LocalDateTime usedAt; + + public TicketFindResDto(Ticket ticket) { + this.ticketId = ticket.getId(); + this.createdAt = ticket.getCreatedAt(); + this.issuedFrom = "42Intra"; + this.issuedFromKey = ticket.getIssuedFrom(); + if (ticket.getIsApproved()) { + this.usedTo = "NotUsed"; + } else { + this.usedTo = "NotApproved"; + } + this.usedToKey = ticket.getUsedTo(); + this.isApproved = ticket.getIsApproved(); + this.approvedAt = ticket.getApprovedAt(); + this.isUsed = ticket.getIsUsed(); + this.usedAt = ticket.getUsedAt(); + } + + public void changeIssuedFrom(Agenda agenda) { + if (agenda == null) { + this.issuedFrom = "42Intra"; + return; + } + this.issuedFrom = agenda.getTitle(); + } + + public void changeUsedTo(Agenda agenda) { + if (agenda == null && !this.isApproved) { + this.usedTo = "NotApproved"; + return; + } + if (agenda == null) { + this.usedTo = "NotUsed"; + return; + } + this.usedTo = agenda.getTitle(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminFindService.java new file mode 100644 index 000000000..243a146e1 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/ticket/service/TicketAdminFindService.java @@ -0,0 +1,31 @@ +package gg.agenda.api.admin.ticket.service; + +import static gg.utils.exception.ErrorCode.*; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.admin.repo.agenda.AgendaAdminRepository; +import gg.admin.repo.agenda.TicketAdminRepository; +import gg.data.agenda.AgendaProfile; +import gg.data.agenda.Ticket; +import gg.repo.agenda.AgendaProfileRepository; +import gg.utils.exception.custom.NotExistException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TicketAdminFindService { + private final AgendaProfileRepository agendaProfileRepository; + private final TicketAdminRepository ticketAdminRepository; + private final AgendaAdminRepository agendaAdminRepository; + + @Transactional(readOnly = true) + public Page findTicket(String intraId, Pageable pageable) { + AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) + .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + return ticketAdminRepository.findByAgendaProfile(agendaProfile, pageable); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 1ceeb7494..66bbd57b8 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -6,6 +6,7 @@ import java.net.URL; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -133,4 +134,9 @@ public void cancelAgenda(Agenda agenda) { attendTeams.forEach(agendaTeamService::leaveTeamAll); agenda.cancelAgenda(); } + + @Transactional(readOnly = true) + public Optional getAgenda(UUID agendaKey) { + return agendaRepository.findByAgendaKey(agendaKey); + } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 5e30ce829..0a5b8f680 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -21,12 +21,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agenda.service.AgendaService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.utils.cookie.CookieUtil; @@ -44,6 +46,7 @@ public class TicketController { private final CookieUtil cookieUtil; private final TicketService ticketService; private final AgendaProfileFindService agendaProfileFindService; + private final AgendaService agendaService; /** * 티켓 설정 추가 @@ -104,7 +107,21 @@ public ResponseEntity> ticketHistoryList( Page tickets = ticketService.findTicketsByUserId(user.getId(), pageable); List ticketDtos = tickets.stream() - .map(ticketService::convertAgendaKeyToTitleWhereIssuedFromAndUsedTo) + .map(ticket -> { + TicketHistoryResDto dto = new TicketHistoryResDto(ticket); + + if (dto.getIssuedFromKey() != null) { + Agenda agendaIssuedFrom = agendaService.getAgenda(dto.getIssuedFromKey()).orElse(null); + dto.changeIssuedFrom(agendaIssuedFrom); + } + + if (dto.getUsedToKey() != null) { + Agenda agendaUsedTo = agendaService.getAgenda(dto.getUsedToKey()).orElse(null); + dto.changeUsedTo(agendaUsedTo); + } + + return dto; + }) .collect(Collectors.toList()); PageResponseDto pageResponseDto = PageResponseDto.of( diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 371d86b73..11cd7fde7 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -18,10 +18,8 @@ import org.springframework.web.client.HttpClientErrorException; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; -import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.auth.FortyTwoAuthUtil; import gg.auth.UserDto; -import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; @@ -160,23 +158,4 @@ public Page findTicketsByUserId(Long userId, Pageable pageable) { .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); return ticketRepository.findByAgendaProfileId(profile.getId(), pageable); } - - /** - * 티켓 이력 조회 - */ - @Transactional(readOnly = true) - public TicketHistoryResDto convertAgendaKeyToTitleWhereIssuedFromAndUsedTo(Ticket ticket) { - TicketHistoryResDto dto = new TicketHistoryResDto(ticket); - if (dto.getIssuedFromKey() != null) { - Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getIssuedFromKey()) - .orElse(null); - dto.changeIssuedFrom(agenda); - } - if (dto.getUsedToKey() != null) { - Agenda agenda = agendaRepository.findAgendaByAgendaKey(dto.getUsedToKey()) - .orElse(null); - dto.changeUsedTo(agenda); - } - return dto; - } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java index 60a35189b..9f662b6fd 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/ticket/TicketAdminControllerTest.java @@ -1,10 +1,13 @@ package gg.agenda.api.admin.ticket; -import static org.assertj.core.api.AssertionsForClassTypes.*; +import static gg.data.agenda.type.Location.*; +import static org.assertj.core.api.Assertions.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import javax.transaction.Transactional; @@ -13,17 +16,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import gg.admin.repo.agenda.TicketAdminRepository; import gg.agenda.api.admin.ticket.controller.request.TicketAddAdminReqDto; import gg.agenda.api.admin.ticket.controller.request.TicketChangeAdminReqDto; import gg.agenda.api.admin.ticket.controller.response.TicketAddAdminResDto; +import gg.agenda.api.admin.ticket.controller.response.TicketFindResDto; +import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; @@ -33,6 +41,8 @@ import gg.data.user.type.RoleType; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; +import gg.utils.dto.PageRequestDto; +import gg.utils.dto.PageResponseDto; import gg.utils.fixture.agenda.AgendaFixture; import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.TicketFixture; @@ -220,4 +230,203 @@ void updateTicketWithInvalidUsedToKey() throws Exception { .andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("티켓 목록 조회 테스트") + class GetTicketList { + + @BeforeEach + void beforeEach() { + user = testDataUtils.createNewAdminUser(RoleType.ADMIN); + accessToken = testDataUtils.getLoginAccessTokenFromUser(user); + agendaProfile = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3}) + @DisplayName("200 모든 티켓 목록 조회 성공") + void getAllTicketsSuccess(int page) throws Exception { + // Given + int size = 10; + int total = 25; + List tickets = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tickets.add(ticketFixture.createTicket(agendaProfile)); + } + tickets.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId())); + + PageRequestDto pageRequest = new PageRequestDto(page, size); + String request = objectMapper.writeValueAsString(pageRequest); + + // When + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // Then + int expectedSize = Math.min(size, total - (page - 1) * size); + assertThat(result).hasSize(expectedSize); + + for (int i = 0; i < result.size(); i++) { + TicketFindResDto actual = result.get(i); + + int ticketIndex = (page - 1) * size + i; + if (ticketIndex >= tickets.size()) { + break; + } + Ticket expected = tickets.get(ticketIndex); + + // 검증 + assertThat(actual.getTicketId()).isEqualTo(expected.getId()); + assertThat(actual.getCreatedAt()).isEqualTo(expected.getCreatedAt()); + assertThat(actual.getIsApproved()).isEqualTo(expected.getIsApproved()); + assertThat(actual.getApprovedAt()).isEqualTo(expected.getApprovedAt()); + assertThat(actual.getIsUsed()).isEqualTo(expected.getIsUsed()); + assertThat(actual.getUsedAt()).isEqualTo(expected.getUsedAt()); + } + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있지 않은 경우") + void findTicketHistorySuccessToNotApprove() throws Exception { + //given + ticketFixture.createTicket(agendaProfile, false, false, null, null); + PageRequestDto req = new PageRequestDto(1, 5); + //when + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotApproved"); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있고 used 되어있는 경우") + void findTicketHistorySuccessToUsed() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(agendaProfile, true, true, null, + seoulAgenda.getAgendaKey()); + PageRequestDto req = new PageRequestDto(1, 5); + //when + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo(seoulAgenda.getTitle()); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - approve 되어있고 used 되어있지 않은 경우") + void findTicketHistorySuccessToNotUsed() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(agendaProfile, true, false, null, + null); + PageRequestDto req = new PageRequestDto(1, 5); + String content = objectMapper.writeValueAsString(req); + //when + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo("42Intra"); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotUsed"); + } + + @Test + @DisplayName("200 티켓 히스토리 조회 성공 - refund 되어있고 used 되어있지 않은 경우") + void findTicketHistorySuccessToRefund() throws Exception { + //given + Agenda seoulAgenda = agendaFixture.createAgenda(SEOUL); + Ticket ticket = ticketFixture.createTicket(agendaProfile, true, false, seoulAgenda.getAgendaKey(), + null); + PageRequestDto req = new PageRequestDto(1, 5); + //when + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", String.valueOf(req.getPage())) + .param("size", String.valueOf(req.getSize()))) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getIssuedFrom()).isEqualTo(seoulAgenda.getTitle()); + assertThat(result.get(0).getUsedTo()).isEqualTo("NotUsed"); + } + + @Test + @DisplayName("200 티켓이 없는 경우") + void getAllTicketsEmpty() throws Exception { + //Given + PageRequestDto req = new PageRequestDto(1, 5); + // When + String res = mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", "1") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { + }); + List result = pageResponseDto.getContent(); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("400 유효하지 않은 페이지 요청") + void getAllTicketsInvalidPageRequest() throws Exception { + // Given + // When & Then + mockMvc.perform( + get("/agenda/admin/ticket/list/" + user.getIntraId()) + .header("Authorization", "Bearer " + accessToken) + .param("page", "-1") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + } } From bf81695da64e1ed61ddbab51d791d7dc94803d0c Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:18:43 +0900 Subject: [PATCH 088/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20GET=20/agenda/te?= =?UTF-8?q?am/my=20=EB=82=B4=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=EC=84=9C=20NonUniqueResultException=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=20#983=20(#985)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: seungsje --- .../repo/agenda/AgendaTeamProfileAdminRepository.java | 8 ++++++++ .../admin/agendateam/service/AgendaTeamAdminService.java | 5 +++++ .../gg/agenda/api/user/agenda/service/AgendaService.java | 7 +++++-- .../api/user/agendateam/service/AgendaTeamService.java | 2 +- .../api/user/agenda/controller/AgendaControllerTest.java | 2 +- .../agenda/api/user/agenda/service/AgendaServiceTest.java | 2 -- .../java/gg/repo/agenda/AgendaTeamProfileRepository.java | 2 +- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java index 33b0336b6..898d9eb91 100644 --- a/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java +++ b/gg-admin-repo/src/main/java/gg/admin/repo/agenda/AgendaTeamProfileAdminRepository.java @@ -1,10 +1,14 @@ package gg.admin.repo.agenda; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; @@ -12,4 +16,8 @@ public interface AgendaTeamProfileAdminRepository extends JpaRepository { List findAllByAgendaTeamAndIsExistIsTrue(AgendaTeam agendaTeam); + + @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agenda = :agenda AND atp.profile = :agendaProfile " + + "AND atp.isExist = true") + Optional findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index 0c90a5bfe..b28db45c3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -21,6 +21,7 @@ import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; import gg.data.agenda.AgendaTeamProfile; +import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; @@ -86,6 +87,10 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { .forEach(intraId -> { AgendaProfile profile = agendaProfileAdminRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); + agendaTeamProfileAdminRepository.findByAgendaAndProfileAndIsExistTrue(team.getAgenda(), profile) + .ifPresent(agendaTeamProfile -> { + throw new ForbiddenException(AGENDA_TEAM_FORBIDDEN); + }); team.attendTeamAdmin(team.getAgenda()); AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(team, team.getAgenda(), profile); agendaTeamProfileAdminRepository.save(agendaTeamProfile); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 66bbd57b8..a8b3328d5 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -6,6 +6,7 @@ import java.net.URL; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -81,8 +82,10 @@ private Comparator agendaComparatorWithIsOfficialThenDeadline() { @Transactional public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster, UserDto user) { try { - URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, createDto.getAgendaTitle(), defaultUri); - createDto.updatePosterUri(storedUrl); + if (Objects.nonNull(agendaPoster)) { + URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, createDto.getAgendaTitle(), defaultUri); + createDto.updatePosterUri(storedUrl); + } Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(createDto, user.getIntraId()); return agendaRepository.save(newAgenda); } catch (IOException e) { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index 7dcfb9ffe..a5cf04933 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -68,7 +68,7 @@ public Optional detailsMyTeamSimple(UserDto user, UUID agend AgendaProfile agendaProfile = agendaProfileRepository.findByUserId(user.getId()) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - Optional agendaTeam = agendaTeamProfileRepository.findByAgendaProfileAndIsExistTrue(agenda, + Optional agendaTeam = agendaTeamProfileRepository.findByAgendaAndAgendaProfileAndIsExistTrue(agenda, agendaProfile) .map(AgendaTeamProfile::getAgendaTeam); if (agendaTeam.isEmpty()) { diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 8bbaafc00..55f0db099 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -304,7 +304,7 @@ void createAgendaSuccess() throws Exception { assertThat(agenda.getTitle()).isEqualTo(dto.getAgendaTitle()); assertThat(agenda.getContent()).isEqualTo(dto.getAgendaContent()); assertThat(agenda.getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); - assertThat(agenda.getPosterUri()).isEqualTo(defaultUri); + assertThat(agenda.getPosterUri()).isNull(); } @Test diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 498359926..1461a2149 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -164,14 +164,12 @@ void createAgendaSuccess() throws IOException { UserDto user = UserDto.builder().intraId("intraId").build(); Agenda agenda = Agenda.builder().build(); when(agendaRepository.save(any())).thenReturn(agenda); - when(imageHandler.uploadImageOrDefault(any(), any(), any())).thenReturn(new URL("http://localhost")); // when Agenda result = agendaService.addAgenda(agendaCreateReqDto, null, user); // then verify(agendaRepository, times(1)).save(any()); - verify(imageHandler, times(1)).uploadImageOrDefault(any(), any(), any()); assertThat(result).isEqualTo(agenda); } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index f24a1555e..f2bdc5972 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -18,7 +18,7 @@ public interface AgendaTeamProfileRepository extends JpaRepository { @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam.agenda = :agenda " + "AND atp.profile = :agendaProfile AND atp.isExist = true") - Optional findByAgendaProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); + Optional findByAgendaAndAgendaProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") List findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); From b6da492bae1388bced609dbe1d712fc2e8d409fa Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:34:35 +0900 Subject: [PATCH 089/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20Agenda?= =?UTF-8?q?=20History=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20CONFIRM=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=B6=94=EA=B0=80=20#988=20(#990)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agenda/api/user/agenda/service/AgendaService.java | 2 +- .../gg/agenda/api/user/agenda/service/AgendaServiceTest.java | 5 +++-- gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index a8b3328d5..91cde141c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -96,7 +96,7 @@ public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster @Transactional(readOnly = true) public Page findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, pageable); + return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, AgendaStatus.CONFIRM, pageable); } @Transactional diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 1461a2149..8989c0742 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -191,7 +191,8 @@ void getAgendaListHistorySuccess() { .build() )); Page agendaPage = new PageImpl<>(agendas.subList(0, 10), pageable, size); - when(agendaRepository.findAllByStatusIs(eq(AgendaStatus.FINISH), any(Pageable.class))) + when(agendaRepository.findAllByStatusIs( + eq(AgendaStatus.FINISH), eq(AgendaStatus.CONFIRM), any(Pageable.class))) .thenReturn(agendaPage); // when @@ -200,7 +201,7 @@ void getAgendaListHistorySuccess() { // then verify(agendaRepository, times(1)) - .findAllByStatusIs(AgendaStatus.FINISH, pageable); + .findAllByStatusIs(AgendaStatus.FINISH, AgendaStatus.CONFIRM, pageable); assertThat(result.size()).isEqualTo(size); for (int i = 1; i < result.size(); i++) { assertThat(result.get(i).getStartTime()) diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index d0d678b26..552f498e0 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -20,7 +20,8 @@ public interface AgendaRepository extends JpaRepository { @Query("SELECT a FROM Agenda a WHERE a.status = :status") List findAllByStatusIs(AgendaStatus status); - Page findAllByStatusIs(AgendaStatus status, Pageable pageable); + @Query("SELECT a FROM Agenda a WHERE a.status = :status1 OR a.status = :status2") + Page findAllByStatusIs(AgendaStatus status1, AgendaStatus status2, Pageable pageable); Optional findAgendaByAgendaKey(UUID usedTo); From 78453f3a61ecd93f50746d91a1bea6619cf7e8d5 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:15:11 +0900 Subject: [PATCH 090/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20agenda?= =?UTF-8?q?=20api=20=EC=BF=BC=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20=EC=84=B1?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20#986=20(#989)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AgendaProfileFindService.java | 4 +- .../agendateam/service/AgendaTeamService.java | 5 ++- .../main/java/gg/data/agenda/AgendaTeam.java | 3 +- .../java/gg/repo/agenda/AgendaRepository.java | 2 - .../agenda/AgendaTeamProfileRepository.java | 41 +++++++++---------- .../gg/repo/agenda/AgendaTeamRepository.java | 11 +---- 6 files changed, 28 insertions(+), 38 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 41b546b91..1ab8710eb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -48,8 +48,8 @@ public List findCurrentAttendAgenda(String intraI AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - List agendaTeamProfiles = agendaTeamProfileRepository.findByProfileAndIsExistTrue( - agendaProfile); + List agendaTeamProfiles = agendaTeamProfileRepository + .findByProfileAndIsExistTrue(agendaProfile); return agendaTeamProfiles.stream() .filter(agendaTeamProfile -> { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index a5cf04933..a1e8d7327 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -187,7 +187,7 @@ public AgendaTeam confirmTeam(UserDto user, Agenda agenda, UUID teamKey) { */ @Transactional(readOnly = true) public AgendaTeam getAgendaTeam(UUID teamKey) { - return agendaTeamRepository.findByTeamKeyFetchJoin(teamKey) + return agendaTeamRepository.findByTeamKey(teamKey) .orElseThrow(() -> new NotExistException(AGENDA_TEAM_NOT_FOUND)); } @@ -286,7 +286,8 @@ public void modifyAgendaTeam(UserDto user, TeamUpdateReqDto teamUpdateReqDto, UU throw new ForbiddenException(TEAM_LEADER_FORBIDDEN); } - List profiles = agendaTeamProfileRepository.findAllByAgendaTeam(agendaTeam); + List profiles = agendaTeamProfileRepository + .findAllByAgendaTeamAndIsExistTrue(agendaTeam); agenda.updateTeam(Location.valueOfLocation(teamUpdateReqDto.getTeamLocation()), LocalDateTime.now()); agendaTeam.updateTeam(teamUpdateReqDto.getTeamName(), teamUpdateReqDto.getTeamContent(), diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index c740ed875..aef548d31 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -12,6 +12,7 @@ import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -39,7 +40,7 @@ public class AgendaTeam extends BaseTimeEntity { @Column(name = "id", nullable = false) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "agenda_id", nullable = false) private Agenda agenda; diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index 552f498e0..357b5d70f 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -23,8 +23,6 @@ public interface AgendaRepository extends JpaRepository { @Query("SELECT a FROM Agenda a WHERE a.status = :status1 OR a.status = :status2") Page findAllByStatusIs(AgendaStatus status1, AgendaStatus status2, Pageable pageable); - Optional findAgendaByAgendaKey(UUID usedTo); - @Query("SELECT a FROM Agenda a WHERE a.hostIntraId = :intraId AND (a.status = :status1 OR a.status = :status2)") Page findAllByHostIntraIdAndStatus( String intraId, AgendaStatus status1, AgendaStatus status2, Pageable pageable); diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java index f2bdc5972..4a35b75bd 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamProfileRepository.java @@ -16,40 +16,39 @@ import gg.data.agenda.type.AgendaStatus; public interface AgendaTeamProfileRepository extends JpaRepository { - @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam.agenda = :agenda " - + "AND atp.profile = :agendaProfile AND atp.isExist = true") + @Query("SELECT atp FROM AgendaTeamProfile atp " + + "WHERE atp.agendaTeam.agenda = :agenda AND atp.profile = :agendaProfile AND atp.isExist = true") Optional findByAgendaAndAgendaProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); - @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") + @Query("SELECT atp FROM AgendaTeamProfile atp " + + "WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") List findByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); - @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agenda = :agenda AND atp.profile = :agendaProfile " - + "AND atp.isExist = true") + @Query("SELECT atp FROM AgendaTeamProfile atp " + + "WHERE atp.agenda = :agenda AND atp.profile = :agendaProfile AND atp.isExist = true") Optional findByAgendaAndProfileAndIsExistTrue(Agenda agenda, AgendaProfile agendaProfile); - /** - * 해당 메서드는 N+1 문제가 발생할 수 있습니다. - */ - List findAllByAgendaTeam(AgendaTeam agendaTeam); + @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.profile " + + "WHERE atp.agendaTeam = :agendaTeam AND atp.isExist = true") + List findAllByAgendaTeamAndIsExistTrue(AgendaTeam agendaTeam); - @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.profile WHERE atp.agendaTeam = :agendaTeam") - List findAllByAgendaTeamWithFetchProfile(AgendaTeam agendaTeam); - - @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.profile = :agendaProfile AND atp.isExist = true") + @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.agenda " + + "WHERE atp.profile = :agendaProfile AND atp.isExist = true") List findByProfileAndIsExistTrue(@Param("agendaProfile") AgendaProfile agendaProfile); @Query( - "SELECT atp FROM AgendaTeamProfile atp " - + "WHERE atp.profile = :agendaProfile " - + "AND atp.isExist = true " - + "AND atp.agenda.status = :status" - ) + value = "SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.agendaTeam at " + + "WHERE atp.profile = :agendaProfile AND atp.isExist = true AND atp.agenda.status = :status", + countQuery = "SELECT count(atp) FROM AgendaTeamProfile atp " + + "WHERE atp.profile = :agendaProfile AND atp.isExist = true AND atp.agenda.status = :status") Page findByProfileAndIsExistTrueAndAgendaStatus( - @Param("agendaProfile") AgendaProfile agendaProfile, - @Param("status") AgendaStatus status, Pageable pageable); + AgendaProfile agendaProfile, AgendaStatus status, Pageable pageable); + @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.profile " + + "WHERE atp.agenda = :agenda AND atp.isExist = true") List findAllByAgendaAndIsExistTrue(Agenda agenda); - @Query("SELECT atp FROM AgendaTeamProfile atp WHERE atp.agendaTeam IN :agendaTeams AND atp.isExist = true") + @Query("SELECT atp FROM AgendaTeamProfile atp JOIN FETCH atp.profile " + + "WHERE atp.agendaTeam IN :agendaTeams AND atp.isExist = true") List findByAgendaTeamInAndIsExistTrue(List agendaTeams); } diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 2d1e48888..3b27ec042 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -24,14 +24,8 @@ Optional findByAgendaAndTeamNameAndStatus(Agenda agenda, String team Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey, AgendaTeamStatus status1, AgendaTeamStatus status2); - @Query("SELECT a FROM AgendaTeam a WHERE a.teamKey = :teamKey") - Optional findByTeamKey(UUID teamKey); - @Query("SELECT a FROM AgendaTeam a JOIN FETCH a.agenda WHERE a.teamKey = :teamKey") - Optional findByTeamKeyFetchJoin(UUID teamKey); - - @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.name = :name AND a.status = :status") - Optional findByAgendaAndNameAndStatus(Agenda agenda, String name, AgendaTeamStatus status); + Optional findByTeamKey(UUID teamKey); @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status") List findAllByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status); @@ -41,7 +35,4 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); - - @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status") - Page findByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status, Pageable pageable); } From 0593cba4302f7dcf9034706aca3ed8acfad5a7b1 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:52:38 +0900 Subject: [PATCH 091/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20=ED=8C=80?= =?UTF-8?q?=EC=9B=90=20=EB=82=98=EA=B0=80=EA=B8=B0=EC=99=80=20=ED=8C=80?= =?UTF-8?q?=EC=9E=A5=20=EB=82=98=EA=B0=80=EA=B8=B0=20API=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#993=20(#995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaTeamController.java | 33 ++- .../agendateam/AgendaTeamControllerTest.java | 200 ++++++++++++++++-- .../java/gg/utils/exception/ErrorCode.java | 2 +- 3 files changed, 205 insertions(+), 30 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java index 6cea4f418..3674e8b57 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/controller/AgendaTeamController.java @@ -1,6 +1,7 @@ package gg.agenda.api.user.agendateam.controller; import static gg.data.agenda.type.AgendaTeamStatus.*; +import static gg.utils.exception.ErrorCode.*; import java.util.List; import java.util.Optional; @@ -42,6 +43,7 @@ import gg.data.agenda.type.Coalition; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; +import gg.utils.exception.custom.ForbiddenException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -106,7 +108,7 @@ public ResponseEntity confirmTeam(@Parameter(hidden = true) @Login UserDto } /** - * 아젠다 팀 나가기 + * 아젠다 팀장 나가기 * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 */ @PatchMapping("/cancel") @@ -114,15 +116,30 @@ public ResponseEntity leaveAgendaTeam(@Parameter(hidden = true) @Login Use @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto) { AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); agendaTeam.getAgenda().agendaStatusMustBeOpen(); + agendaTeam.agendaTeamStatusMustBeOpenAndConfirm(); + if (!agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { + throw new ForbiddenException(TEAM_LEADER_FORBIDDEN); + } + agendaTeamService.leaveTeamAll(agendaTeam); + agendaSlackService.slackCancelAgendaTeam(agendaTeam.getAgenda(), agendaTeam); + return ResponseEntity.noContent().build(); + } + + /** + * 아젠다 팀원 나가기 + * @param user 사용자 정보, teamKeyReqDto 팀 KEY 요청 정보, agendaId 아젠다 아이디 + */ + @PatchMapping("/drop") + public ResponseEntity dropAgendaTeamMate(@Parameter(hidden = true) @Login UserDto user, + @ModelAttribute @Valid TeamKeyReqDto teamKeyReqDto) { + AgendaTeam agendaTeam = agendaTeamService.getAgendaTeam(teamKeyReqDto.getTeamKey()); + agendaTeam.getAgenda().agendaStatusMustBeOpen(); + agendaTeam.agendaTeamStatusMustBeOpen(); if (agendaTeam.getLeaderIntraId().equals(user.getIntraId())) { - agendaTeam.agendaTeamStatusMustBeOpenAndConfirm(); - agendaTeamService.leaveTeamAll(agendaTeam); - agendaSlackService.slackCancelAgendaTeam(agendaTeam.getAgenda(), agendaTeam); - } else { - agendaTeam.agendaTeamStatusMustBeOpen(); - agendaTeamService.leaveTeamMate(agendaTeam, user); - agendaSlackService.slackLeaveTeamMate(agendaTeam.getAgenda(), agendaTeam, user.getIntraId()); + throw new ForbiddenException(NOT_TEAM_MATE); } + agendaTeamService.leaveTeamMate(agendaTeam, user); + agendaSlackService.slackLeaveTeamMate(agendaTeam.getAgenda(), agendaTeam, user.getIntraId()); return ResponseEntity.noContent().build(); } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java index eaeec8f1e..ccc21e603 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendateam/AgendaTeamControllerTest.java @@ -827,7 +827,7 @@ public void notValidAgendaTeam() throws Exception { } @Nested - @DisplayName("팀 Leave 테스트") + @DisplayName("팀장 Leave 테스트") class LeaveTeamTest { @BeforeEach void beforeEach() { @@ -843,13 +843,13 @@ void beforeEach() { } @Test - @DisplayName("200 팀원 팀 나가기 성공") + @DisplayName("403 팀원 팀 나가기 실패") public void leaveTeamMateSuccess() throws Exception { //given Agenda agenda = agendaMockData.createAgenda(SEOUL); AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); - AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); // when mockMvc.perform( patch("/agenda/team/cancel") @@ -857,20 +857,8 @@ public void leaveTeamMateSuccess() throws Exception { .param("agenda_key", agenda.getAgendaKey().toString()) .param("teamKey", team.getTeamKey().toString()) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); + .andExpect(status().isForbidden()); // then - AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); - assert updatedTeam != null; - assertThat(updatedTeam.getMateCount()).isEqualTo(1); - assertThat(agenda.getCurrentTeam()).isEqualTo(1); - AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); - assert updatedAtp != null; - assertThat(updatedAtp.getIsExist()).isFalse(); - ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( - updatedAtp.getProfile()) - .ifPresent(ticket -> { - assertThat(ticket.getUsedTo()).isNull(); - }); } @Test @@ -999,6 +987,176 @@ public void notValidAgendaTeamStatusWhenTeamLeader() throws Exception { .andExpect(status().isBadRequest()); } + @Test + @DisplayName("400 탈퇴 불가능한 Agenda 시간으로 인한 실패") + public void notValidAgendaDeadline() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(LocalDateTime.now().minusHours(1)); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("403 탈퇴 불가능한 Agenda Status 으로 인한 실패") + public void notValidAgendaStatus() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(FINISH); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + TeamKeyReqDto req = new TeamKeyReqDto(team.getTeamKey()); + String content = objectMapper.writeValueAsString(req); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("403 팀장이 아님으로 인한 실패") + public void notTeamMateFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + // when && then + mockMvc.perform( + patch("/agenda/team/cancel") + .header("Authorization", "Bearer " + gyeongsanUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + } + + @Nested + @DisplayName("팀원 Drop 테스트") + class DropTeamTest { + @BeforeEach + void beforeEach() { + seoulUser = testDataUtils.createNewUser(); + seoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(seoulUser); + seoulUserAgendaProfile = agendaMockData.createAgendaProfile(seoulUser, SEOUL); + anotherSeoulUser = testDataUtils.createNewUser(); + anotherSeoulUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(anotherSeoulUser); + anotherSeoulUserAgendaProfile = agendaMockData.createAgendaProfile(anotherSeoulUser, SEOUL); + gyeongsanUser = testDataUtils.createNewUser(); + gyeongsanUserAccessToken = testDataUtils.getLoginAccessTokenFromUser(gyeongsanUser); + gyeongsanUserAgendaProfile = agendaMockData.createAgendaProfile(gyeongsanUser, GYEONGSAN); + } + + @Test + @DisplayName("200 팀원 팀 나가기 성공") + public void leaveTeamMateSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + // when + mockMvc.perform( + patch("/agenda/team/drop") + .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + // then + AgendaTeam updatedTeam = agendaTeamRepository.findByTeamKey(team.getTeamKey()).orElse(null); + assert updatedTeam != null; + assertThat(updatedTeam.getMateCount()).isEqualTo(1); + assertThat(agenda.getCurrentTeam()).isEqualTo(1); + AgendaTeamProfile updatedAtp = agendaTeamProfileRepository.findById(atp.getId()).orElse(null); + assert updatedAtp != null; + assertThat(updatedAtp.getIsExist()).isFalse(); + ticketRepository.findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( + updatedAtp.getProfile()) + .ifPresent(ticket -> { + assertThat(ticket.getUsedTo()).isNull(); + }); + } + + @Test + @DisplayName("403 팀리더 팀 나가기 실패") + public void leaveTeamLeaderSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, 2); + AgendaTeamProfile atpLeader = agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + AgendaTeamProfile atp = agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + // when + mockMvc.perform( + patch("/agenda/team/drop") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("403 Confirm 팀 리더 팀 나가기 실패") + public void leaveTeamLeaderTeamStatusConfirmSuccess() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + AgendaTeam team = agendaMockData.createAgendaTeam(agenda, seoulUser, SEOUL, AgendaTeamStatus.CONFIRM); + agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); + agendaMockData.createAgendaTeamProfile(team, anotherSeoulUserAgendaProfile); + // when + mockMvc.perform( + patch("/agenda/team/drop") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", team.getTeamKey().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("404 agenda 없음으로 인한 실패") + public void noAgendaFail() throws Exception { + //given + UUID noAgendaKey = UUID.randomUUID(); + UUID noTeamKey = UUID.randomUUID(); + // when && then + mockMvc.perform( + patch("/agenda/team/drop") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", noAgendaKey.toString()) + .param("teamKey", noTeamKey.toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("404 team 없음으로 인한 실패") + public void noTeamFail() throws Exception { + //given + Agenda agenda = agendaMockData.createAgenda(SEOUL); + // when && then + mockMvc.perform( + patch("/agenda/team/drop") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .param("agenda_key", agenda.getAgendaKey().toString()) + .param("teamKey", UUID.randomUUID().toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + @Test @DisplayName("400 탈퇴 불가능한 AgendaTeam Status로 인한 팀원의 나가기 실패") public void notValidAgendaTeamStatusConfirmWhenTeamMate() throws Exception { @@ -1011,7 +1169,7 @@ public void notValidAgendaTeamStatusConfirmWhenTeamMate() throws Exception { String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - patch("/agenda/team/cancel") + patch("/agenda/team/drop") .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) @@ -1031,7 +1189,7 @@ public void notValidAgendaTeamStatusCoiWhenTeamMate() throws Exception { String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - patch("/agenda/team/cancel") + patch("/agenda/team/drop") .header("Authorization", "Bearer " + anotherSeoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) @@ -1050,7 +1208,7 @@ public void notValidAgendaDeadline() throws Exception { String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - patch("/agenda/team/cancel") + patch("/agenda/team/drop") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) @@ -1069,7 +1227,7 @@ public void notValidAgendaStatus() throws Exception { String content = objectMapper.writeValueAsString(req); // when && then mockMvc.perform( - patch("/agenda/team/cancel") + patch("/agenda/team/drop") .header("Authorization", "Bearer " + seoulUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .content(content) @@ -1086,7 +1244,7 @@ public void notTeamMateFail() throws Exception { agendaMockData.createAgendaTeamProfile(team, seoulUserAgendaProfile); // when && then mockMvc.perform( - patch("/agenda/team/cancel") + patch("/agenda/team/drop") .header("Authorization", "Bearer " + gyeongsanUserAccessToken) .param("agenda_key", agenda.getAgendaKey().toString()) .param("teamKey", team.getTeamKey().toString()) diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index b3c68558c..cdc8162f1 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -213,7 +213,7 @@ public enum ErrorCode { NOT_TEAM_MATE(403, "AG204", "팀원이 아닙니다."), CONFIRM_FORBIDDEN(403, "AG205", "개최자만 일정을 종료할 수 있습니다."), TICKET_FORBIDDEN(403, "AG206", "티켓 신청은 1분의 대기시간이 있습니다."), - TEAM_LEADER_FORBIDDEN(403, "AG207", "팀장만 팀을 확정할 수 있습니다."), + TEAM_LEADER_FORBIDDEN(403, "AG207", "팀장이 아닙니다."), AGENDA_TEAM_FORBIDDEN(403, "AG208", "일정에 참여한 팀이 있습니다."), AGENDA_MODIFICATION_FORBIDDEN(403, "AG209", "개최자만 일정을 수정할 수 있습니다."), AUTH_NOT_FOUND(404, "AG301", "42 정보가 만료되었습니다."), From 86656ad629aa365aeaf7a2eb91e223f753fbd822 Mon Sep 17 00:00:00 2001 From: jeongwpa <55525927+yhames@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:08:39 +0900 Subject: [PATCH 092/103] =?UTF-8?q?=F0=9F=94=A8=20[Refactoring]=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=EC=A4=91=20=EB=8C=80=ED=9A=8C=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EC=A1=B0=ED=9A=8C=20API=20=EB=B6=84=EB=A6=AC=20#992?= =?UTF-8?q?=20(#994)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/controller/AgendaController.java | 44 +++++++--- .../user/agenda/service/AgendaService.java | 29 ++++++- .../controller/AgendaControllerTest.java | 80 ++++++++++++++++--- .../agenda/service/AgendaServiceTest.java | 11 +-- .../java/gg/repo/agenda/AgendaRepository.java | 6 +- 5 files changed, 137 insertions(+), 33 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index d2efc4b12..e2ad37295 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -61,27 +61,33 @@ public ResponseEntity agendaDetails(@RequestParam("agenda_key") UU return ResponseEntity.ok(agendaResDto); } - @GetMapping("/list") - public ResponseEntity> agendaListCurrent() { - List agendaList = agendaService.findCurrentAgendaList(); + /** + * OPEN인데 deadline이 지나지 않은 대회 반환 + */ + @GetMapping("/open") + public ResponseEntity> agendaListOpen() { + List agendaList = agendaService.findOpenAgendaList(); List agendaSimpleResDtoList = agendaList.stream() .map(AgendaSimpleResDto.MapStruct.INSTANCE::toDto) .collect(Collectors.toList()); return ResponseEntity.ok(agendaSimpleResDtoList); } - @PostMapping("/request") - public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, - @ModelAttribute @Valid AgendaCreateReqDto agendaCreateReqDto, - @RequestParam(required = false) MultipartFile agendaPoster) { - if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024) { // 1MB - throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); - } - UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, agendaPoster, user).getAgendaKey(); - AgendaKeyResDto responseDto = AgendaKeyResDto.builder().agendaKey(agendaKey).build(); - return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); + /** + * OPEN인데 deadline이 지난 대회와 CONFIRM인 대회 반환 + */ + @GetMapping("/confirm") + public ResponseEntity> agendaListConfirm() { + List agendaList = agendaService.findConfirmAgendaList(); + List agendaSimpleResDtoList = agendaList.stream() + .map(AgendaSimpleResDto.MapStruct.INSTANCE::toDto) + .collect(Collectors.toList()); + return ResponseEntity.ok(agendaSimpleResDtoList); } + /** + * FINISH 상태인 대회 반환, 페이지네이션 + */ @GetMapping("/history") public ResponseEntity> agendaListHistory( @ModelAttribute @Valid PageRequestDto pageRequest) { @@ -99,6 +105,18 @@ public ResponseEntity> agendaListHistory( return ResponseEntity.ok(pageResponseDto); } + @PostMapping("/request") + public ResponseEntity agendaAdd(@Login @Parameter(hidden = true) UserDto user, + @ModelAttribute @Valid AgendaCreateReqDto agendaCreateReqDto, + @RequestParam(required = false) MultipartFile agendaPoster) { + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 1024 * 1024) { // 1MB + throw new InvalidParameterException(AGENDA_POSTER_SIZE_TOO_LARGE); + } + UUID agendaKey = agendaService.addAgenda(agendaCreateReqDto, agendaPoster, user).getAgendaKey(); + AgendaKeyResDto responseDto = AgendaKeyResDto.builder().agendaKey(agendaKey).build(); + return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); + } + @PatchMapping("/finish") public ResponseEntity agendaEndWithAwards(@RequestParam("agenda_key") UUID agendaKey, @RequestBody @Valid AgendaAwardsReqDto agendaAwardsReqDto, @Login @Parameter(hidden = true) UserDto user) { diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 91cde141c..aa63696ee 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URL; +import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -67,9 +68,13 @@ public Agenda findAgendaByAgendaKey(UUID agendaKey) { .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); } + /** + * OPEN인데 deadline이 지나지 않은 대회 반환 + */ @Transactional(readOnly = true) - public List findCurrentAgendaList() { + public List findOpenAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN).stream() + .filter(agenda -> agenda.getDeadline().isAfter(LocalDateTime.now())) .sorted(agendaComparatorWithIsOfficialThenDeadline()) .collect(Collectors.toList()); } @@ -79,6 +84,23 @@ private Comparator agendaComparatorWithIsOfficialThenDeadline() { .thenComparing(Agenda::getDeadline, Comparator.reverseOrder()); } + /** + * OPEN인데 deadline이 지난 대회와 CONFIRM인 대회 반환 + */ + @Transactional(readOnly = true) + public List findConfirmAgendaList() { + return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN, AgendaStatus.CONFIRM).stream() + .filter(agenda -> agenda.getDeadline().isBefore(LocalDateTime.now()) + || agenda.getStatus() == AgendaStatus.CONFIRM) + .sorted(agendaComparatorWithIsOfficialThenStartTime()) + .collect(Collectors.toList()); + } + + private Comparator agendaComparatorWithIsOfficialThenStartTime() { + return Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) + .thenComparing(Agenda::getStartTime, Comparator.reverseOrder()); + } + @Transactional public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster, UserDto user) { try { @@ -94,9 +116,12 @@ public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster } } + /** + * FINISH 상태인 대회 반환, 페이지네이션 + */ @Transactional(readOnly = true) public Page findHistoryAgendaList(Pageable pageable) { - return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, AgendaStatus.CONFIRM, pageable); + return agendaRepository.findAllByStatusIs(AgendaStatus.FINISH, pageable); } @Transactional diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 55f0db099..c07318e69 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -222,19 +222,19 @@ void getAgendaFailedWhenNoContent() throws Exception { } @Nested - @DisplayName("Agenda 현황 전체 조회") - class GetAgendaListCurrent { + @DisplayName("모집중 Agenda 전체 조회") + class GetAgendaListOpen { @Test - @DisplayName("Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") - void getAgendaListSuccess() throws Exception { + @DisplayName("모집중인 대회를 Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") + void getAgendaListOpenSuccess() throws Exception { // given List officialAgendaList = agendaMockData.createOfficialAgendaList(3, AgendaStatus.OPEN); List nonOfficialAgendaList = agendaMockData - .createNonOfficialAgendaList(6, AgendaStatus.OPEN); + .createNonOfficialAgendaList(3, AgendaStatus.OPEN); // when - String response = mockMvc.perform(get("/agenda/list") + String response = mockMvc.perform(get("/agenda/open") .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -252,14 +252,76 @@ void getAgendaListSuccess() throws Exception { } @Test - @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") - void getAgendaListSuccessWithNoAgenda() throws Exception { + @DisplayName("모집 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListOpenSuccessWithNoAgenda() throws Exception { // given agendaMockData.createOfficialAgendaList(3, AgendaStatus.FINISH); agendaMockData.createNonOfficialAgendaList(6, AgendaStatus.CANCEL); // when - String response = mockMvc.perform(get("/agenda/list") + String response = mockMvc.perform(get("/agenda/open") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); + + // then + assertThat(result.length).isEqualTo(0); + } + } + + @Nested + @DisplayName("진행중 Agenda 전체 조회") + class GetAgendaListConfirm { + + @Test + @DisplayName("진행중인 대회를 Official과 Deadline이 빠른 순으로 정렬하여 반환합니다.") + void getAgendaListConfirmSuccess() throws Exception { + // given + List officialAgendaList = agendaMockData + .createOfficialAgendaList(3, AgendaStatus.OPEN); + for (Agenda agenda : officialAgendaList) { + agenda.updateSchedule(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1), + LocalDateTime.now().plusDays(2)); + } + List nonOfficialAgendaList = agendaMockData + .createNonOfficialAgendaList(3, AgendaStatus.OPEN); + for (Agenda agenda : nonOfficialAgendaList) { + agenda.updateSchedule(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1), + LocalDateTime.now().plusDays(2)); + } + List confirmedAgendaList = agendaMockData + .createNonOfficialAgendaList(3, AgendaStatus.CONFIRM); + int totalSize = officialAgendaList.size() + nonOfficialAgendaList.size() + confirmedAgendaList.size(); + + // when + String response = mockMvc.perform(get("/agenda/confirm") + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + AgendaSimpleResDto[] result = objectMapper.readValue(response, AgendaSimpleResDto[].class); + + // then + assertThat(result.length).isEqualTo(totalSize); + for (int i = 0; i < result.length; i++) { + assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); + if (i == 0 || i == officialAgendaList.size()) { + continue; + } + assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + } + } + + @Test + @DisplayName("진행 중인 Agenda가 없는 경우 빈 리스트를 반환합니다.") + void getAgendaListConfirmSuccessWithNoAgenda() throws Exception { + // given + agendaMockData.createOfficialAgendaList(3, AgendaStatus.OPEN); + agendaMockData.createOfficialAgendaList(3, AgendaStatus.FINISH); + agendaMockData.createNonOfficialAgendaList(3, AgendaStatus.CANCEL); + + // when + String response = mockMvc.perform(get("/agenda/confirm") .header("Authorization", "Bearer " + accessToken)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 8989c0742..39bd87b89 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -5,8 +5,6 @@ import static org.mockito.Mockito.*; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -124,7 +122,7 @@ void getAgendaListSuccess() { when(agendaRepository.findAllByStatusIs(AgendaStatus.OPEN)).thenReturn(agendas); // when - List result = agendaService.findCurrentAgendaList(); + List result = agendaService.findOpenAgendaList(); // then verify(agendaRepository, times(1)).findAllByStatusIs(any()); @@ -145,7 +143,7 @@ void getAgendaListWithNoContent() { when(agendaRepository.findAllByStatusIs(AgendaStatus.OPEN)).thenReturn(agendas); // when - agendaService.findCurrentAgendaList(); + agendaService.findOpenAgendaList(); // then verify(agendaRepository, times(1)).findAllByStatusIs(any()); @@ -191,8 +189,7 @@ void getAgendaListHistorySuccess() { .build() )); Page agendaPage = new PageImpl<>(agendas.subList(0, 10), pageable, size); - when(agendaRepository.findAllByStatusIs( - eq(AgendaStatus.FINISH), eq(AgendaStatus.CONFIRM), any(Pageable.class))) + when(agendaRepository.findAllByStatusIs(eq(AgendaStatus.FINISH), any(Pageable.class))) .thenReturn(agendaPage); // when @@ -201,7 +198,7 @@ void getAgendaListHistorySuccess() { // then verify(agendaRepository, times(1)) - .findAllByStatusIs(AgendaStatus.FINISH, AgendaStatus.CONFIRM, pageable); + .findAllByStatusIs(AgendaStatus.FINISH, pageable); assertThat(result.size()).isEqualTo(size); for (int i = 1; i < result.size(); i++) { assertThat(result.get(i).getStartTime()) diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java index 357b5d70f..f8f3b030a 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaRepository.java @@ -10,7 +10,6 @@ import org.springframework.data.jpa.repository.Query; import gg.data.agenda.Agenda; -import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.type.AgendaStatus; public interface AgendaRepository extends JpaRepository { @@ -21,7 +20,10 @@ public interface AgendaRepository extends JpaRepository { List findAllByStatusIs(AgendaStatus status); @Query("SELECT a FROM Agenda a WHERE a.status = :status1 OR a.status = :status2") - Page findAllByStatusIs(AgendaStatus status1, AgendaStatus status2, Pageable pageable); + List findAllByStatusIs(AgendaStatus status1, AgendaStatus status2); + + @Query("SELECT a FROM Agenda a WHERE a.status = :status") + Page findAllByStatusIs(AgendaStatus status, Pageable pageable); @Query("SELECT a FROM Agenda a WHERE a.hostIntraId = :intraId AND (a.status = :status1 OR a.status = :status2)") Page findAllByHostIntraIdAndStatus( From 0ed96a8ecaf9b2fd896fe74dade0a0bc68d47755 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:37:20 +0900 Subject: [PATCH 093/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20PosterImage=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=82=AC=EC=9A=A9=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EB=A5=BC=20=EC=95=8C=EA=B8=B0=EC=9C=84=ED=95=9C=20db?= =?UTF-8?q?=20table=20=EC=B6=94=EA=B0=80=20#984=20(#991)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/service/AgendaAdminService.java | 8 ++++ .../user/agenda/service/AgendaService.java | 10 +++- .../controller/AgendaAdminControllerTest.java | 48 +++++++++++++++++++ .../service/AgendaAdminServiceTest.java | 5 +- .../controller/AgendaControllerTest.java | 14 +++--- .../gg/data/agenda/AgendaPosterImage.java | 45 +++++++++++++++++ .../resources/db/migration/V3__agenda.sql | 13 ++++- .../agenda/AgendaPosterImageRepository.java | 14 ++++++ .../agenda/AgendaPosterImageFixture.java | 23 +++++++++ 9 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 gg-data/src/main/java/gg/data/agenda/AgendaPosterImage.java create mode 100644 gg-repo/src/main/java/gg/repo/agenda/AgendaPosterImageRepository.java create mode 100644 gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaPosterImageFixture.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index 3b837d276..68430d836 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -22,7 +22,9 @@ import gg.agenda.api.admin.agenda.controller.request.AgendaAdminUpdateReqDto; import gg.agenda.api.admin.agenda.controller.response.AgendaAdminSimpleResDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaPosterImage; import gg.data.agenda.AgendaTeam; +import gg.repo.agenda.AgendaPosterImageRepository; import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.ImageHandler; @@ -38,6 +40,8 @@ public class AgendaAdminService { private final AgendaTeamAdminRepository agendaTeamAdminRepository; + private final AgendaPosterImageRepository agendaPosterImageRepository; + private final ImageHandler imageHandler; @Value("${info.image.defaultUrl}") @@ -58,6 +62,10 @@ public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto, Mult if (Objects.nonNull(agendaPoster)) { URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, agenda.getTitle(), defaultUri); agenda.updatePosterUri(storedUrl.toString()); + Optional posterImage = agendaPosterImageRepository.findByAgendaIdAndIsCurrentTrue( + agenda.getId()); + posterImage.ifPresent(AgendaPosterImage::updateIsCurrentToFalse); + agendaPosterImageRepository.save(new AgendaPosterImage(agenda.getId(), storedUrl.toString())); } } catch (IOException e) { log.error("Failed to upload image", e); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index aa63696ee..089134f79 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -26,9 +26,11 @@ import gg.agenda.api.utils.SnsMessageUtil; import gg.auth.UserDto; import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaPosterImage; import gg.data.agenda.AgendaTeam; import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; +import gg.repo.agenda.AgendaPosterImageRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; @@ -49,6 +51,8 @@ public class AgendaService { private final AgendaTeamRepository agendaTeamRepository; + private final AgendaPosterImageRepository agendaPosterImageRepository; + private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaTeamService agendaTeamService; @@ -109,7 +113,11 @@ public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster createDto.updatePosterUri(storedUrl); } Agenda newAgenda = AgendaCreateReqDto.MapStruct.INSTANCE.toEntity(createDto, user.getIntraId()); - return agendaRepository.save(newAgenda); + newAgenda = agendaRepository.save(newAgenda); + if (newAgenda.getPosterUri() != null) { + agendaPosterImageRepository.save(new AgendaPosterImage(newAgenda.getId(), newAgenda.getPosterUri())); + } + return newAgenda; } catch (IOException e) { log.error("Failed to upload image for agenda poster", e); throw new BusinessException(AGENDA_CREATE_FAILED); diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java index d07adb842..901d705c6 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/controller/AgendaAdminControllerTest.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.MultiValueMap; @@ -41,12 +42,14 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.Location; import gg.data.user.User; +import gg.repo.agenda.AgendaPosterImageRepository; import gg.utils.TestDataUtils; import gg.utils.annotation.IntegrationTest; import gg.utils.converter.MultiValueMapConverter; import gg.utils.dto.PageResponseDto; import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; +import gg.utils.fixture.agenda.AgendaPosterImageFixture; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -70,12 +73,18 @@ public class AgendaAdminControllerTest { @Autowired private AgendaFixture agendaFixture; + @Autowired + private AgendaPosterImageFixture agendaPosterImageFixture; + @Autowired EntityManager em; @Autowired AgendaAdminRepository agendaAdminRepository; + @Autowired + AgendaPosterImageRepository agendaPosterImageRepository; + @Value("${info.image.defaultUrl}") private String defaultUri; @@ -376,6 +385,45 @@ void updateAgendaAdminSuccessWithAgendaTeamCapacity() throws Exception { assertThat(updated.get().getMaxPeople()).isEqualTo(agendaDto.getAgendaMaxPeople()); } + @Test + @DisplayName("Admin Agenda 수정 및 삭제 성공 -Poster URI 변경") + void updateAgendaAdminSuccessWithAgendaPosterUri() throws Exception { + // given + URL mockUrl = new URL(defaultUri); + URL newMockUrl = new URL("https://newPosterUri.com"); + Mockito.when(imageHandler.uploadImageOrDefault(Mockito.any(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(newMockUrl); + Agenda agenda = agendaMockData.createAgendaWithTeam(10); + agendaPosterImageFixture.createAgendaPosterImage(agenda, mockUrl.toString()); + AgendaAdminUpdateReqDto agendaDto = AgendaAdminUpdateReqDto.builder() + .agendaPosterUri(newMockUrl) + .build(); + MockMultipartFile multipartFile = new MockMultipartFile("file", "test.jpg", "image/jpeg", + "test".getBytes()); + MultiValueMap params = MultiValueMapConverter.convert(objectMapper, agendaDto); + + // when + mockMvc.perform(multipart("/agenda/admin/request") + .file("agendaPoster", multipartFile.getBytes()) + .param("agenda_key", agenda.getAgendaKey().toString()) + .params(params) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNoContent()); + Optional updated = agendaAdminRepository.findByAgendaKey(agenda.getAgendaKey()); + + // then + assert (updated.isPresent()); + assertThat(updated.get().getPosterUri()).isEqualTo(agendaDto.getAgendaPosterUri().toString()); + agendaPosterImageRepository.findByAgendaIdAndIsCurrentTrue(agenda.getId()) + .ifPresentOrElse(posterImage -> assertThat(posterImage.getImageUri()) + .isEqualTo(agendaDto.getAgendaPosterUri().toString()), + () -> fail("Agenda Poster Image not found")); + agendaPosterImageRepository.findByAgendaIdAndIsCurrentFalse(agenda.getId()) + .ifPresentOrElse(posterImage -> assertThat(posterImage.getIsCurrent()) + .isEqualTo(false), + () -> fail("Agenda Poster Image not found")); + } + @Test @DisplayName("Admin Agenda 수정 및 삭제 실패 - 대회가 존재하지 않는 경우") void updateAgendaAdminFailedWithNoAgenda() throws Exception { diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java index b75cc3155..006740e30 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agenda/service/AgendaAdminServiceTest.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -31,6 +30,7 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; +import gg.repo.agenda.AgendaPosterImageRepository; import gg.utils.annotation.UnitTest; import gg.utils.exception.custom.InvalidParameterException; import gg.utils.exception.custom.NotExistException; @@ -45,6 +45,9 @@ public class AgendaAdminServiceTest { @Mock AgendaTeamAdminRepository agendaTeamAdminRepository; + @Mock + AgendaPosterImageRepository agendaPosterImageRepository; + @Mock AwsImageHandler imageHandler; diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index c07318e69..95e13a23c 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -50,6 +50,7 @@ import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.data.user.User; +import gg.repo.agenda.AgendaPosterImageRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.utils.AgendaTestDataUtils; @@ -62,9 +63,7 @@ import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.AwsImageHandler; import gg.utils.fixture.agenda.AgendaFixture; -import gg.utils.fixture.agenda.AgendaProfileFixture; import gg.utils.fixture.agenda.AgendaTeamFixture; -import gg.utils.fixture.agenda.AgendaTeamProfileFixture; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -91,12 +90,6 @@ public class AgendaControllerTest { @Autowired private AgendaTeamFixture agendaTeamFixture; - @Autowired - private AgendaProfileFixture agendaProfileFixture; - - @Autowired - private AgendaTeamProfileFixture agendaTeamProfileFixture; - @Autowired private AgendaTestDataUtils agendaTestDataUtils; @@ -112,6 +105,9 @@ public class AgendaControllerTest { @Autowired AgendaTeamRepository agendaTeamRepository; + @Autowired + AgendaPosterImageRepository agendaPosterImageRepository; + @Value("${info.image.defaultUrl}") private String defaultUri; @@ -410,6 +406,8 @@ void createAgendaSuccessWithPosterImage() throws Exception { assertThat(agenda.getMinPeople()).isEqualTo(dto.getAgendaMinPeople()); assertThat(agenda.getPosterUri()).isNotEqualTo(defaultUri); assertThat(agenda.getPosterUri()).isEqualTo(mockS3Path.toString()); + agendaPosterImageRepository.findByAgendaIdAndIsCurrentTrue(agenda.getId()) + .orElseThrow(() -> new NotExistException("Agenda poster image not found")); } @Test diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaPosterImage.java b/gg-data/src/main/java/gg/data/agenda/AgendaPosterImage.java new file mode 100644 index 000000000..6a54bcd29 --- /dev/null +++ b/gg-data/src/main/java/gg/data/agenda/AgendaPosterImage.java @@ -0,0 +1,45 @@ +package gg.data.agenda; + +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AgendaPosterImage { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @Column(name = "agenda_id", nullable = false) + private Long agendaId; + @Column(name = "image_uri", nullable = false, length = 255) + private String imageUri; + @Column(name = "is_current", nullable = false) + private Boolean isCurrent; + @Column(name = "s3_deleted", nullable = false) + private Boolean s3Deleted; + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + public AgendaPosterImage(Long agendaID, String imageUri) { + this.agendaId = agendaID; + this.imageUri = imageUri; + this.isCurrent = true; + this.s3Deleted = false; + this.createdAt = LocalDateTime.now(); + } + + public void updateIsCurrentToFalse() { + this.isCurrent = false; + } +} diff --git a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql index 2770051d8..8f75c8208 100644 --- a/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql +++ b/gg-pingpong-api/src/main/resources/db/migration/V3__agenda.sql @@ -109,4 +109,15 @@ CREATE TABLE `ticket` PRIMARY KEY (`id`), KEY `fk_ticket_profile_profile_id` (`profile_id`), CONSTRAINT `fk_ticket_profile_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `agenda_profile` (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +CREATE TABLE `agenda_poster_image` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `agenda_id` BIGINT NOT NULL, + `image_uri` VARCHAR(255) NOT NULL, + `is_current` BOOLEAN NOT NULL, + `s3_deleted` BOOLEAN NOT NULL, + `created_at` DATETIME NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaPosterImageRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaPosterImageRepository.java new file mode 100644 index 000000000..f8d747347 --- /dev/null +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaPosterImageRepository.java @@ -0,0 +1,14 @@ +package gg.repo.agenda; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.data.agenda.AgendaPosterImage; + +public interface AgendaPosterImageRepository extends JpaRepository { + + Optional findByAgendaIdAndIsCurrentFalse(Long agendaId); + + Optional findByAgendaIdAndIsCurrentTrue(Long agendaId); +} diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaPosterImageFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaPosterImageFixture.java new file mode 100644 index 000000000..a4a9a8b84 --- /dev/null +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaPosterImageFixture.java @@ -0,0 +1,23 @@ +package gg.utils.fixture.agenda; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.springframework.stereotype.Component; + +import gg.data.agenda.Agenda; +import gg.data.agenda.AgendaPosterImage; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AgendaPosterImageFixture { + @PersistenceContext + private final EntityManager em; + + public AgendaPosterImage createAgendaPosterImage(Agenda agenda, String posterUri) { + AgendaPosterImage posterImage = new AgendaPosterImage(agenda.getId(), "posterUri"); + em.persist(posterImage); + return posterImage; + } +} From 0d56342caf4e514ebd6a7114c07e404ee5ac0aa2 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:22:04 +0900 Subject: [PATCH 094/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20=ED=8F=AC?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=86?= =?UTF-8?q?=EC=9D=B4=20=EB=B3=B4=EB=82=B4=EB=8F=84=200=EB=B0=94=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EC=A7=9C=EB=A6=AC=20=ED=8F=AC=EC=8A=A4=ED=84=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B0=80=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20#996=20(#997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/admin/agenda/service/AgendaAdminService.java | 2 +- .../agenda/api/user/agenda/service/AgendaService.java | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java index 68430d836..2980c7149 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agenda/service/AgendaAdminService.java @@ -59,7 +59,7 @@ public void updateAgenda(UUID agendaKey, AgendaAdminUpdateReqDto agendaDto, Mult List teams = agendaTeamAdminRepository.findAllByAgenda(agenda); try { - if (Objects.nonNull(agendaPoster)) { + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 0) { URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, agenda.getTitle(), defaultUri); agenda.updatePosterUri(storedUrl.toString()); Optional posterImage = agendaPosterImageRepository.findByAgendaIdAndIsCurrentTrue( diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 089134f79..262ff7a09 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -23,7 +23,6 @@ import gg.agenda.api.user.agenda.controller.request.AgendaCreateReqDto; import gg.agenda.api.user.agenda.controller.request.AgendaTeamAward; import gg.agenda.api.user.agendateam.service.AgendaTeamService; -import gg.agenda.api.utils.SnsMessageUtil; import gg.auth.UserDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaPosterImage; @@ -32,13 +31,11 @@ import gg.data.agenda.type.AgendaTeamStatus; import gg.repo.agenda.AgendaPosterImageRepository; import gg.repo.agenda.AgendaRepository; -import gg.repo.agenda.AgendaTeamProfileRepository; import gg.repo.agenda.AgendaTeamRepository; import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.NotExistException; import gg.utils.file.handler.ImageHandler; -import gg.utils.sns.MessageSender; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -53,16 +50,10 @@ public class AgendaService { private final AgendaPosterImageRepository agendaPosterImageRepository; - private final AgendaTeamProfileRepository agendaTeamProfileRepository; - private final AgendaTeamService agendaTeamService; private final ImageHandler imageHandler; - private final MessageSender messageSender; - - private final SnsMessageUtil snsMessageUtil; - @Value("${info.image.defaultUrl}") private String defaultUri; @@ -108,7 +99,7 @@ private Comparator agendaComparatorWithIsOfficialThenStartTime() { @Transactional public Agenda addAgenda(AgendaCreateReqDto createDto, MultipartFile agendaPoster, UserDto user) { try { - if (Objects.nonNull(agendaPoster)) { + if (Objects.nonNull(agendaPoster) && agendaPoster.getSize() > 0) { URL storedUrl = imageHandler.uploadImageOrDefault(agendaPoster, createDto.getAgendaTitle(), defaultUri); createDto.updatePosterUri(storedUrl); } From c82ebcabc33b13b62b221d198765140fb721b08b Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:12:59 +0900 Subject: [PATCH 095/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Agenda=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=A0=84=EC=B2=B4=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=EC=88=98=EC=A0=95=20#998=20(#999)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/agenda/controller/AgendaController.java | 2 +- .../api/user/agenda/service/AgendaService.java | 16 ++++++++-------- .../agenda/controller/AgendaControllerTest.java | 13 ++++--------- .../user/agenda/service/AgendaServiceTest.java | 3 +-- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java index e2ad37295..5d1957815 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/controller/AgendaController.java @@ -93,7 +93,7 @@ public ResponseEntity> agendaListHistory( @ModelAttribute @Valid PageRequestDto pageRequest) { int page = pageRequest.getPage(); int size = pageRequest.getSize(); - Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").descending()); + Pageable pageable = PageRequest.of(page - 1, size, Sort.by("startTime").ascending()); Page agendas = agendaService.findHistoryAgendaList(pageable); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java index 262ff7a09..694c61bce 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agenda/service/AgendaService.java @@ -70,13 +70,13 @@ public Agenda findAgendaByAgendaKey(UUID agendaKey) { public List findOpenAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN).stream() .filter(agenda -> agenda.getDeadline().isAfter(LocalDateTime.now())) - .sorted(agendaComparatorWithIsOfficialThenDeadline()) + .sorted(agendaComparatorWithDeadlineThenIsOfficial()) .collect(Collectors.toList()); } - private Comparator agendaComparatorWithIsOfficialThenDeadline() { - return Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) - .thenComparing(Agenda::getDeadline, Comparator.reverseOrder()); + private Comparator agendaComparatorWithDeadlineThenIsOfficial() { + return Comparator.comparing(Agenda::getDeadline) + .thenComparing(Agenda::getIsOfficial); } /** @@ -87,13 +87,13 @@ public List findConfirmAgendaList() { return agendaRepository.findAllByStatusIs(AgendaStatus.OPEN, AgendaStatus.CONFIRM).stream() .filter(agenda -> agenda.getDeadline().isBefore(LocalDateTime.now()) || agenda.getStatus() == AgendaStatus.CONFIRM) - .sorted(agendaComparatorWithIsOfficialThenStartTime()) + .sorted(agendaComparatorWithStartTimeThenIsOfficial()) .collect(Collectors.toList()); } - private Comparator agendaComparatorWithIsOfficialThenStartTime() { - return Comparator.comparing(Agenda::getIsOfficial, Comparator.reverseOrder()) - .thenComparing(Agenda::getStartTime, Comparator.reverseOrder()); + private Comparator agendaComparatorWithStartTimeThenIsOfficial() { + return Comparator.comparing(Agenda::getDeadline) + .thenComparing(Agenda::getIsOfficial); } @Transactional diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java index 95e13a23c..6b4dab4bb 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/controller/AgendaControllerTest.java @@ -239,11 +239,10 @@ void getAgendaListOpenSuccess() throws Exception { // then assertThat(result.length).isEqualTo(officialAgendaList.size() + nonOfficialAgendaList.size()); for (int i = 0; i < result.length; i++) { - assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); if (i == 0 || i == officialAgendaList.size()) { continue; } - assertThat(result[i].getAgendaDeadLine()).isBefore(result[i - 1].getAgendaDeadLine()); + assertThat(result[i].getAgendaDeadLine()).isAfter(result[i - 1].getAgendaDeadLine()); } } @@ -300,11 +299,10 @@ void getAgendaListConfirmSuccess() throws Exception { // then assertThat(result.length).isEqualTo(totalSize); for (int i = 0; i < result.length; i++) { - assertThat(result[i].getIsOfficial()).isEqualTo(i < officialAgendaList.size()); if (i == 0 || i == officialAgendaList.size()) { continue; } - assertThat(result[i].getAgendaStartTime()).isBefore(result[i - 1].getAgendaStartTime()); + assertThat(result[i].getAgendaStartTime()).isAfter(result[i - 1].getAgendaDeadLine()); } } @@ -587,12 +585,10 @@ void getAgendaListHistorySuccess(int page) throws Exception { // then assertThat(result.size()).isEqualTo(size * page < totalCount ? size : totalCount % size); for (int i = 0; i < result.size(); i++) { - assertThat(result.get(i).getAgendaTitle()) - .isEqualTo(agendaHistory.get(size * (page - 1) + i).getTitle()); if (i == 0) { continue; } - assertThat(result.get(i).getAgendaStartTime()).isBefore(result.get(i - 1).getAgendaStartTime()); + assertThat(result.get(i).getAgendaStartTime()).isAfter(result.get(i - 1).getAgendaStartTime()); } } @@ -711,11 +707,10 @@ void getAgendaListHistoryWithoutSize() throws Exception { // then assertThat(result.size()).isEqualTo(20); for (int i = 0; i < result.size(); i++) { - assertThat(result.get(i).getAgendaTitle()).isEqualTo(agendaHistory.get(i).getTitle()); if (i == 0) { continue; } - assertThat(result.get(i).getAgendaStartTime()).isBefore(result.get(i - 1).getAgendaStartTime()); + assertThat(result.get(i).getAgendaStartTime()).isAfter(result.get(i - 1).getAgendaStartTime()); } } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 39bd87b89..c2f0549ca 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -127,11 +127,10 @@ void getAgendaListSuccess() { // then verify(agendaRepository, times(1)).findAllByStatusIs(any()); for (int i = 0; i < result.size(); i++) { - assertThat(result.get(i).getIsOfficial()).isEqualTo(i < officialSize); if (i == 0 || i == officialSize) { continue; } - assertThat(result.get(i).getDeadline()).isBefore(result.get(i - 1).getDeadline()); + assertThat(result.get(i).getDeadline()).isAfter(result.get(i - 1).getDeadline()); } } From b8ae23818570991bc3037392235be9474d753c94 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:01:17 +0900 Subject: [PATCH 096/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20Agenda=20Confirm?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=ED=8C=80=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20#1000=20(#10?= =?UTF-8?q?01)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agenda/api/user/agendateam/service/AgendaTeamService.java | 2 +- gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java index a1e8d7327..4ad2897e3 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendateam/service/AgendaTeamService.java @@ -235,7 +235,7 @@ public void leaveTeam(AgendaTeam agendaTeam, AgendaTeamProfile agendaTeamProfile public Page findAgendaTeamWithStatus(UUID agendaKey, AgendaTeamStatus status, Pageable pageable) { Agenda agenda = agendaRepository.findByAgendaKey(agendaKey) .orElseThrow(() -> new NotExistException(AGENDA_NOT_FOUND)); - return agendaTeamRepository.findByAgendaAndStatusAndIsPrivateFalse(agenda, status, pageable); + return agendaTeamRepository.findByAgendaAndStatus(agenda, status, pageable); } @Transactional(readOnly = true) diff --git a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java index 3b27ec042..213a2f484 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/AgendaTeamRepository.java @@ -34,5 +34,5 @@ Optional findByAgendaAndTeamKeyAndStatus(Agenda agenda, UUID teamKey List findAllByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status1, AgendaTeamStatus status2); @Query("SELECT a FROM AgendaTeam a WHERE a.agenda = :agenda AND a.status = :status AND a.isPrivate = false") - Page findByAgendaAndStatusAndIsPrivateFalse(Agenda agenda, AgendaTeamStatus status, Pageable pageable); + Page findByAgendaAndStatus(Agenda agenda, AgendaTeamStatus status, Pageable pageable); } From 97682d80ae8fdde4afd23c5fcc8e3b4678d4fd79 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:12:54 +0900 Subject: [PATCH 097/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=20AgendaProfile?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EC=9C=A0=EC=A0=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=8B=9C=20=EA=B5=AC=EB=B6=84=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC=20#1002=20(?= =?UTF-8?q?#1003)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 15 ++++++--- .../response/AgendaProfileDetailsResDto.java | 18 ++-------- .../response/IntraProfileResDto.java | 33 +++++++++++++++++++ .../service/IntraProfileUtils.java | 20 ++++++++--- .../service/intraprofile/IntraProfile.java | 4 ++- .../intraprofile/IntraProfileResponse.java | 4 ++- .../agenda/service/AgendaServiceTest.java | 4 +-- .../AgendaProfileControllerTest.java | 6 ++-- .../main/java/gg/auth/FortyTwoAuthUtil.java | 1 + .../java/gg/utils/exception/ErrorCode.java | 3 +- 10 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/IntraProfileResDto.java diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 4f3abac15..09f8ff60b 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -25,6 +25,7 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.agenda.api.user.agendaprofile.controller.response.IntraProfileResDto; import gg.agenda.api.user.agendaprofile.controller.response.MyAgendaProfileDetailsResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; @@ -109,12 +110,18 @@ public ResponseEntity> getCurrentAttendAgend return ResponseEntity.ok(currentAttendAgendaList); } - @GetMapping("/{intraId}") - public ResponseEntity agendaProfileDetails(@PathVariable String intraId, - HttpServletResponse response) { + @GetMapping("{intraId}") + public ResponseEntity agendaProfileDetails(@PathVariable String intraId) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(intraId); + AgendaProfileDetailsResDto resDto = AgendaProfileDetailsResDto.toDto(profile); + return ResponseEntity.ok(resDto); + } + + @GetMapping("/intra/{intraId}") + public ResponseEntity intraProfileDetails(@PathVariable String intraId, + HttpServletResponse response) { IntraProfile intraProfile = intraProfileUtils.getIntraProfile(intraId, response); - AgendaProfileDetailsResDto resDto = AgendaProfileDetailsResDto.toDto(profile, intraProfile); + IntraProfileResDto resDto = IntraProfileResDto.toDto(intraProfile); return ResponseEntity.ok(resDto); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java index c7f224121..104d8332c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -1,12 +1,5 @@ package gg.agenda.api.user.agendaprofile.controller.response; -import java.net.URL; -import java.util.List; - -import org.mapstruct.Mapper; - -import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; -import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; @@ -23,31 +16,24 @@ public class AgendaProfileDetailsResDto { private String userGithub; private Coalition userCoalition; private Location userLocation; - private URL imageUrl; - private List achievements; @Builder public AgendaProfileDetailsResDto(String userIntraId, String userContent, String userGithub, - Coalition userCoalition, Location userLocation, URL imageUrl, - List achievements) { + Coalition userCoalition, Location userLocation) { this.userIntraId = userIntraId; this.userContent = userContent; this.userGithub = userGithub; this.userCoalition = userCoalition; this.userLocation = userLocation; - this.imageUrl = imageUrl; - this.achievements = achievements; } - public static AgendaProfileDetailsResDto toDto(AgendaProfile profile, IntraProfile intraProfile) { + public static AgendaProfileDetailsResDto toDto(AgendaProfile profile) { return AgendaProfileDetailsResDto.builder() .userIntraId(profile.getIntraId()) .userContent(profile.getContent()) .userGithub(profile.getGithubUrl()) .userCoalition(profile.getCoalition()) .userLocation(profile.getLocation()) - .imageUrl(intraProfile.getImageUrl()) - .achievements(intraProfile.getAchievements()) .build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/IntraProfileResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/IntraProfileResDto.java new file mode 100644 index 000000000..b3b7a791b --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/IntraProfileResDto.java @@ -0,0 +1,33 @@ +package gg.agenda.api.user.agendaprofile.controller.response; + +import java.net.URL; +import java.util.List; + +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class IntraProfileResDto { + private String intraId; + private URL imageUrl; + private List achievements; + + @Builder + public IntraProfileResDto(String intraId, URL imageUrl, List achievements) { + this.intraId = intraId; + this.imageUrl = imageUrl; + this.achievements = achievements; + } + + public static IntraProfileResDto toDto(IntraProfile intraProfile) { + return IntraProfileResDto.builder() + .intraId(intraProfile.getIntraId()) + .imageUrl(intraProfile.getImageUrl()) + .achievements(intraProfile.getAchievements()) + .build(); + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java index 610c16db5..e342ed9cb 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java @@ -9,7 +9,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraImage; @@ -18,6 +20,7 @@ import gg.auth.FortyTwoAuthUtil; import gg.utils.cookie.CookieUtil; import gg.utils.exception.custom.AuthenticationException; +import gg.utils.exception.custom.NotExistException; import gg.utils.external.ApiUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -42,7 +45,7 @@ public IntraProfile getIntraProfile(HttpServletResponse response) { intraProfileResponseValidation(intraProfileResponse); IntraImage intraImage = intraProfileResponse.getImage(); List intraAchievements = intraProfileResponse.getAchievements(); - return new IntraProfile(intraImage.getLink(), intraAchievements); + return new IntraProfile(intraProfileResponse.getLogin(), intraImage.getLink(), intraAchievements); } catch (Exception e) { log.error("42 Intra Profile API 호출 실패", e); cookieUtil.deleteCookie(response, "refresh_token"); @@ -56,11 +59,14 @@ public IntraProfile getIntraProfile(String intraId, HttpServletResponse response intraProfileResponseValidation(intraProfileResponse); IntraImage intraImage = intraProfileResponse.getImage(); List intraAchievements = intraProfileResponse.getAchievements(); - return new IntraProfile(intraImage.getLink(), intraAchievements); + return new IntraProfile(intraProfileResponse.getLogin(), intraImage.getLink(), intraAchievements); } catch (Exception e) { + if (e instanceof NotExistException) { + throw new NotExistException(AUTH_NOT_FOUND); + } log.error("42 Intra Profile API 호출 실패", e); cookieUtil.deleteCookie(response, "refresh_token"); - throw new AuthenticationException(AUTH_NOT_FOUND); + throw new AuthenticationException(AUTH_NOT_VALID); } } @@ -70,7 +76,10 @@ private IntraProfileResponse requestIntraProfile(String requestUrl) { HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); return apiUtil.apiCall(requestUrl, IntraProfileResponse.class, headers, HttpMethod.GET); - } catch (Exception e) { + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + throw new NotExistException(AUTH_NOT_FOUND); + } String accessToken = fortyTwoAuthUtil.refreshAccessToken(); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); @@ -82,6 +91,9 @@ private void intraProfileResponseValidation(IntraProfileResponse intraProfileRes if (Objects.isNull(intraProfileResponse)) { throw new AuthenticationException(AUTH_NOT_FOUND); } + if (Objects.isNull(intraProfileResponse.getLogin())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } if (Objects.isNull(intraProfileResponse.getImage())) { throw new AuthenticationException(AUTH_NOT_FOUND); } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java index 6ed2b7862..801b304f1 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java @@ -11,13 +11,15 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class IntraProfile { + private String intraId; private URL imageUrl; private List achievements; @Builder - public IntraProfile(URL imageUrl, List achievements) { + public IntraProfile(String intraId, URL imageUrl, List achievements) { + this.intraId = intraId; this.imageUrl = imageUrl; this.achievements = achievements; } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java index 8031e3561..86e295a10 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java @@ -10,13 +10,15 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class IntraProfileResponse { + String login; IntraImage image; List achievements; @Builder - public IntraProfileResponse(IntraImage image, List achievements) { + public IntraProfileResponse(String login, IntraImage image, List achievements) { + this.login = login; this.image = image; this.achievements = achievements; } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index c2f0549ca..96bbb7811 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -116,9 +116,9 @@ void getAgendaListSuccess() { int nonOfficialSize = 6; List agendas = new ArrayList<>(); IntStream.range(0, officialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(true) - .deadline(LocalDateTime.now().plusDays(i + 3)).build())); + .deadline(LocalDateTime.now().plusDays(i + 5)).build())); IntStream.range(0, nonOfficialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(false) - .deadline(LocalDateTime.now().plusDays(i + 3)).build())); + .deadline(LocalDateTime.now().plusDays(i + 2)).build())); when(agendaRepository.findAllByStatusIs(AgendaStatus.OPEN)).thenReturn(agendas); // when diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index a01fc39ec..53cbe832b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -90,7 +90,7 @@ void beforeEach() { void test() throws Exception { //given URL url = new URL("http://localhost:8080"); - IntraProfile intraProfile = new IntraProfile(url, List.of()); + IntraProfile intraProfile = new IntraProfile(user.getIntraId(), url, List.of()); Mockito.when(intraProfileUtils.getIntraProfile(any(HttpServletResponse.class))) .thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); @@ -148,7 +148,7 @@ void beforeEach() { void getAgendaProfileSuccess() throws Exception { //given URL url = new URL("http://localhost:8080"); - IntraProfile intraProfile = new IntraProfile(url, List.of()); + IntraProfile intraProfile = new IntraProfile(user.getIntraId(), url, List.of()); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); Mockito.when(intraProfileUtils.getIntraProfile(any(String.class), any(HttpServletResponse.class))) @@ -174,7 +174,7 @@ void getAgendaProfileSuccess() throws Exception { void getAgendaProfileFailedWithInvalidIntraId() throws Exception { //given URL url = new URL("http://localhost:8080"); - IntraProfile intraProfile = new IntraProfile(url, List.of()); + IntraProfile intraProfile = new IntraProfile(user.getIntraId(), url, List.of()); HttpServletResponse res = Mockito.mock(HttpServletResponse.class); Mockito.when(intraProfileUtils.getIntraProfile(res)).thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); diff --git a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java index b83615326..9a6b6299d 100644 --- a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java +++ b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java @@ -86,6 +86,7 @@ private OAuth2AuthorizedClient requestNewClient(OAuth2AuthorizedClient client, C params.add("refresh_token", client.getRefreshToken().getTokenValue()); params.add("client_id", registration.getClientId()); params.add("client_secret", registration.getClientSecret()); + params.add("redirect_uri", registration.getRedirectUri()); List> responseBody = apiUtil.apiCall( registration.getProviderDetails().getTokenUri(), diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index cdc8162f1..e39ddf8fa 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -193,6 +193,7 @@ public enum ErrorCode { INVALID_CHECKLIST(400, "RE001", "잘못된 요청 데이터입니다."), // agenda + AUTH_NOT_VALID(401, "AG001", "인증이 유효하지 않습니다."), AGENDA_TEAM_FULL(400, "AG101", "팀이 꽉 찼습니다."), LOCATION_NOT_VALID(400, "AG102", "유효하지 않은 지역입니다."), AGENDA_AWARD_EMPTY(400, "AG103", "시상 정보가 없습니다."), @@ -216,7 +217,7 @@ public enum ErrorCode { TEAM_LEADER_FORBIDDEN(403, "AG207", "팀장이 아닙니다."), AGENDA_TEAM_FORBIDDEN(403, "AG208", "일정에 참여한 팀이 있습니다."), AGENDA_MODIFICATION_FORBIDDEN(403, "AG209", "개최자만 일정을 수정할 수 있습니다."), - AUTH_NOT_FOUND(404, "AG301", "42 정보가 만료되었습니다."), + AUTH_NOT_FOUND(404, "AG301", "42 정보를 찾을 수 없습니다."), TICKET_NOT_FOUND(404, "AG302", "해당 티켓이 존재하지 않습니다."), AGENDA_NOT_FOUND(404, "AG303", "해당 일정이 존재하지 않습니다."), NOT_SETUP_TICKET(404, "AG304", "티켓 신청이 되어있지 않습니다."), From 3898aee748f8f8eaee0d56ff390d0350ea37d995 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:01:21 +0900 Subject: [PATCH 098/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20AgendaTeamAdmin?= =?UTF-8?q?=EC=97=90=EC=84=9C=20cancel=20=EC=83=81=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=8B=9C=EC=97=90=20Confirm=20=ED=8C=80?= =?UTF-8?q?=EC=9D=BC=EA=B2=BD=EC=9A=B0=20currentTeam=EC=9D=B4=20=EC=A4=84?= =?UTF-8?q?=EC=96=B4=EB=93=A4=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EB=B0=9C=EC=83=9D=20#1004=20(#1005)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AgendaTeamAdminService.java | 8 ++ .../AgendaTeamAdminControllerTest.java | 125 +++++++++++++++--- .../service/AgendaTeamAdminServiceTest.java | 98 -------------- .../agenda/service/AgendaServiceTest.java | 2 +- .../src/main/java/gg/data/agenda/Agenda.java | 16 +++ .../main/java/gg/data/agenda/AgendaTeam.java | 4 - .../java/gg/utils/exception/ErrorCode.java | 2 + .../utils/fixture/agenda/AgendaFixture.java | 2 +- 8 files changed, 136 insertions(+), 121 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index b28db45c3..1d99a9d18 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -71,6 +71,7 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { // AgendaTeam 정보 변경 team.updateTeamAdmin(agendaTeamUpdateDto.getTeamName(), agendaTeamUpdateDto.getTeamContent(), agendaTeamUpdateDto.getTeamIsPrivate(), agendaTeamUpdateDto.getTeamStatus()); + team.getAgenda().adminConfirmTeam(team.getStatus()); team.updateLocation(agendaTeamUpdateDto.getTeamLocation(), profiles); team.acceptAward(agendaTeamUpdateDto.getTeamAward(), agendaTeamUpdateDto.getTeamAwardPriority()); @@ -95,6 +96,11 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { AgendaTeamProfile agendaTeamProfile = new AgendaTeamProfile(team, team.getAgenda(), profile); agendaTeamProfileAdminRepository.save(agendaTeamProfile); }); + + // 팀장이 없는지 확인하는 로직 + if (profiles.stream().noneMatch(profile -> profile.getProfile().getIntraId().equals(team.getLeaderIntraId()))) { + throw new NotExistException(TEAM_LEADER_NOT_FOUND); + } } @Transactional @@ -102,6 +108,8 @@ public void cancelAgendaTeam(AgendaTeam agendaTeam) { List agendaTeamProfiles = agendaTeamProfileAdminRepository .findAllByAgendaTeamAndIsExistIsTrue(agendaTeam); agendaTeamProfiles.forEach(AgendaTeamProfile::changeExistFalse); + Agenda agenda = agendaTeam.getAgenda(); + agenda.adminCancelTeam(agendaTeam.getStatus()); agendaTeam.adminCancelTeam(); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index dd9433a51..ca9328888 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -1,5 +1,6 @@ package gg.agenda.api.admin.agendateam.controller; +import static gg.data.agenda.type.Location.*; import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -236,17 +237,19 @@ class UpdateAgendaTeamAdmin { void updateAgendaTeamAdminSuccess() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); - AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + AgendaProfile seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUserAgendaProfile); List profiles = agendaProfileFixture.createAgendaProfileList(5); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) .collect(Collectors.toList()); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -271,7 +274,7 @@ void updateAgendaTeamAdminSuccess() throws Exception { } @Test - @DisplayName("Admin AgendaTeam 수정 성공 - Location을 변경할 수 없는 경우") + @DisplayName("Admin AgendaTeam 수정 실패 - Location을 변경할 수 없는 경우") void updateAgendaTeamAdminFailedWithLocation() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(Location.MIX); @@ -285,7 +288,7 @@ void updateAgendaTeamAdminFailedWithLocation() throws Exception { .collect(Collectors.toList()); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.GYEONGSAN) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.GYEONGSAN) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -314,11 +317,13 @@ void updateAgendaTeamAdminFailedWithLocation() throws Exception { void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); - AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + AgendaProfile seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUserAgendaProfile); List profiles = agendaProfileFixture.createAgendaProfileList(3); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); AgendaProfile newProfile = agendaProfileFixture.createAgendaProfile(); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) @@ -326,7 +331,7 @@ void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { updateTeamMates.add(new AgendaTeamMateReqDto(newProfile.getIntraId())); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -357,6 +362,34 @@ void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { .isTrue(); } + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - AgendaTeamStatus는 Cancel로 변경할 수 없음") + void updateAgendaTeamAdminFailedWithCancelStatus() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/agenda/admin/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + } + @Test @DisplayName("Admin AgendaTeam 수정 실패 - 이미 꽉 찬 팀에 팀원 추가하기") void updateAgendaTeamAdminFailedWithMaxPeople() throws Exception { @@ -374,7 +407,7 @@ void updateAgendaTeamAdminFailedWithMaxPeople() throws Exception { updateTeamMates.add(new AgendaTeamMateReqDto(newProfile.getIntraId())); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -392,19 +425,21 @@ void updateAgendaTeamAdminFailedWithMaxPeople() throws Exception { void updateAgendaTeamAdminSuccessWithRemoveTeammate() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); - AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + AgendaProfile seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUserAgendaProfile); List profiles = agendaProfileFixture.createAgendaProfileList(3); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); AgendaProfile wrongProfile = agendaProfileFixture.createAgendaProfile(); agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, wrongProfile); + agendaTeamProfileFixture.createAgendaTeamProfile(team, seoulUserAgendaProfile); List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) .collect(Collectors.toList()); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -440,10 +475,8 @@ void updateAgendaTeamAdminSuccessWithRemoveTeammate() throws Exception { void updateAgendaTeamAdminFailedWithRemoveLeader() throws Exception { // given Agenda agenda = agendaFixture.createAgenda(); - User user = testDataUtils.createNewUser(); - AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, user); - AgendaProfile leaderProfile = agendaProfileFixture.createAgendaProfile(user, Location.SEOUL); - agendaTeamProfileFixture.createAgendaTeamProfile(agenda, team, leaderProfile); + AgendaProfile seoulUserAgendaProfile = agendaProfileFixture.createAgendaProfile(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, seoulUserAgendaProfile); List profiles = agendaProfileFixture.createAgendaProfileList(3); profiles.forEach(profile -> agendaTeamProfileFixture .createAgendaTeamProfile(agenda, team, profile)); @@ -453,7 +486,35 @@ void updateAgendaTeamAdminFailedWithRemoveLeader() throws Exception { .collect(Collectors.toList()); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) + .teamName("newName").teamContent("newContent").teamIsPrivate(true) + .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); + String request = objectMapper.writeValueAsString(updateDto); + + // when + mockMvc.perform(patch("/agenda/admin/team") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("Admin AgendaTeam 수정 실패 - 팀장이 존재하지 않음") + void updateAgendaTeamAdminFailedWithNoLeader() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda); + List profiles = agendaProfileFixture.createAgendaProfileList(3); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(agenda, team, profile)); + + List updateTeamMates = profiles.stream() + .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) + .collect(Collectors.toList()); + AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() + .teamKey(team.getTeamKey()).teamMates(updateTeamMates) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -463,7 +524,7 @@ void updateAgendaTeamAdminFailedWithRemoveLeader() throws Exception { .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isForbidden()); + .andExpect(status().isNotFound()); } @Test @@ -481,7 +542,7 @@ void updateAgendaTeamAdminFailedWithInvalidTeamKey() throws Exception { .collect(Collectors.toList()); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(UUID.randomUUID()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -510,7 +571,7 @@ void updateAgendaTeamAdminFailedWithInvalidIntraId() throws Exception { updateTeamMates.add(new AgendaTeamMateReqDto("invalid")); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) - .teamStatus(AgendaTeamStatus.CANCEL).teamLocation(Location.MIX) + .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) .teamName("newName").teamContent("newContent").teamIsPrivate(true) .teamAward("newAward").teamAwardPriority(team.getAwardPriority() + 1).build(); String request = objectMapper.writeValueAsString(updateDto); @@ -560,6 +621,36 @@ void cancelAgendaTeamAdminSuccess() throws Exception { assertThat(result.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); assertThat(agendaTeamProfileAdminRepository .findAllByAgendaTeamAndIsExistIsTrue(result).size()).isEqualTo(0); + assertThat(agenda.getCurrentTeam()).isEqualTo(1); + } + + @Nested + @DisplayName("Admin Confirm 상태의 AgendaTeam 취소") + class CancelConfirmAgendaTeamAdmin { + @Test + @DisplayName("Admin AgendaTeam 취소 성공") + void cancelAgendaTeamAdminSuccess() throws Exception { + // given + Agenda agenda = agendaFixture.createAgenda(); + AgendaTeam team = agendaTeamFixture.createAgendaTeam(agenda, SEOUL, AgendaTeamStatus.CONFIRM); + List profiles = agendaProfileFixture.createAgendaProfileList(5); + profiles.forEach(profile -> agendaTeamProfileFixture + .createAgendaTeamProfile(team, profile)); + + // when + mockMvc.perform(patch("/agenda/admin/team/cancel") + .header("Authorization", "Bearer " + accessToken) + .param("team_key", team.getTeamKey().toString())) + .andExpect(status().isNoContent()); + AgendaTeam result = agendaTeamAdminRepository.findByTeamKey(team.getTeamKey()) + .orElseThrow(() -> new AssertionError("AgendaTeam not found")); + + // then + assertThat(result.getStatus()).isEqualTo(AgendaTeamStatus.CANCEL); + assertThat(agendaTeamProfileAdminRepository + .findAllByAgendaTeamAndIsExistIsTrue(result).size()).isEqualTo(0); + assertThat(agenda.getCurrentTeam()).isEqualTo(0); + } } @Test diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java index 03c706703..1c5f7fc63 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminServiceTest.java @@ -176,104 +176,6 @@ void getAgendaProfileListByAgendaTeamFailedWithNoTeam() { @Nested @DisplayName("Admin AgendaTeam 수정") class UpdateAgendaTeamAdmin { - - @Test - @DisplayName("Admin AgendaTeam 수정 성공") - void updateAgendaTeamAdminSuccess() { - // given - Agenda agenda = Agenda.builder().build(); - AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); - AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); - AgendaTeamProfile participant = AgendaTeamProfile.builder() - .agendaTeam(team).agenda(agenda).profile(profile).build(); - AgendaTeamMateReqDto agendaTeamMateReqDto = AgendaTeamMateReqDto.builder() - .intraId("intra").build(); - AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() - .teamKey(team.getTeamKey()).teamMates(List.of(agendaTeamMateReqDto)).build(); - when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); - when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) - .thenReturn(List.of(participant)); - - // when - agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); - - // then - verify(agendaTeamAdminRepository, times(1)) - .findByTeamKey(any(UUID.class)); - verify(agendaTeamProfileAdminRepository, times(1)) - .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); - } - - @Test - @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 추가하기") - void updateAgendaTeamAdminSuccessWithAddTeammate() { - // given - Agenda agenda = Agenda.builder().maxPeople(10).build(); - AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); - AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); - AgendaTeamProfile participant = AgendaTeamProfile.builder() - .agendaTeam(team).agenda(agenda).profile(profile).build(); - - AgendaProfile newProfile = AgendaProfile.builder().intraId("newIntra").build(); - List updateTeamMates = new ArrayList<>(); - updateTeamMates.add(AgendaTeamMateReqDto.builder() - .intraId(profile.getIntraId()).build()); - updateTeamMates.add(AgendaTeamMateReqDto.builder() - .intraId(newProfile.getIntraId()).build()); - - AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() - .teamKey(team.getTeamKey()).teamMates(updateTeamMates).build(); - - when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); - when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) - .thenReturn(List.of(participant)); - when(agendaProfileAdminRepository.findByIntraId("newIntra")) - .thenReturn(Optional.of(newProfile)); - when(agendaTeamProfileAdminRepository.save(any(AgendaTeamProfile.class))) - .thenReturn(mock(AgendaTeamProfile.class)); - - // when - agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); - - // then - verify(agendaTeamAdminRepository, times(1)) - .findByTeamKey(any(UUID.class)); - verify(agendaTeamProfileAdminRepository, times(1)) - .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); - verify(agendaProfileAdminRepository, times(1)) - .findByIntraId("newIntra"); - verify(agendaTeamProfileAdminRepository, times(1)) - .save(any(AgendaTeamProfile.class)); - } - - @Test - @DisplayName("Admin AgendaTeam 수정 성공 - 팀원 삭제하기") - void updateAgendaTeamAdminSuccessWithRemoveTeammate() { - // given - Agenda agenda = Agenda.builder().build(); - AgendaTeam team = AgendaTeam.builder().teamKey(UUID.randomUUID()).agenda(agenda).build(); - AgendaProfile profile = AgendaProfile.builder().intraId("intra").build(); - AgendaTeamProfile participant = AgendaTeamProfile.builder() - .agendaTeam(team).agenda(agenda).profile(profile).build(); - - AgendaTeamUpdateDto agendaTeamUpdateDto = AgendaTeamUpdateDto.builder() - .teamKey(team.getTeamKey()).teamMates(List.of()).build(); - - when(agendaTeamAdminRepository.findByTeamKey(any(UUID.class))).thenReturn(Optional.of(team)); - when(agendaTeamProfileAdminRepository.findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class))) - .thenReturn(List.of(participant)); - - // when - agendaTeamAdminService.updateAgendaTeam(agendaTeamUpdateDto); - - // then - verify(agendaTeamAdminRepository, times(1)) - .findByTeamKey(any(UUID.class)); - verify(agendaTeamProfileAdminRepository, times(1)) - .findAllByAgendaTeamAndIsExistIsTrue(any(AgendaTeam.class)); - assertThat(participant.getIsExist()).isFalse(); // leaveTeam() 호출 확인 - } - @Test @DisplayName("Admin AgendaTeam 수정 실패 - 존재하지 않는 Team Key") void updateAgendaTeamAdminFailedWithInvalidTeamKey() { diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java index 96bbb7811..7ac7d12d0 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agenda/service/AgendaServiceTest.java @@ -113,7 +113,7 @@ class GetAgendaListCurrent { void getAgendaListSuccess() { // given int officialSize = 3; - int nonOfficialSize = 6; + int nonOfficialSize = 3; List agendas = new ArrayList<>(); IntStream.range(0, officialSize).forEach(i -> agendas.add(Agenda.builder().isOfficial(true) .deadline(LocalDateTime.now().plusDays(i + 5)).build())); diff --git a/gg-data/src/main/java/gg/data/agenda/Agenda.java b/gg-data/src/main/java/gg/data/agenda/Agenda.java index 8eaeb2158..11c11b025 100644 --- a/gg-data/src/main/java/gg/data/agenda/Agenda.java +++ b/gg-data/src/main/java/gg/data/agenda/Agenda.java @@ -23,6 +23,7 @@ import gg.data.agenda.type.AgendaStatus; import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; +import gg.utils.exception.custom.BusinessException; import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; @@ -320,4 +321,19 @@ public void agendaStatusMustBeConfirm() { throw new InvalidParameterException(AGENDA_ALREADY_FINISHED); } } + + public void adminCancelTeam(AgendaTeamStatus status) { + if (status == AgendaTeamStatus.CONFIRM) { + this.currentTeam--; + } + } + + public void adminConfirmTeam(AgendaTeamStatus status) { + if (status == AgendaTeamStatus.CANCEL) { + throw new BusinessException(AGENDA_TEAM_CANCEL_FAIL); + } + if (status == AgendaTeamStatus.CONFIRM) { + this.currentTeam++; + } + } } diff --git a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java index aef548d31..1a69ac55a 100644 --- a/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java +++ b/gg-data/src/main/java/gg/data/agenda/AgendaTeam.java @@ -23,7 +23,6 @@ import gg.data.agenda.type.AgendaTeamStatus; import gg.data.agenda.type.Location; import gg.utils.exception.custom.BusinessException; -import gg.utils.exception.custom.ForbiddenException; import gg.utils.exception.custom.InvalidParameterException; import lombok.AccessLevel; import lombok.Builder; @@ -125,9 +124,6 @@ public void leaveTeamMate() { } public void leaveTeamMateAdmin(String intraId) { - if (intraId.equals(this.leaderIntraId)) { - throw new ForbiddenException(NOT_TEAM_MATE); - } this.mateCount--; } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index e39ddf8fa..5526b53d0 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -208,6 +208,7 @@ public enum ErrorCode { AGENDA_TEAM_ALREADY_CONFIRM(400, "AG112", "이미 확정된 팀입니다."), AGENDA_POSTER_SIZE_TOO_LARGE(400, "AG113", "포스터 사이즈가 너무 큽니다."), AGENDA_AWARD_PRIORITY_DUPLICATE(400, "AG114", "시상 우선순위가 중복됩니다."), + AGENDA_TEAM_CANCEL_FAIL(400, "AG115", "팀 취소를 이용해주세요."), AGENDA_NO_CAPACITY(403, "AG201", "해당 일정에 참여할 수 있는 팀이 꽉 찼습니다."), HOST_FORBIDDEN(403, "AG202", "개최자는 팀을 생성할 수 없습니다."), TICKET_NOT_EXIST(403, "AG203", "보유한 티켓이 부족합니다."), @@ -225,6 +226,7 @@ public enum ErrorCode { AGENDA_PROFILE_NOT_FOUND(404, "AG306", "프로필이 존재하지 않습니다."), POINT_HISTORY_NOT_FOUND(404, "AG307", "기부 내역이 존재하지 않습니다."), AGENDA_ANNOUNCEMENT_NOT_FOUND(404, "AG308", "공지사항이 존재하지 않습니다."), + TEAM_LEADER_NOT_FOUND(404, "AG309", "팀장이 존재하지 않습니다."), TEAM_NAME_EXIST(409, "AG401", "이미 존재하는 팀 이름입니다."), ALREADY_TICKET_SETUP(409, "AG402", "이미 티켓 신청이 되어있습니다."), AGENDA_DOES_NOT_CONFIRM(409, "AG403", "확정되지 않은 일정입니다."), diff --git a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java index c99385bf5..23affa18b 100644 --- a/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java +++ b/gg-utils/src/testFixtures/java/gg/utils/fixture/agenda/AgendaFixture.java @@ -28,7 +28,7 @@ public Agenda createAgenda() { .endTime(LocalDateTime.now().plusDays(6)) .minTeam(2) .maxTeam(5) - .currentTeam(0) + .currentTeam(1) .minPeople(1) .maxPeople(6) .status(OPEN) From 368be131034acc772e5949997b33ca47802f53d6 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Sun, 8 Sep 2024 02:32:35 +0900 Subject: [PATCH 099/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20AdminTeamUpdate?= =?UTF-8?q?=EC=8B=9C=20=ED=8C=80=EC=9E=A5=20=EB=B9=84=EA=B5=90=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20#1006=20(#1007)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/admin/agendateam/service/AgendaTeamAdminService.java | 2 +- .../agendateam/controller/AgendaTeamAdminControllerTest.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java index 1d99a9d18..7fd7cde5f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/admin/agendateam/service/AgendaTeamAdminService.java @@ -98,7 +98,7 @@ public void updateAgendaTeam(AgendaTeamUpdateDto agendaTeamUpdateDto) { }); // 팀장이 없는지 확인하는 로직 - if (profiles.stream().noneMatch(profile -> profile.getProfile().getIntraId().equals(team.getLeaderIntraId()))) { + if (!updatedTeamMates.contains(team.getLeaderIntraId())) { throw new NotExistException(TEAM_LEADER_NOT_FOUND); } } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java index ca9328888..27b777c1b 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/admin/agendateam/controller/AgendaTeamAdminControllerTest.java @@ -247,6 +247,7 @@ void updateAgendaTeamAdminSuccess() throws Exception { List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto(seoulUserAgendaProfile.getIntraId())); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) @@ -328,6 +329,7 @@ void updateAgendaTeamAdminSuccessWithAddTeammate() throws Exception { List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto(seoulUserAgendaProfile.getIntraId())); updateTeamMates.add(new AgendaTeamMateReqDto(newProfile.getIntraId())); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) @@ -437,6 +439,7 @@ void updateAgendaTeamAdminSuccessWithRemoveTeammate() throws Exception { List updateTeamMates = profiles.stream() .map(profile -> new AgendaTeamMateReqDto(profile.getIntraId())) .collect(Collectors.toList()); + updateTeamMates.add(new AgendaTeamMateReqDto(seoulUserAgendaProfile.getIntraId())); AgendaTeamUpdateDto updateDto = AgendaTeamUpdateDto.builder() .teamKey(team.getTeamKey()).teamMates(updateTeamMates) .teamStatus(AgendaTeamStatus.CONFIRM).teamLocation(Location.MIX) From 7cc4008a8911303f98dca50de277fc390a67a87a Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:32:27 +0900 Subject: [PATCH 100/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20TicketSetup=20?= =?UTF-8?q?=EC=9C=A0=EB=AC=B4=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=84=EB=B6=80=20False=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=EA=B0=80=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20#1008=20(#1009)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AgendaProfileController.java | 2 +- .../ticket/controller/TicketController.java | 4 +-- .../response/TicketCountResDto.java | 4 +-- .../user/ticket/service/TicketService.java | 11 ++++---- .../api/user/ticket/TicketControllerTest.java | 27 ++++++++++++++++--- .../java/gg/repo/agenda/TicketRepository.java | 2 ++ 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 09f8ff60b..0e3c67745 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -77,7 +77,7 @@ public ResponseEntity myAgendaProfileInfoDetails public ResponseEntity myAgendaProfileDetails( @Login @Parameter(hidden = true) UserDto user, HttpServletResponse response) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); - int ticketCount = ticketService.findTicketList(profile).size(); + int ticketCount = ticketService.findUsedTrueApproveTrueTicketList(profile).size(); IntraProfile intraProfile = intraProfileUtils.getIntraProfile(response); MyAgendaProfileDetailsResDto agendaProfileDetails = MyAgendaProfileDetailsResDto.toDto( profile, ticketCount, intraProfile); diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 0a5b8f680..1c6b503aa 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -66,13 +66,13 @@ public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login User @GetMapping public ResponseEntity ticketCountFind(@Parameter(hidden = true) @Login UserDto user) { AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); - List tickets = ticketService.findTicketList(profile); + List tickets = ticketService.findUsedFalseTicketList(profile); long approvedCount = tickets.stream() .filter(Ticket::getIsApproved) .count(); boolean setupTicket = tickets.size() > approvedCount; - return ResponseEntity.ok(new TicketCountResDto(tickets.size(), setupTicket)); + return ResponseEntity.ok(new TicketCountResDto(approvedCount, setupTicket)); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java index 60d153d73..bbf872f53 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/response/TicketCountResDto.java @@ -6,10 +6,10 @@ @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class TicketCountResDto { - private int ticketCount; + private long ticketCount; private boolean setupTicket; - public TicketCountResDto(int ticketCount, boolean setupTicket) { + public TicketCountResDto(long ticketCount, boolean setupTicket) { this.ticketCount = ticketCount; this.setupTicket = setupTicket; } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 11cd7fde7..e69cf6613 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -17,14 +17,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.HttpClientErrorException; -import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.auth.FortyTwoAuthUtil; import gg.auth.UserDto; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; import gg.data.agenda.Ticket; import gg.repo.agenda.AgendaProfileRepository; -import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.TicketRepository; import gg.utils.DateTimeUtil; import gg.utils.exception.custom.DuplicationException; @@ -38,8 +36,6 @@ public class TicketService { private final ApiUtil apiUtil; private final FortyTwoAuthUtil fortyTwoAuthUtil; private final TicketRepository ticketRepository; - private final AgendaRepository agendaRepository; - private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileRepository agendaProfileRepository; @Value("https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id") @@ -74,10 +70,15 @@ public void addTicketSetup(UserDto user) { * @return 티켓 수 */ @Transactional(readOnly = true) - public List findTicketList(AgendaProfile profile) { + public List findUsedTrueApproveTrueTicketList(AgendaProfile profile) { return ticketRepository.findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(profile); } + @Transactional(readOnly = true) + public List findUsedFalseTicketList(AgendaProfile profile) { + return ticketRepository.findByAgendaProfileAndIsUsedFalse(profile); + } + /** * 티켓 승인/거절 * @param profile 사용자 정보 diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index dcfcb2c59..d8839894e 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -141,8 +141,8 @@ void beforeEach() { } @Test - @DisplayName("200 티켓 개수 확인 성공") - void findTicketCountSuccess() throws Exception { + @DisplayName("200 티켓 개수 확인 성공 및 setupTicket 확인") + void findTicketCountSetupTrueSuccess() throws Exception { //given ticketFixture.createTicket(seoulUserAgendaProfile); ticketFixture.createTicket(seoulUserAgendaProfile); @@ -158,6 +158,27 @@ void findTicketCountSuccess() throws Exception { TicketCountResDto result = objectMapper.readValue(res, TicketCountResDto.class); //then assertThat(result.getTicketCount()).isEqualTo(2); + assertThat(result.isSetupTicket()).isTrue(); + } + + @Test + @DisplayName("200 티켓 개수 확인 성공 및 setupTicket 확인") + void findTicketCountSetupFalseSuccess() throws Exception { + //given + ticketFixture.createTicket(seoulUserAgendaProfile); + ticketFixture.createTicket(seoulUserAgendaProfile); + + //when + String res = mockMvc.perform( + get("/agenda/ticket") + .header("Authorization", "Bearer " + seoulUserAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + TicketCountResDto result = objectMapper.readValue(res, TicketCountResDto.class); + //then + assertThat(result.getTicketCount()).isEqualTo(2); + assertThat(result.isSetupTicket()).isFalse(); } @Test @@ -269,7 +290,7 @@ void findTicketHistorySuccessToUsed() throws Exception { .param("size", String.valueOf(req.getSize()))) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); PageResponseDto pageResponseDto = objectMapper.readValue(res, new TypeReference<>() { - }); + }); List result = pageResponseDto.getContent(); //then diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 3c9c9b957..9037062ed 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -21,4 +21,6 @@ Optional findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByC Page findByAgendaProfileId(Long agendaProfileId, Pageable pageable); List findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(AgendaProfile agendaProfile); + + List findByAgendaProfileAndIsUsedFalse(AgendaProfile agendaProfile); } From ae2af959faaf2de6db3338da221b1cabef070ff4 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:36:19 +0900 Subject: [PATCH 101/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=207=EA=B8=B0=20RE?= =?UTF-8?q?ADME=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20#1010=20(#1011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 124 +++++++++++++++++++++++++++++++++++++++++----------- codecov.yml | 2 + 2 files changed, 101 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 98b8f9924..31070d082 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ https://gg.42seoul.kr/ - ## ⚡️ 프로젝트 소개 + 42 서울 내에서 탁구 경기 매칭, 전적, 상점 서비스를 제공하는 프로젝트 입니다.
향후 추가 서비스 확장 예정 @@ -27,12 +27,13 @@ https://gg.42seoul.kr/ - ## ⚡️ 프로젝트 관리 + ## ⚡️ 프로젝트 개발기간 + - 3기: 2023.04.16 ~ 2023.06.23 - 4기: 2023.08.01 ~ 2023.09.21 @@ -42,31 +43,38 @@ https://gg.42seoul.kr/ - 6기 : 2023.02.01 ~ 2024.05.10 ## ⚡️ 프로젝트 아키텍처 -![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) - +![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) ## ⚡️ 팀소개 + ### 3기 +
3기 진행 사항
### ⚡️⚡ 로그인 연동 추가 -- v1에서 지원하지 않던 카카오계정 연동 기능 추가(좌 : v1, 우: v2)

- loginv1     - loginv2     +- v1에서 지원하지 않던 카카오계정 연동 기능 추가(좌 : v1, 우: v2)

+ loginv1 +      + loginv2 +      ### ⚡️⚡ DB table 구조 변경 + - v1에서 확장을 위해 열어둔 구조나 테이블마다 여러 곳에 있던 중복된 속성 제거 - v1 -> v2 테이블 수 감소 : 14 -> 12 -erdv1     +erdv1 +    
-erdv2     +erdv2 +     ### ⚡️⚡ 게임추가 기능 + - v1에서 1개의 예약만 되던 것에서 최대 3개까지 예약을 잡을 수 있도록 변경

@@ -74,15 +82,19 @@ https://gg.42seoul.kr/
### ⚡️⚡ 도커 도입 + - v2에서 도커 도입을 통해 컨테이너를 통한 서버 관리 도입 -
+
+
dockerPs    
### ⚡️⚡ 모니터링 도입 + - grafana를 통한 서버 모니터링 도입 -
+
+
dockerPs    
@@ -118,33 +130,37 @@ https://gg.42seoul.kr/ - - ### 4기 +
4기 진행 사항
### ⚡️⚡ DB table 구조 변경 + - 상점, 티어 등 서비스 확장을 위한 DB 재설계 -ERD V3 + ERD V3 ### ⚡️⚡ 재화 시스템 추가 + - 출석, 게임 승패에 연관해 재화 시스템 추가 -attendance + attendance ### ⚡️⚡ 상점, 아이템 서비스 추가 + - 유저 요구사항을 반영한 기능 확장 -스크린샷 2023-09-23 오후 11 48 01 -스크린샷 2023-09-23 오후 11 48 18 + 스크린샷 2023-09-23 오후 11 48 01 + 스크린샷 2023-09-23 오후 11 48 18 ### ⚡️⚡ 티어 시스템 추가 + - 랭킹전 활성화를 위한 티어 시스템 추가 -tier + tier ### ⚡️⚡ 관리자 페이지 구현 + - 원활한 운영을 위한 관리자 기능 추가 -admin + admin
@@ -177,28 +193,38 @@ https://gg.42seoul.kr/ ### 5기 +
5기 진행 사항
### ⚡️⚡ 토너먼트 개발 + 5th-tournament ### ⚡️⚡ 테스트 커버리지 개선 (2024-03-19 기준) + ### 전체 68% -> 74% + 5th-test-coverage-total ### 단위 테스트 0% -> 30% -5th-test-coverage-unit +5th-test-coverage-unit ### ⚡️⚡ 아키텍처 변경 + ### BEFORE - systemArchitecture     + +systemArchitecture +     + ### AFTER - ![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) + +![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) ### ⚡️⚡ DB table 구조 변경 + ![image](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/d4c68d74-590c-41db-9c47-0bdd4f249bc3) @@ -232,24 +258,27 @@ https://gg.42seoul.kr/ ### 6기 +
6기 진행 사항
### ⚡️⚡ 파티 서비스 개발 -42party +42party ### ⚡️⚡ 테스트 커버리지 개선 (2024-04-16 기준) + ### 전체 74% -> 75.9% -![integrationTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/79731062-a8f4-4575-a683-61fa5dd60a15) +![integrationTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/79731062-a8f4-4575-a683-61fa5dd60a15) ### 단위 테스트 30% -> 36.7% -![unitTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/b0e5055b-9008-40d8-b93a-3b05fdffc710) +![unitTest](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/b0e5055b-9008-40d8-b93a-3b05fdffc710) ### ⚡️⚡ DB table 구조 변경 + ![image](https://github.com/42organization/42gg.server.dev.v2/assets/79272189/c9c47670-b955-4e34-a589-c498008446f0) @@ -279,12 +308,57 @@ https://gg.42seoul.kr/ +### 7기 + +
+ 7기 진행 사항 +
+ +### ⚡️⚡ 행사 서비스 개발 + +- 42서울 내 행사를 진행할 수 있는 서비스 개발 +- 행사 개최, 참가, 결과 확인, 개인 프로필 등의 기능을 제공 +- 평가 포인트를 티켓으로 환전해 사용해 공식 대회를 참가해 칭호와 업적 등의 보상을 받을 수 있음(현재는 기부만 가능) + +### ⚡️⚡ DB table 구조 변경 + +### ⚡️⚡ 테스트 커버리지 개선 + +### 전체 75.9% -> + +### 단위 테스트 36.7% -> + +
+
+ + + + + + + + + + + + + + + + + + + +
🏓🏓🏓
정승수 @AreSain박정우 @yhames김지은 @kimjieun0301
팀장, 아젠다 서비스 개발,
테스트 커버리지 개선
아젠다 서비스 개발,
테스트 커버리지 개선
아젠다 서비스 개발,
테스트 커버리지 개선
+ ## ⚡️ 필요 파일 +
application.yml
다음과 같은 양식의 "application.yml"파일이 "src/main/resources/"경로에 필요합니다. + ``` spring: profiles: diff --git a/codecov.yml b/codecov.yml index 3cb47c47c..9fc75e6da 100644 --- a/codecov.yml +++ b/codecov.yml @@ -19,6 +19,7 @@ flags: - gg-auth/src/main/java/gg/auth - gg-utils/src/main/java/gg/utils - gg-recruit-api/src/main/java/gg/recruit/api + - gg-agenda-api/src/main/java/gg/agenda/api integrationTest: paths: - gg-pingpong-api/src/main/java/gg/pingpong/api @@ -28,3 +29,4 @@ flags: - gg-auth/src/main/java/gg/auth - gg-utils/src/main/java/gg/utils - gg-recruit-api/src/main/java/gg/recruit/api + - gg-agenda-api/src/main/java/gg/agenda/api From 7e447f34195da0bf609bfaa251839e7ad43b639c Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:18:42 +0900 Subject: [PATCH 102/103] =?UTF-8?q?=E2=9C=A8=20[Feature]=207=EA=B8=B0=20RE?= =?UTF-8?q?ADME=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20#1010=20(#1012)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31070d082..60214c684 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,40 @@ https://gg.42seoul.kr/ - 6기 : 2023.02.01 ~ 2024.05.10 +- 7기 : 2024.06.03 ~ 2024.09.10 + ## ⚡️ 프로젝트 아키텍처 -![gg-5th-architecture](https://github.com/42organization/42gg.server.dev.v2/assets/33301153/f801e7b5-d579-467b-9ad0-2bfec506dcaa) +![AwsArchitecture](https://github.com/user-attachments/assets/54da941b-a8c4-4586-9489-5e1d1085d7b8) + +## ⚡️ ERD 및 모듈 구조 + +
+ 모듈 구조도 + +모듈 구조도 +
+ +
+ 5기 + +![5기ERD](https://github.com/user-attachments/assets/0f889aaa-a39d-4062-8063-a495d6cd8863) +
+
+ 6기 + +![6기ERD](https://github.com/user-attachments/assets/4719ec57-64b3-42f8-8ada-a745f91c6444) +
+
+ Recurit + +![RecuritERD](https://github.com/user-attachments/assets/ad07f23e-2c99-4d21-b0b5-a5d47c28dcb1) +
+
+ 7기 + +![7기ERD](https://github.com/user-attachments/assets/c5a147b6-107c-4524-b656-6183dc04ccf6) +
## ⚡️ 팀소개 @@ -317,16 +348,29 @@ https://gg.42seoul.kr/ ### ⚡️⚡ 행사 서비스 개발 - 42서울 내 행사를 진행할 수 있는 서비스 개발 +![인덱스](https://github.com/user-attachments/assets/48966d80-337f-42d9-9024-b1f5392a81ab) + - 행사 개최, 참가, 결과 확인, 개인 프로필 등의 기능을 제공 +![대회목록](https://github.com/user-attachments/assets/cf5fb4b3-bcad-4e89-ab8b-3f798f3cba9f) +![상세보기](https://github.com/user-attachments/assets/f6109e2c-3a93-462c-a899-cfc35989dc20) +![대회 참가](https://github.com/user-attachments/assets/f11b5c89-ebc2-4d2d-91c7-25317d33ad2d) +![프로필](https://github.com/user-attachments/assets/f9b31b71-76f6-4bf0-9b5c-d56446e292a0) + - 평가 포인트를 티켓으로 환전해 사용해 공식 대회를 참가해 칭호와 업적 등의 보상을 받을 수 있음(현재는 기부만 가능) +![티켓 페이지](https://github.com/user-attachments/assets/fd76a962-1254-4354-a1ff-be93950d75a3) + +### ⚡️⚡ DataFlow + +![AgendaDataFlow](https://github.com/user-attachments/assets/f9fd25ee-d275-41a3-be78-501eba88df5f) ### ⚡️⚡ DB table 구조 변경 -### ⚡️⚡ 테스트 커버리지 개선 +![7기ERD](https://github.com/user-attachments/assets/e3d2e431-1154-43d6-8a48-dd2ac2e510a5) -### 전체 75.9% -> +### ⚡️⚡ 테스트 커버리지 -### 단위 테스트 36.7% -> +### 전체 75.9% -> 76.5% +![테스트 전체](https://github.com/user-attachments/assets/3c567a75-a897-483c-ba89-8c5e9caff210)
@@ -351,6 +395,7 @@ https://gg.42seoul.kr/ + ## ⚡️ 필요 파일
From c8cd3f3fd1c68229766eb108b766db60d33a2e59 Mon Sep 17 00:00:00 2001 From: seungsje <111065574+AreSain@users.noreply.github.com> Date: Sun, 15 Sep 2024 21:59:34 +0900 Subject: [PATCH 103/103] =?UTF-8?q?=F0=9F=90=9B=20[Bug]=20=ED=94=84?= =?UTF-8?q?=EB=A1=A0=ED=8A=B8=EC=99=80=20=EB=B0=B1=EC=97=94=EB=93=9C?= =?UTF-8?q?=EA=B0=84=20URL=20=EC=A3=BC=EC=86=8C=20=EB=8B=A4=EB=A6=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=8A=AC=EB=9E=99=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#1014=20(#1015)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agenda/api/utils/AgendaSlackService.java | 4 ++-- .../java/gg/agenda/api/utils/SnsMessageUtil.java | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java b/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java index 70158dcad..276427d2f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/utils/AgendaSlackService.java @@ -54,10 +54,10 @@ public void slackConfirmAgendaTeam(Agenda agenda, AgendaTeam newTeam) { agendaTeamProfiles.stream().map(atp -> atp.getProfile().getIntraId()) .forEach(intraId -> messageSender.send(intraId, message)); if (agenda.getMaxTeam() == agenda.getCurrentTeam()) { - String toHostMessage = snsMessageUtil.agendaHostMinTeamSatisfiedMessage(agenda); + String toHostMessage = snsMessageUtil.agendaHostMaxTeamSatisfiedMessage(agenda); messageSender.send(agenda.getHostIntraId(), toHostMessage); } else if (agenda.getMinTeam() == agenda.getCurrentTeam()) { - String toHostMessage = snsMessageUtil.agendaHostMaxTeamSatisfiedMessage(agenda); + String toHostMessage = snsMessageUtil.agendaHostMinTeamSatisfiedMessage(agenda); messageSender.send(agenda.getHostIntraId(), toHostMessage); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java b/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java index 290355125..f72e14f44 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/utils/SnsMessageUtil.java @@ -12,11 +12,10 @@ public class SnsMessageUtil { private static final String SUBJECT = "행사요정🧚으로부터 도착한 편지"; public String addAgendaAnnouncementMessage(Agenda agenda, AgendaAnnouncement newAnnounce) { - String link = URL + "agenda_key=" + agenda.getAgendaKey() + "/announcement/" + newAnnounce.getId(); return SUBJECT + "\n" + agenda.getTitle() + "의 새로운 공지사항이 도착했습니다." + "\n" + newAnnounce.getTitle() - + "\n" + "$$" + link + "$$"; + + "\n" + URL + "detail?" + "agenda_key=" + agenda.getAgendaKey(); } public String confirmAgendaMessage(Agenda agenda) { @@ -24,7 +23,7 @@ public String confirmAgendaMessage(Agenda agenda) { return SUBJECT + "\n" + agenda.getTitle() + "이 확정되었습니다." + "\n" + "행사가 확정되었습니다. 시작일자와 장소를 확인해주세요!" - + "\n" + "$$" + link + "$$"; + + "\n" + URL + "detail?" + "agenda_key=" + agenda.getAgendaKey(); } public String cancelAgendaMessage(Agenda agenda) { @@ -34,12 +33,12 @@ public String cancelAgendaMessage(Agenda agenda) { } public String finishAgendaMessage(Agenda agenda) { - String link = URL + "agenda_key=" + agenda.getAgendaKey(); + String link = URL + "detail?" + "agenda_key=" + agenda.getAgendaKey(); if (agenda.getIsRanking()) { return SUBJECT + "\n" + agenda.getTitle() + "이 종료되었습니다." + "\n" + "행사가 성공적으로 종료되었습니다. 수고하셨습니다!" - + "\n" + "결과 확인 $$" + link + "$$"; + + "\n" + "결과 확인 :" + link; } else { return SUBJECT + "\n" + agenda.getTitle() + "이 종료되었습니다." @@ -79,13 +78,13 @@ public String agendaHostMinTeamSatisfiedMessage(Agenda agenda) { + "\n" + agenda.getTitle() + "행사가 최소 팀 개수를 충족했습니다." + "\n" + "행사를 확정할 수 있습니다." + "\n" + "확정시엔 다른 팀들이 참가 할 수 없으니, 주의하세요!" - + "\n" + "$$" + URL + "agenda_key=" + agenda.getAgendaKey() + "$$"; + + "\n" + URL + "detail?" + "agenda_key=" + agenda.getAgendaKey(); } public String agendaHostMaxTeamSatisfiedMessage(Agenda agenda) { return SUBJECT + "\n" + agenda.getTitle() + "행사가 최대 팀 개수를 충족했습니다." + "\n" + "행사를 확정하고 진행 시간과 장소를 공지사항으로 전달해주세요." - + "\n" + "$$" + URL + "agenda_key=" + agenda.getAgendaKey() + "$$"; + + "\n" + URL + "detail?" + "agenda_key=" + agenda.getAgendaKey(); } }