From 9f66ecd1d058d80ea6bfdfe21deafbea9b8153b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=ED=83=9C?= Date: Fri, 8 Dec 2023 23:35:21 +0900 Subject: [PATCH] =?UTF-8?q?Revert=20"[BE]=20=E2=9C=A8=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=B1=84=ED=8C=85=20=EA=B5=AC=ED=98=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/server.yml | 6 +- server/Dockerfile | 8 +- server/build.gradle | 8 - server/server_log | 312 --------- server/server_log.2023-10-04.0.gz | Bin 66812 -> 0 bytes .../com/growstory/GrowstoryApplication.java | 8 +- .../account/constants/AccountGrade.java | 24 - .../domain/account/constants/Status.java | 22 - .../account/controller/AccountController.java | 28 +- .../domain/account/dto/AccountDto.java | 17 +- .../domain/account/entity/Account.java | 86 +-- .../account/service/AccountService.java | 206 +----- .../domain/alarm/constants/AlarmType.java | 23 - .../alarm/controller/AlarmController.java | 61 -- .../growstory/domain/alarm/dto/AlarmDto.java | 19 - .../growstory/domain/alarm/entity/Alarm.java | 76 --- .../alarm/repository/AlarmRepository.java | 7 - .../domain/alarm/service/AlarmService.java | 86 --- .../bannedAccount/entity/BannedAccount.java | 27 - .../board/controller/BoardController.java | 7 +- .../domain/board/dto/RequestBoardDto.java | 25 +- .../domain/board/service/BoardService.java | 14 +- .../comment/controller/CommentController.java | 2 +- .../domain/comment/dto/CommentDto.java | 15 +- .../comment/service/CommentService.java | 2 +- .../domain/guest/service/GuestService.java | 247 ------- .../controller/GuestBookController.java | 76 --- .../guestbook/dto/GuestBookRequestDto.java | 41 -- .../guestbook/dto/GuestBookResponseDto.java | 21 - .../domain/guestbook/entity/GuestBook.java | 44 -- .../repository/GuestBookRepository.java | 11 - .../guestbook/service/GuestBookService.java | 110 ---- .../images/entity/ChatMessageImage.java | 42 -- .../ChatMessageImageRepository.java | 7 - .../service/ChatMessageImageService.java | 53 -- .../images/service/JournalImageService.java | 6 +- .../journal/controller/JournalController.java | 24 +- .../domain/journal/dto/JournalDto.java | 42 +- .../journal/service/JournalService.java | 18 +- .../growstory/domain/leaf/dto/LeafDto.java | 17 +- .../growstory/domain/leaf/entity/Leaf.java | 4 +- .../domain/leaf/service/LeafService.java | 26 +- .../domain/plant_object/entity/PlantObj.java | 3 - .../plant_object/service/PlantObjService.java | 17 +- .../point/controller/PointController.java | 45 -- .../domain/point/service/PointService.java | 45 +- .../constant/ChatMessageConstants.java | 17 - .../controller/ChatMessageController.java | 48 -- .../ChatMessageStompController.java | 38 -- .../dto/ChatMessageRequestDto.java | 39 -- .../dto/ChatMessageResponseDto.java | 34 - .../chatmessage/entity/ChatMessage.java | 54 -- .../chatmessage/mapper/ChatMessageMapper.java | 31 - .../repository/ChatMessageRepository.java | 26 - .../service/ChatMessageService.java | 17 - .../service/ChatMessageServiceImpl.java | 127 ---- .../chatroom/constants/ChatRoomConstants.java | 16 - .../controller/ChatRoomController.java | 65 -- .../chatroom/dto/ChatRoomRequestDto.java | 23 - .../chatroom/dto/ChatRoomResponseDto.java | 89 --- .../chatroom/dto/CreateQnaChatRequest.java | 20 - .../chatroom/dto/CreateQnaChatResponse.java | 20 - .../chatroom/dto/EntryCheckRequestDto.java | 23 - .../chatroom/dto/EntryCheckResponseDto.java | 24 - .../chatroom/dto/SimpChatRoomRequestDto.java | 16 - .../chatroom/entity/AccountChatRoom.java | 59 -- .../qnachat/chatroom/entity/ChatRoom.java | 78 --- .../repository/AccountChatRoomRepository.java | 15 - .../repository/ChatRoomRepository.java | 22 - .../chatroom/service/ChatRoomService.java | 22 - .../chatroom/service/ChatRoomServiceImpl.java | 188 ------ .../growstory/domain/rank/RankService.java | 2 +- .../board_likes/entity/BoardLikesRank.java | 5 +- .../service/BoardLikesRankService.java | 55 +- .../rank/controller/RankController.java | 2 +- .../growstory/domain/rank/entity/Rank.java | 64 +- .../report/controller/ReportController.java | 53 -- .../domain/report/dto/ReportDto.java | 39 -- .../domain/report/entity/Report.java | 36 - .../report/repository/ReportRepository.java | 7 - .../domain/report/service/ReportService.java | 64 -- .../global/advice/GlobalExceptionAdvice.java | 11 +- .../auth/config/PasswordEncoderConfig.java | 14 - .../auth/config/SecurityConfiguration.java | 20 +- .../auth/config/SecurityCorsConfig.java | 2 +- .../growstory/global/auth/dto/LoginDto.java | 1 - .../auth/filter/JwtAuthenticationFilter.java | 1 - .../auth/filter/JwtVerificationFilter.java | 10 +- .../AccountAuthenticationSuccessHandler.java | 14 - .../handler/OAuth2AccountSuccessHandler.java | 58 +- .../global/auth/jwt/JwtTokenizer.java | 2 +- .../auth/utils/CustomAuthorityUtils.java | 7 - .../growstory/global/aws/config/S3Config.java | 6 +- .../global/aws/service/S3Uploader.java | 11 +- .../badwords/aspect/BadWordsAspect.java | 61 -- .../global/badwords/dto/ProfanityDto.java | 26 - .../global/badwords/dto/TextContainer.java | 6 - .../global/badwords/filterlist/BlackList.java | 42 -- .../global/badwords/filterlist/WhiteList.java | 7 - .../badwords/service/BlackListService.java | 99 --- .../com/growstory/global/config/S3Config.java | 31 + .../email/controller/EmailController.java | 21 +- .../growstory/global/email/dto/EmailDto.java | 16 - .../global/email/service/EmailService.java | 58 +- .../exception/BusinessLogicException.java | 11 - .../global/exception/ExceptionCode.java | 16 +- .../global/response/ErrorResponse.java | 6 - .../global/response/PageResponse.java | 32 - .../global/sse/controller/SseController.java | 39 -- .../sse/repository/SseEmitterRepository.java | 27 - .../global/sse/service/SseService.java | 82 --- .../growstory/global/utils/UriCreator.java | 11 - .../websocket/FilterChannelInterceptor.java | 132 ---- .../global/websocket/WebSocketConfig.java | 42 -- .../src/main/resources/application-prod.yml | 4 - server/src/main/resources/images/test1.jpg | Bin 139670 -> 0 bytes server/src/main/resources/images/test2.jpg | Bin 54151 -> 0 bytes .../main/resources/templates/qnaResponse.html | 82 --- .../growstory/GrowstoryApplicationTests.java | 9 - .../controller/AccountControllerTest.java | 339 ---------- .../account/service/AccountServiceTest.java | 617 ------------------ .../board/controller/BoardControllerTest.java | 77 --- .../board/service/BoardRankingTest.java | 192 ------ .../board/service/BoardServiceTest.java | 62 -- .../controller/CommentControllerTest.java | 127 ---- .../domain/journal/JournalControllerTest.java | 194 ------ .../leaf/controller/LeafControllerTest.java | 191 ------ .../domain/leaf/service/LeafServiceTest.java | 408 ------------ .../controller/PlantObjectControllerTest.java | 158 ----- .../service/PlantObjServiceTest.java | 407 ------------ .../com/growstory/domain/stubdata/Stub.java | 499 -------------- .../annotation/WithMockCustomUser.java | 19 - ...hMockCustomUserSecurityContextFactory.java | 46 -- 133 files changed, 280 insertions(+), 7499 deletions(-) delete mode 100644 server/server_log delete mode 100644 server/server_log.2023-10-04.0.gz delete mode 100644 server/src/main/java/com/growstory/domain/account/constants/AccountGrade.java delete mode 100644 server/src/main/java/com/growstory/domain/account/constants/Status.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/constants/AlarmType.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/controller/AlarmController.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/dto/AlarmDto.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/entity/Alarm.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/repository/AlarmRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/alarm/service/AlarmService.java delete mode 100644 server/src/main/java/com/growstory/domain/bannedAccount/entity/BannedAccount.java delete mode 100644 server/src/main/java/com/growstory/domain/guest/service/GuestService.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/controller/GuestBookController.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookRequestDto.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookResponseDto.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/entity/GuestBook.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/repository/GuestBookRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/guestbook/service/GuestBookService.java delete mode 100644 server/src/main/java/com/growstory/domain/images/entity/ChatMessageImage.java delete mode 100644 server/src/main/java/com/growstory/domain/images/repository/ChatMessageImageRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/images/service/ChatMessageImageService.java delete mode 100644 server/src/main/java/com/growstory/domain/point/controller/PointController.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/constant/ChatMessageConstants.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageController.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageStompController.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageRequestDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageResponseDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/entity/ChatMessage.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/mapper/ChatMessageMapper.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/repository/ChatMessageRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageService.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageServiceImpl.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/constants/ChatRoomConstants.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/controller/ChatRoomController.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomRequestDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomResponseDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatRequest.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatResponse.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckRequestDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckResponseDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/SimpChatRoomRequestDto.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/AccountChatRoom.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/ChatRoom.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/AccountChatRoomRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/ChatRoomRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomService.java delete mode 100644 server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomServiceImpl.java delete mode 100644 server/src/main/java/com/growstory/domain/report/controller/ReportController.java delete mode 100644 server/src/main/java/com/growstory/domain/report/dto/ReportDto.java delete mode 100644 server/src/main/java/com/growstory/domain/report/entity/Report.java delete mode 100644 server/src/main/java/com/growstory/domain/report/repository/ReportRepository.java delete mode 100644 server/src/main/java/com/growstory/domain/report/service/ReportService.java delete mode 100644 server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/aspect/BadWordsAspect.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/dto/ProfanityDto.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/dto/TextContainer.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/filterlist/BlackList.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/filterlist/WhiteList.java delete mode 100644 server/src/main/java/com/growstory/global/badwords/service/BlackListService.java create mode 100644 server/src/main/java/com/growstory/global/config/S3Config.java delete mode 100644 server/src/main/java/com/growstory/global/response/PageResponse.java delete mode 100644 server/src/main/java/com/growstory/global/sse/controller/SseController.java delete mode 100644 server/src/main/java/com/growstory/global/sse/repository/SseEmitterRepository.java delete mode 100644 server/src/main/java/com/growstory/global/sse/service/SseService.java delete mode 100644 server/src/main/java/com/growstory/global/websocket/FilterChannelInterceptor.java delete mode 100644 server/src/main/java/com/growstory/global/websocket/WebSocketConfig.java delete mode 100644 server/src/main/resources/images/test1.jpg delete mode 100644 server/src/main/resources/images/test2.jpg delete mode 100644 server/src/main/resources/templates/qnaResponse.html delete mode 100644 server/src/test/java/com/growstory/GrowstoryApplicationTests.java delete mode 100644 server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java delete mode 100644 server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java delete mode 100644 server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java delete mode 100644 server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java delete mode 100644 server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java delete mode 100644 server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java delete mode 100644 server/src/test/java/com/growstory/domain/stubdata/Stub.java delete mode 100644 server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java delete mode 100644 server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index c655e89a..85687775 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -16,14 +16,12 @@ jobs: AWS_SECRET_KEY: ${{secrets.AWS_SECRET_KEY}} AWS_BUCKET_NAME: ${{secrets.AWS_BUCKET_NAME}} ADMIN_EMAIL: ${{secrets.ADMIN_EMAIL}} - GUEST_EMAIL: ${{secrets.GUEST_EMAIL}} GROWSTORY_EMAIL_USERNAME: ${{secrets.GROWSTORY_EMAIL_USERNAME}} GROWSTORY_EMAIL_PASSWORD: ${{secrets.GROWSTORY_EMAIL_PASSWORD}} G_CLIENT_ID: ${{secrets.G_CLIENT_ID}} G_CLIENT_SECRET: ${{secrets.G_CLIENT_SECRET}} JWT_SECRET_KEY: ${{secrets.JWT_SECRET_KEY}} KEY_STORE_PASSWORD: ${{secrets.KEY_STORE_PASSWORD}} - EVENT_KEY: ${{secrets.EVENT_KEY}} working-directory: ./server steps: @@ -53,14 +51,12 @@ jobs: --build-arg AWS_SECRET_KEY="${{env.AWS_SECRET_KEY}}" \ --build-arg AWS_BUCKET_NAME="${{env.AWS_BUCKET_NAME}}" \ --build-arg ADMIN_EMAIL="${{env.ADMIN_EMAIL}}" \ - --build-arg GUEST_EMAIL="${{env.GUEST_EMAIL}}" \ --build-arg GROWSTORY_EMAIL_USERNAME="${{env.GROWSTORY_EMAIL_USERNAME}}" \ --build-arg GROWSTORY_EMAIL_PASSWORD="${{env.GROWSTORY_EMAIL_PASSWORD}}" \ --build-arg G_CLIENT_ID="${{env.G_CLIENT_ID}}" \ --build-arg G_CLIENT_SECRET="${{env.G_CLIENT_SECRET}}" \ --build-arg JWT_SECRET_KEY="${{env.JWT_SECRET_KEY}}" \ --build-arg KEY_STORE_PASSWORD="${{env.KEY_STORE_PASSWORD}}" \ - --build-arg EVENT_KEY="${{env.EVENT_KEY}}" \ -t growstory-cicd . docker tag growstory-cicd leest/growstory-cicd:${GITHUB_SHA::7} docker push leest/growstory-cicd:${GITHUB_SHA::7} @@ -84,4 +80,4 @@ jobs: sudo docker rm -f server sudo docker pull leest/growstory-cicd:${GITHUB_SHA::7} sudo docker tag leest/growstory-cicd:${GITHUB_SHA::7} growstory-cicd - sudo docker run -d --name server -e TZ=Asia/Seoul -p 443:443 growstory-cicd + sudo docker run -d --name server -e TZ=Asia/Seoul -p 443:443 growstory-cicd \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile index 96503c33..7e1571b5 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -6,7 +6,6 @@ ARG MYSQL_USER \ MYSQL_PASSWORD \ RDS_ENDPOINT \ ADMIN_EMAIL \ - GUEST_EMAIL \ AWS_ACCESS_KEY \ AWS_SECRET_KEY \ AWS_BUCKET_NAME \ @@ -15,15 +14,13 @@ ARG MYSQL_USER \ GROWSTORY_EMAIL_PASSWORD \ GROWSTORY_EMAIL_USERNAME \ JWT_SECRET_KEY \ - KEY_STORE_PASSWORD \ - EVENT_KEY + KEY_STORE_PASSWORD # ⭐ 'ENV' 예약어를 통해 전달받은 값을 실제 값과 매칭시켜야 한다. ENV MYSQL_USER=${MYSQL_USER} \ MYSQL_PASSWORD=${MYSQL_PASSWORD} \ RDS_ENDPOINT=${RDS_ENDPOINT} \ ADMIN_EMAIL=${ADMIN_EMAIL} \ - GUEST_EMAIL=${GUEST_EMAIL} \ AWS_ACCESS_KEY=${AWS_ACCESS_KEY} \ AWS_SECRET_KEY=${AWS_SECRET_KEY} \ AWS_BUCKET_NAME=${AWS_BUCKET_NAME} \ @@ -32,8 +29,7 @@ ENV MYSQL_USER=${MYSQL_USER} \ GROWSTORY_EMAIL_PASSWORD=${GROWSTORY_EMAIL_PASSWORD} \ GROWSTORY_EMAIL_USERNAME=${GROWSTORY_EMAIL_USERNAME} \ JWT_SECRET_KEY=${JWT_SECRET_KEY} \ - KEY_STORE_PASSWORD=${KEY_STORE_PASSWORD} \ - EVENT_KEY=${EVENT_KEY} + KEY_STORE_PASSWORD=${KEY_STORE_PASSWORD} # (2) COPY에서 사용될 경로 변수 ARG JAR_FILE=build/libs/*-SNAPSHOT.jar diff --git a/server/build.gradle b/server/build.gradle index e46d6002..f97425ee 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -62,14 +62,6 @@ dependencies { // spring-security-test 모듈 추가 testImplementation 'org.springframework.security:spring-security-test' - - // websocket - implementation 'org.springframework.boot:spring-boot-starter-websocket' -// implementation 'org.webjars:sockjs-client' -// implementation 'org.webjars:stomp-websocket' - implementation 'org.springframework:spring-messaging' - implementation 'org.springframework.security:spring-security-messaging' - } test { diff --git a/server/server_log b/server/server_log deleted file mode 100644 index bec7e7cb..00000000 --- a/server/server_log +++ /dev/null @@ -1,312 +0,0 @@ -2023-10-15 18:53:26.658 INFO 22224 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : Starting PlantObjectControllerTest using Java 11.0.18 on DESKTOP-T3EJV1L with PID 22224 (started by LG in C:\Users\LG\Desktop\Main-project\seb45_main_011\seb45_main_011\server) -2023-10-15 18:53:26.730 INFO 22224 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : The following 1 profile is active: "prod" -2023-10-15 18:53:34.328 INFO 22224 --- [Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. -2023-10-15 18:53:34.973 INFO 22224 --- [Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 578 ms. Found 18 JPA repository interfaces. -2023-10-15 18:53:44.610 WARN 22224 --- [Test worker] com.amazonaws.util.EC2MetadataUtils : Unable to retrieve the requested metadata (/latest/meta-data/instance-id). Failed to connect to service endpoint: - -com.amazonaws.SdkClientException: Failed to connect to service endpoint: - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:100) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:70) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.InstanceMetadataServiceResourceFetcher.readResource(InstanceMetadataServiceResourceFetcher.java:75) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.readResource(EC2ResourceFetcher.java:66) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.util.EC2MetadataUtils.getItems(EC2MetadataUtils.java:402) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.util.EC2MetadataUtils.getData(EC2MetadataUtils.java:371) ~[aws-java-sdk-core-1.11.792.jar:na] - at org.springframework.cloud.aws.context.support.env.AwsCloudEnvironmentCheckUtils.isRunningOnCloudEnvironment(AwsCloudEnvironmentCheckUtils.java:38) ~[spring-cloud-aws-context-2.2.6.RELEASE.jar:2.2.6.RELEASE] - at org.springframework.cloud.aws.context.annotation.OnAwsCloudEnvironmentCondition.matches(OnAwsCloudEnvironmentCondition.java:38) ~[spring-cloud-aws-context-2.2.6.RELEASE.jar:2.2.6.RELEASE] - at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:489) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:140) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:748) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:136) ~[spring-boot-test-2.7.15.jar:2.7.15] - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:141) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:90) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) ~[spring-test-5.3.29.jar:5.3.29] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] - at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) ~[na:na] - at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) ~[na:na] - at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] - at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] - at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312) ~[na:na] - at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735) ~[na:na] - at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) ~[na:na] - at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658) ~[na:na] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:362) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:283) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:282) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at java.base/java.util.Optional.orElseGet(Optional.java:369) ~[na:na] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110) ~[na:na] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90) ~[na:na] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85) ~[na:na] - at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) ~[na:na] - at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] - at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] - at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] - at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na] - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na] - at com.sun.proxy.$Proxy5.stop(Unknown Source) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) ~[na:na] - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[na:na] - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) ~[na:na] - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) ~[na:na] - at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) ~[gradle-worker.jar:na] - at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) ~[gradle-worker.jar:na] -Caused by: java.net.SocketTimeoutException: connect timed out - at java.base/java.net.PlainSocketImpl.waitForConnect(Native Method) ~[na:na] - at java.base/java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:107) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:412) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:255) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:237) ~[na:na] - at java.base/java.net.Socket.connect(Socket.java:615) ~[na:na] - at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:177) ~[na:na] - at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:507) ~[na:na] - at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:602) ~[na:na] - at java.base/sun.net.www.http.HttpClient.(HttpClient.java:275) ~[na:na] - at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:374) ~[na:na] - at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:395) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1258) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1237) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1086) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1020) ~[na:na] - at com.amazonaws.internal.ConnectionUtils.connectToEndpoint(ConnectionUtils.java:52) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:80) ~[aws-java-sdk-core-1.11.792.jar:na] - ... 109 common frames omitted - -2023-10-15 18:53:51.096 INFO 22224 --- [Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] -2023-10-15 18:53:51.431 INFO 22224 --- [Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.15.Final -2023-10-15 18:53:51.674 INFO 22224 --- [Test worker] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final} -2023-10-15 18:53:52.195 INFO 22224 --- [Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... -2023-10-15 18:53:53.832 INFO 22224 --- [Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. -2023-10-15 18:53:53.904 INFO 22224 --- [Test worker] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect -2023-10-15 18:53:54.662 WARN 22224 --- [Test worker] org.hibernate.cfg.AnnotationBinder : HHH000457: Joined inheritance hierarchy [com.growstory.domain.rank.entity.Rank] defined explicit @DiscriminatorColumn. Legacy Hibernate behavior was to ignore the @DiscriminatorColumn. However, as part of issue HHH-6911 we now apply the explicit @DiscriminatorColumn. If you would prefer the legacy behavior, enable the `hibernate.discriminator.ignore_explicit_for_joined` setting (hibernate.discriminator.ignore_explicit_for_joined=true) -2023-10-15 18:53:58.142 INFO 22224 --- [Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] -2023-10-15 18:53:58.181 INFO 22224 --- [Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' -2023-10-15 18:54:13.016 INFO 22224 --- [Test worker] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@54c2daa5, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@38a6e207, org.springframework.security.web.context.SecurityContextPersistenceFilter@af1e856, org.springframework.security.web.header.HeaderWriterFilter@4d05f9b7, org.springframework.web.filter.CorsFilter@ac2dde2, org.springframework.security.web.authentication.logout.LogoutFilter@ba1678f, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@573bb6d7, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@4d6a7d58, com.growstory.global.auth.filter.JwtVerificationFilter@4d2b73c5, com.growstory.global.auth.filter.JwtAuthenticationFilter@5fa8c4b8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5be71883, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@35d20a03, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1dfd2a1a, org.springframework.security.web.session.SessionManagementFilter@4c82fd27, org.springframework.security.web.access.ExceptionTranslationFilter@4784ce56, org.springframework.security.web.access.intercept.AuthorizationFilter@160d925f] -2023-10-15 18:54:14.710 WARN 22224 --- [Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2023-10-15 18:54:21.049 INFO 22224 --- [Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet '' -2023-10-15 18:54:21.051 INFO 22224 --- [Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet '' -2023-10-15 18:54:21.057 INFO 22224 --- [Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 4 ms -2023-10-15 18:54:21.523 INFO 22224 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : Started PlantObjectControllerTest in 56.732 seconds (JVM running for 64.9) -2023-10-15 18:54:23.780 INFO 22224 --- [SpringApplicationShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' -2023-10-15 18:54:23.788 INFO 22224 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... -2023-10-15 18:54:23.822 INFO 22224 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. -2023-10-15 19:04:43.265 INFO 25028 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : Starting PlantObjectControllerTest using Java 11.0.18 on DESKTOP-T3EJV1L with PID 25028 (started by LG in C:\Users\LG\Desktop\Main-project\seb45_main_011\seb45_main_011\server) -2023-10-15 19:04:43.278 INFO 25028 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : The following 1 profile is active: "prod" -2023-10-15 19:04:52.334 INFO 25028 --- [Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. -2023-10-15 19:04:53.537 INFO 25028 --- [Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 1162 ms. Found 18 JPA repository interfaces. -2023-10-15 19:05:04.202 WARN 25028 --- [Test worker] com.amazonaws.util.EC2MetadataUtils : Unable to retrieve the requested metadata (/latest/meta-data/instance-id). Failed to connect to service endpoint: - -com.amazonaws.SdkClientException: Failed to connect to service endpoint: - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:100) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:70) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.InstanceMetadataServiceResourceFetcher.readResource(InstanceMetadataServiceResourceFetcher.java:75) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.readResource(EC2ResourceFetcher.java:66) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.util.EC2MetadataUtils.getItems(EC2MetadataUtils.java:402) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.util.EC2MetadataUtils.getData(EC2MetadataUtils.java:371) ~[aws-java-sdk-core-1.11.792.jar:na] - at org.springframework.cloud.aws.context.support.env.AwsCloudEnvironmentCheckUtils.isRunningOnCloudEnvironment(AwsCloudEnvironmentCheckUtils.java:38) ~[spring-cloud-aws-context-2.2.6.RELEASE.jar:2.2.6.RELEASE] - at org.springframework.cloud.aws.context.annotation.OnAwsCloudEnvironmentCondition.matches(OnAwsCloudEnvironmentCondition.java:38) ~[spring-cloud-aws-context-2.2.6.RELEASE.jar:2.2.6.RELEASE] - at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:489) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:140) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:748) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.29.jar:5.3.29] - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.15.jar:2.7.15] - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:136) ~[spring-boot-test-2.7.15.jar:2.7.15] - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:141) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:90) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) ~[spring-test-5.3.29.jar:5.3.29] - at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138) ~[spring-test-5.3.29.jar:5.3.29] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] - at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) ~[na:na] - at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) ~[na:na] - at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] - at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] - at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312) ~[na:na] - at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735) ~[na:na] - at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) ~[na:na] - at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658) ~[na:na] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:362) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:283) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:282) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at java.base/java.util.Optional.orElseGet(Optional.java:369) ~[na:na] - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66) ~[junit-jupiter-engine-5.8.2.jar:5.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) ~[na:na] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) ~[junit-platform-launcher-1.8.2.jar:1.8.2] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110) ~[na:na] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90) ~[na:na] - at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85) ~[na:na] - at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) ~[na:na] - at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] - at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] - at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] - at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na] - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na] - at com.sun.proxy.$Proxy5.stop(Unknown Source) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) ~[na:na] - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) ~[na:na] - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[na:na] - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) ~[na:na] - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) ~[na:na] - at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) ~[gradle-worker.jar:na] - at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) ~[gradle-worker.jar:na] -Caused by: java.net.SocketTimeoutException: connect timed out - at java.base/java.net.PlainSocketImpl.waitForConnect(Native Method) ~[na:na] - at java.base/java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:107) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:412) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:255) ~[na:na] - at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:237) ~[na:na] - at java.base/java.net.Socket.connect(Socket.java:615) ~[na:na] - at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:177) ~[na:na] - at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:507) ~[na:na] - at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:602) ~[na:na] - at java.base/sun.net.www.http.HttpClient.(HttpClient.java:275) ~[na:na] - at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:374) ~[na:na] - at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:395) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1258) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1237) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1086) ~[na:na] - at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1020) ~[na:na] - at com.amazonaws.internal.ConnectionUtils.connectToEndpoint(ConnectionUtils.java:52) ~[aws-java-sdk-core-1.11.792.jar:na] - at com.amazonaws.internal.EC2ResourceFetcher.doReadResource(EC2ResourceFetcher.java:80) ~[aws-java-sdk-core-1.11.792.jar:na] - ... 109 common frames omitted - -2023-10-15 19:05:10.086 INFO 25028 --- [Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] -2023-10-15 19:05:10.393 INFO 25028 --- [Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.15.Final -2023-10-15 19:05:10.540 INFO 25028 --- [Test worker] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final} -2023-10-15 19:05:11.060 INFO 25028 --- [Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... -2023-10-15 19:05:11.908 INFO 25028 --- [Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. -2023-10-15 19:05:11.964 INFO 25028 --- [Test worker] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect -2023-10-15 19:05:12.803 WARN 25028 --- [Test worker] org.hibernate.cfg.AnnotationBinder : HHH000457: Joined inheritance hierarchy [com.growstory.domain.rank.entity.Rank] defined explicit @DiscriminatorColumn. Legacy Hibernate behavior was to ignore the @DiscriminatorColumn. However, as part of issue HHH-6911 we now apply the explicit @DiscriminatorColumn. If you would prefer the legacy behavior, enable the `hibernate.discriminator.ignore_explicit_for_joined` setting (hibernate.discriminator.ignore_explicit_for_joined=true) -2023-10-15 19:05:16.390 INFO 25028 --- [Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] -2023-10-15 19:05:16.417 INFO 25028 --- [Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' -2023-10-15 19:05:25.460 INFO 25028 --- [Test worker] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@56a658d0, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@541b5225, org.springframework.security.web.context.SecurityContextPersistenceFilter@71f6eb10, org.springframework.security.web.header.HeaderWriterFilter@4ab52b06, org.springframework.web.filter.CorsFilter@68aa0a5f, org.springframework.security.web.authentication.logout.LogoutFilter@548f1320, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@3f5ca1a1, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@530fa6c8, com.growstory.global.auth.filter.JwtVerificationFilter@67334787, com.growstory.global.auth.filter.JwtAuthenticationFilter@2e3a6d7f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5b93d8c7, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4e81d32e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4abec0b0, org.springframework.security.web.session.SessionManagementFilter@2ed099fd, org.springframework.security.web.access.ExceptionTranslationFilter@5f5a8308, org.springframework.security.web.access.intercept.AuthorizationFilter@2a166e62] -2023-10-15 19:05:27.291 WARN 25028 --- [Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning -2023-10-15 19:05:33.460 INFO 25028 --- [Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet '' -2023-10-15 19:05:33.461 INFO 25028 --- [Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet '' -2023-10-15 19:05:33.466 INFO 25028 --- [Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 4 ms -2023-10-15 19:05:33.940 INFO 25028 --- [Test worker] c.g.d.p.c.PlantObjectControllerTest : Started PlantObjectControllerTest in 54.001 seconds (JVM running for 62.547) -2023-10-15 19:05:36.027 INFO 25028 --- [SpringApplicationShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' -2023-10-15 19:05:36.034 INFO 25028 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... -2023-10-15 19:05:36.071 INFO 25028 --- [SpringApplicationShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. diff --git a/server/server_log.2023-10-04.0.gz b/server/server_log.2023-10-04.0.gz deleted file mode 100644 index 8ecd26cfd992419e1de1f414277ca674193035ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66812 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 postAccount(@Valid @RequestBody AccountDto.Post accountPostDto) { - AccountDto.Response responseDto = accountService.createAccount(accountPostDto); - URI location = UriCreator.createUri(ACCOUNT_DEFAULT_URL, responseDto.getAccountId()); - + AccountDto.Response accountResponseDto = accountService.createAccount(accountPostDto); + URI location = UriCreator.createUri(ACCOUNT_DEFAULT_URL, accountResponseDto.getAccountId()); return ResponseEntity.created(location).build(); } - @Operation(summary = "회원가입", description = "게스트 계정 생성") - @PostMapping("/guest") - public ResponseEntity postAccount() { - List token = accountService.createAccount(); - URI location = UriCreator.createUri(ACCOUNT_DEFAULT_URL, Long.parseLong(token.get(2))); - - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", token.get(0)); - headers.add("Refresh", token.get(1)); - - return ResponseEntity.created(location).headers(headers).build(); - } - @Operation(summary = "프로필 사진 수정", description = "입력받은 프로필 사진으로 정보 수정") @PatchMapping(value = "/profileimage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity patchProfileImage(@RequestPart MultipartFile profileImage) { @@ -170,12 +156,4 @@ public ResponseEntity deleteAccount() { return ResponseEntity.noContent().build(); } - - @Operation(summary = "게스트 회원 탈퇴", description = "게스트 사용자 계정 삭제") - @DeleteMapping("/guest/{account-id}") - public ResponseEntity deleteAccount(@Positive @PathVariable("account-id") Long accountId){ - accountService.deleteAccount(accountId); - - return ResponseEntity.noContent().build(); - } } diff --git a/server/src/main/java/com/growstory/domain/account/dto/AccountDto.java b/server/src/main/java/com/growstory/domain/account/dto/AccountDto.java index 741e2570..6b715fd7 100644 --- a/server/src/main/java/com/growstory/domain/account/dto/AccountDto.java +++ b/server/src/main/java/com/growstory/domain/account/dto/AccountDto.java @@ -1,7 +1,6 @@ package com.growstory.domain.account.dto; import com.growstory.domain.point.entity.Point; -import com.growstory.global.badwords.dto.TextContainer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -17,7 +16,7 @@ public class AccountDto { @Getter @Builder @Schema(name = "AccountPostDto") - public static class Post implements TextContainer { + public static class Post { @NotBlank private String displayName; @@ -28,17 +27,11 @@ public static class Post implements TextContainer { @NotBlank @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).{6,}$" , message = "영문, 숫자 포함 6글자 이상의 패스워드만 허용합니다.") private String password; - - @Override - public String combineText() { - return displayName; - } } - @Getter @NoArgsConstructor - public static class DisplayNamePatch implements TextContainer { + public static class DisplayNamePatch { @NotBlank private String displayName; @@ -46,11 +39,6 @@ public static class DisplayNamePatch implements TextContainer { public DisplayNamePatch(String displayName) { this.displayName = displayName; } - - @Override - public String combineText() { - return this.displayName; - } } @Getter @@ -82,7 +70,6 @@ public static class Response { private Long accountId; private String email; private String displayName; - private String status; private String profileImageUrl; private String grade; private Point point; diff --git a/server/src/main/java/com/growstory/domain/account/entity/Account.java b/server/src/main/java/com/growstory/domain/account/entity/Account.java index 1f0f03ca..5e342dca 100644 --- a/server/src/main/java/com/growstory/domain/account/entity/Account.java +++ b/server/src/main/java/com/growstory/domain/account/entity/Account.java @@ -2,25 +2,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.growstory.domain.account.constants.AccountGrade; -import com.growstory.domain.account.constants.Status; -import com.growstory.domain.alarm.entity.Alarm; import com.growstory.domain.board.entity.Board; import com.growstory.domain.comment.entity.Comment; -import com.growstory.domain.guestbook.entity.GuestBook; import com.growstory.domain.leaf.entity.Leaf; import com.growstory.domain.likes.entity.AccountLike; import com.growstory.domain.likes.entity.BoardLike; import com.growstory.domain.plant_object.entity.PlantObj; import com.growstory.domain.point.entity.Point; -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import com.growstory.domain.report.entity.Report; import com.growstory.global.audit.BaseTimeEntity; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import javax.persistence.*; import java.util.ArrayList; @@ -33,10 +23,9 @@ public class Account extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ACCOUNT_ID") private Long accountId; - @Column(name = "EMAIL", unique = true, nullable = false, length = 100) + @Column(name = "EMAIL", unique = true, nullable = false, length = 50) private String email; @Column(name = "DISPLAY_NAME", nullable = false, length = 50) @@ -48,15 +37,10 @@ public class Account extends BaseTimeEntity { @Column(name = "PROFILE_IMAGE_URL") private String profileImageUrl; - // 신고 받은 횟수 - private int reportNums = 0; - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference private List boards = new ArrayList<>(); - // cascade = 부모를 db에서 delete하면 자식도 지워진다. - // orphan = 부모를 db에서 delete하면 자식도 지워진다. @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) private List leaves = new ArrayList<>(); @@ -83,41 +67,31 @@ public class Account extends BaseTimeEntity { @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) private List plantObjs; - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) - private List alarms = new ArrayList<>(); - - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) - private List accountChatRooms = new ArrayList<>(); - - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) - private List chatMessages = new ArrayList<>(); - - // 자신이 신고한 목록 - @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true) - private List reports = new ArrayList<>(); - - // 방명록을 받은 계정 리스트 - @OneToMany(mappedBy = "receiver", cascade = CascadeType.ALL, orphanRemoval = true) - private List receivedGuestBooks = new ArrayList<>(); - - // 방명록을 작성한 계정 리스트 - @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true) - private List writerGuestBooks = new ArrayList<>(); - @ElementCollection(fetch = FetchType.EAGER) private List roles = new ArrayList<>(); @Enumerated(EnumType.STRING) private AccountGrade accountGrade = AccountGrade.GRADE_BRONZE; - @Enumerated(EnumType.STRING) - private Status status = Status.USER; +// 식물카드개수에 의한 등급 제도 +// 50개 미만 - 브론즈 가드너 +// 50개 이상 - 실버 가드너 +// 100개 이상 - 골드 가드너 + public enum AccountGrade { + GRADE_BRONZE(1, "브론즈 가드너"), + GRADE_SILVER(2, "실버 가드너"), + GRADE_GOLD(3, "골드 가드너"); - // 출석 체크 - private Boolean attendance = false; + @Getter + private int stepNumber; - public void updateDisplayName(String displayName) { - this.displayName = displayName; + @Getter + private String stepDescription; + + AccountGrade(int stepNumber, String stepDescription) { + this.stepNumber = stepNumber; + this.stepDescription = stepDescription; + } } public void addLeaf(Leaf leaf) { @@ -132,10 +106,6 @@ public void addReceivingAccountLike(AccountLike accountLike) { receivingAccountLikes.add(accountLike); } - public void addAlarm(Alarm alarm) { - alarms.add(0, alarm); - } - public void updateGrade(AccountGrade accountGrade) { this.accountGrade = accountGrade; } @@ -146,10 +116,6 @@ public void updatePoint(Point point) { point.updateAccount(this); } - public void updateAttendance(Boolean attendance) { - this.attendance = attendance; - } - public void addBoardLike(BoardLike boardLike) { boardLikes.add(boardLike); } @@ -161,14 +127,6 @@ public void addPlantObj(PlantObj plantObj) { } } - public void addReport(Report report) { - reports.add(report); - } - - public void updateReportsNum() { - reportNums += 1; - } - public void removePlantObj(PlantObj plantObj) { this.plantObjs.remove(plantObj); } @@ -186,7 +144,7 @@ public Account(Long accountId, String email, String displayName, String password public Account(Long accountId, String email, String displayName, String password, String profileImageUrl, List boards, List leaves, List givingAccountLikes, List receivingAccountLikes, List boardLikes, List comments, - Point point, List plantObjs, List roles, AccountGrade accountGrade, Status status, int reportNums) { + Point point, List plantObjs, List roles, AccountGrade accountGrade) { this.accountId = accountId; this.email = email; this.displayName = displayName; @@ -202,7 +160,5 @@ public Account(Long accountId, String email, String displayName, String password this.plantObjs = plantObjs; this.roles = roles; this.accountGrade = accountGrade; - this.status = status; - this.reportNums = reportNums; } -} \ No newline at end of file +} diff --git a/server/src/main/java/com/growstory/domain/account/service/AccountService.java b/server/src/main/java/com/growstory/domain/account/service/AccountService.java index 73c1e8c1..68008fa8 100644 --- a/server/src/main/java/com/growstory/domain/account/service/AccountService.java +++ b/server/src/main/java/com/growstory/domain/account/service/AccountService.java @@ -1,33 +1,21 @@ package com.growstory.domain.account.service; -import com.growstory.domain.account.constants.AccountGrade; -import com.growstory.domain.account.constants.Status; import com.growstory.domain.account.dto.AccountDto; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.alarm.constants.AlarmType; -import com.growstory.domain.alarm.service.AlarmService; import com.growstory.domain.board.entity.Board; -import com.growstory.domain.guest.service.GuestService; import com.growstory.domain.images.entity.BoardImage; -import com.growstory.domain.leaf.entity.Leaf; -import com.growstory.domain.plant_object.dto.PlantObjDto; import com.growstory.domain.point.entity.Point; import com.growstory.domain.point.service.PointService; -import com.growstory.global.auth.jwt.JwtTokenizer; import com.growstory.global.auth.utils.AuthUserUtils; import com.growstory.global.auth.utils.CustomAuthorityUtils; import com.growstory.global.aws.service.S3Uploader; -import com.growstory.global.email.service.EmailService; import com.growstory.global.exception.BusinessLogicException; import com.growstory.global.exception.ExceptionCode; -import com.growstory.global.sse.service.SseService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -36,10 +24,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; -@Slf4j @Transactional @Service @RequiredArgsConstructor @@ -52,145 +41,30 @@ public class AccountService { private final PointService pointService; private final S3Uploader s3Uploader; private final AuthUserUtils authUserUtils; - private final SseService sseService; - private final AlarmService alarmService; - private final EmailService emailService; - // Guest - private final GuestService guestService; - private final JwtTokenizer jwtTokenizer; + public AccountDto.Response createAccount(AccountDto.Post accountPostDto) { + verifyExistsEmail(accountPostDto.getEmail()); - public AccountDto.Response createAccount(AccountDto.Post requestDto) { - if (verifyExistsEmail(requestDto.getEmail())) { - throw new BusinessLogicException(ExceptionCode.ACCOUNT_ALREADY_EXISTS); - } - - Status status = Status.USER; - String encryptedPassword = passwordEncoder.encode(requestDto.getPassword()); - List roles = authorityUtils.createRoles(requestDto.getEmail()); - Point point = pointService.createPoint(requestDto.getEmail()); - - //TODO: if admin@gmail.com 일때 status.admin 추가 - if (requestDto.getEmail().equals("admin@gmail.com")) status = Status.ADMIN; + String encryptedPassword = passwordEncoder.encode(accountPostDto.getPassword()); + List roles = authorityUtils.createRoles(accountPostDto.getEmail()); + Point point = pointService.createPoint(accountPostDto.getEmail()); Account savedAccount = accountRepository.save(Account.builder() - .displayName(requestDto.getDisplayName()) - .email(requestDto.getEmail()) + .displayName(accountPostDto.getDisplayName()) + .email(accountPostDto.getEmail()) .password(encryptedPassword) .point(point) .roles(roles) - .status(status) - .accountGrade(AccountGrade.GRADE_BRONZE) - .reportNums(0) + .accountGrade(Account.AccountGrade.GRADE_BRONZE) .build()); point.updateAccount(savedAccount); - alarmService.createAlarm(savedAccount.getAccountId(), AlarmType.SIGN_UP); return AccountDto.Response.builder() .accountId(savedAccount.getAccountId()) .build(); } - public List createAccount() { - Status status = Status.GUEST_USER; - List roles = authorityUtils.createRoles(" "); - Point point = pointService.createPoint("guest"); - String encryptedPassword = passwordEncoder.encode("gs123!@#"); - - - // Save Account - Account savedAccount = accountRepository.save(Account.builder() - // Guest Email: guest+8자리 난수@gmail.com - .email("guest" + emailService.getAuthCode() + "@gmail.com") - .password(encryptedPassword) - // DisplayName: Guest + 8자리 난수 - .displayName("Guest" + emailService.getAuthCode()) - .leaves(new ArrayList<>()) - .plantObjs(new ArrayList<>()) - .point(point) - .roles(roles) - .status(status) - .accountGrade(AccountGrade.GRADE_BRONZE) - .reportNums(0) - .build()); - - // Update Point - point.updateAccount(savedAccount); - - // 식물 카드 - Leaf leafA = guestService.createGuestLeaf(savedAccount, "귀염둥이 니드몬","사막에서 공수한 선인장입니다.", "https://growstory.s3.ap-northeast-2.amazonaws.com/image/guest/leaves/cactus-1842095_1280.jpg"); - Leaf leafB = guestService.createGuestLeaf(savedAccount, "가시나","예쁜 선인장이에요!! ", "https://growstory.s3.ap-northeast-2.amazonaws.com/image/guest/leaves/cactus-5434469_1280.jpg"); - - // 일지 각각의 image S3에 업로드 후 imageUrl 반환 - guestService.createGuestJournal(leafA, "니드몬 성장 일기 1일차", "물 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 2일차", "칭찬해 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 3일차", "햇빛 쫴기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 4일차", "반려 식물 병원 가는 날", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 5일차", "물 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 6일차", "영양 거름 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 7일차", "분갈이", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 8일차", "물 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 9일차", "칭찬해 주기", null); - guestService.createGuestJournal(leafA, "니드몬 성장 일기 10일차", "물 주기", null); - - // Buy Garden Object - PlantObjDto.TradeResponse plantObjA = guestService.buyProduct(savedAccount, 1L); // 벽돌 유적 - guestService.buyProduct(savedAccount, 2L); // 콜로세움 - guestService.buyProduct(savedAccount, 3L); // 잊혀진 연구소 - guestService.buyProduct(savedAccount, 4L); // 대리석 신전 - PlantObjDto.TradeResponse plantObjB = guestService.buyProduct(savedAccount, 5L); // 벚나무 - - - // Batch Garden Object - // 벽돌 유적(2x2): (6, 5) - // 벚나무(1x1): (3, 3) - guestService.saveLocation(plantObjA.getPlantObj(), plantObjB.getPlantObj()); - - - - // Connect Garden Object and Plants Card - // 식물 카드 A와 벽돌 유적 오브젝트 연결 - guestService.updateLeafConnection(1L, leafA.getLeafId()); - - List tokenList = new ArrayList<>(); - // access token, Refresh Token 발급 - String accessToken = delegateAccessToken(savedAccount); - String refreshToken = delegateRefreshToken(savedAccount); - tokenList.add(accessToken); - tokenList.add(refreshToken); - tokenList.add(String.valueOf(savedAccount.getAccountId())); - - return tokenList; - } - - private String delegateAccessToken(Account account) { - Map claims = new HashMap<>(); - claims.put("accountId", account.getAccountId().toString()); - claims.put("username", account.getEmail()); - claims.put("profileImageUrl", account.getProfileImageUrl()); - claims.put("roles", account.getRoles()); - claims.put("displayName", account.getDisplayName()); - - String subject = account.getEmail(); - Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getAccessTokenExpirationMinutes()); - String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey()); - String acceesToken = jwtTokenizer.generateAccessToken(claims, subject, expiration, base64EncodedSecretKey); - - return "Bearer " + acceesToken; - } - - private String delegateRefreshToken(Account account) { - String subject = account.getEmail(); - Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getRefreshTokenExpirationMinutes()); - String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey()); - - String refreshToken = jwtTokenizer.generateRefreshToken(subject, expiration, base64EncodedSecretKey); - - return refreshToken; - } - - public void updateProfileImage(MultipartFile profileImage) { Account findAccount = authUserUtils.getAuthUser(); @@ -202,18 +76,20 @@ public void updateProfileImage(MultipartFile profileImage) { .build()); } - public void updateDisplayName(AccountDto.DisplayNamePatch requestDto) { + public void updateDisplayName(AccountDto.DisplayNamePatch displayNamePatchDto) { Account findAccount = authUserUtils.getAuthUser(); - findAccount.updateDisplayName(requestDto.getDisplayName()); + accountRepository.save(findAccount.toBuilder() + .displayName(displayNamePatchDto.getDisplayName()) + .build()); } - public void updatePassword(AccountDto.PasswordPatch requestDto) { + public void updatePassword(AccountDto.PasswordPatch passwordPatchDto) { Account findAccount = authUserUtils.getAuthUser(); - String encryptedChangedPassword = passwordEncoder.encode(requestDto.getChangedPassword()); + String encryptedChangedPassword = passwordEncoder.encode(passwordPatchDto.getChangedPassword()); - if (!passwordEncoder.matches(requestDto.getPresentPassword(), findAccount.getPassword())) + if (!passwordEncoder.matches(passwordPatchDto.getPresentPassword(), findAccount.getPassword())) throw new BadCredentialsException("현재 비밀번호가 일치하지 않습니다."); if (findAccount.getPassword().equals(encryptedChangedPassword)) @@ -268,7 +144,7 @@ public Page getAccountCommentWrittenBoard(int page, in Account findAccount = findVerifiedAccount(accountId); List commentWrittenBoardList = findAccount.getComments().stream() .map(comment -> getBoardResponse(comment.getBoard())) - .distinct() // 같은 게시글 중복 제거 + .distinct() .collect(Collectors.toList()); int startIdx = page * size; @@ -285,42 +161,17 @@ public void deleteAccount() { accountRepository.delete(findAccount); } - - // 게스트 용 v1/accounts/{account-id} - public void deleteAccount(Long id) { - Account findAccount = findVerifiedAccount(id); - - accountRepository.delete(findAccount); - } - - // 출석 체크 - public void attendanceCheck(Account account) { - if (!account.getAttendance()) { - account.updatePoint(pointService.updatePoint(account.getPoint(), "login")); - account.updateAttendance(true); - accountRepository.save(account); - - sseService.notify(account.getAccountId(), AlarmType.DAILY_LOGIN); - } - } - - // 자정에 초기화 - @Scheduled(cron = "0 0 0 * * *") - public void attendanceReset() { - accountRepository.findAll() - .forEach(account -> account.updateAttendance(false)); - } - - public Boolean verifyPassword(AccountDto.PasswordVerify requestDto) { + public Boolean verifyPassword(AccountDto.PasswordVerify passwordVerifyDto) { Account findAccount = authUserUtils.getAuthUser(); - return passwordEncoder.matches(requestDto.getPassword(), findAccount.getPassword()); + return passwordEncoder.matches(passwordVerifyDto.getPassword(), findAccount.getPassword()); } - public Boolean verifyExistsEmail(String email) { // 입력받은 이메일의 계정이 이미 존재한다면 true + private void verifyExistsEmail(String email) { Optional findAccount = accountRepository.findByEmail(email); - return findAccount.isPresent(); + if(findAccount.isPresent()) + throw new BusinessLogicException(ExceptionCode.ACCOUNT_ALREADY_EXISTS); } @Transactional(readOnly = true) @@ -329,8 +180,7 @@ public Account findVerifiedAccount(Long accountId) { new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); } - //TODO: 리팩토링 -> AuthUserUtil - public void checkAuthIdMatching(Long accountId) { + public void isAuthIdMatching(Long accountId) { Authentication authentication = null; Map claims = null; try { @@ -346,12 +196,8 @@ public void checkAuthIdMatching(Long accountId) { } // 사용자가 일치하지 않으면 405 예외 던지기 - log.info("## login-id = {}", String.valueOf(claims.get("accountId"))); - log.info("## leafAuthorId = {}", String.valueOf(accountId)); - log.info("##" + !String.valueOf(claims.get("accountId")).equals(String.valueOf(accountId))); - if (!String.valueOf(claims.get("accountId")).equals(String.valueOf(accountId))) { + if (Long.valueOf((String) claims.get("accountId")) != accountId) throw new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_ALLOW); - } } private static AccountDto.Response getAccountResponse(Account findAccount) { diff --git a/server/src/main/java/com/growstory/domain/alarm/constants/AlarmType.java b/server/src/main/java/com/growstory/domain/alarm/constants/AlarmType.java deleted file mode 100644 index 2b7837aa..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/constants/AlarmType.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.growstory.domain.alarm.constants; - -import lombok.Getter; - -@Getter -public enum AlarmType { - REPORT_COMMENT(1, "reportComment"), - REPORT_POST(2, "reportPost"), - SIGN_UP(500, "signUp"), - DAILY_LOGIN(10, "dailyLogin"), - DAILY_QUIZ(10, "dailyQuiz"), - WRITE_POST(30, "writePost"), - WRITE_DIARY(10, "writeDiary"); - - private int point; - - private String stepDescription; - - AlarmType(int point, String stepDescription) { - this.point = point; - this.stepDescription = stepDescription; - } -} diff --git a/server/src/main/java/com/growstory/domain/alarm/controller/AlarmController.java b/server/src/main/java/com/growstory/domain/alarm/controller/AlarmController.java deleted file mode 100644 index e6aca4bf..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/controller/AlarmController.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.growstory.domain.alarm.controller; - -import com.growstory.domain.alarm.dto.AlarmDto; -import com.growstory.domain.alarm.service.AlarmService; -import com.growstory.global.constants.HttpStatusCode; -import com.growstory.global.response.SingleResponseDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.validation.constraints.Positive; -import java.util.List; - -@Tag(name = "Alarm", description = "Alarm Controller") -@Validated -@RequiredArgsConstructor -@RequestMapping("/v1/alarms") -@RestController -public class AlarmController { - private final AlarmService alarmService; - - @Operation(summary = "알림 전체 조회", description = "입력받은 사용자의 모든 알림 조회") - @GetMapping("/{account-id}") - public ResponseEntity>> getAlarms(@Positive @PathVariable("account-id") Long accountId) { - List responseDtos = alarmService.findAlarms(accountId); - - return ResponseEntity.ok(SingleResponseDto.>builder() - .status(HttpStatusCode.OK.getStatusCode()) - .message(HttpStatusCode.OK.getMessage()) - .data(responseDtos) - .build()); - } - - @Operation(summary = "알림 개별 삭제", description = "입력받은 알림 ID의 알림 삭제") - @DeleteMapping("/{alarm-id}") - public ResponseEntity deleteAlarm(@PathVariable("alarm-id") @Positive Long alarmId) { - alarmService.deleteAlarm(alarmId); - - return ResponseEntity.noContent().build(); - } - - @Operation(summary = "알림 전체 삭제", description = "입력받은 사용자의 알림 전체 삭제") - @DeleteMapping("/all/{account-id}") - public ResponseEntity deleteAlarms(@PathVariable("account-id") @Positive Long accountId) { - alarmService.deleteAlarms(accountId); - - return ResponseEntity.noContent().build(); - } - - @Operation(summary = "알림 읽음 표시", description = "입력받은 사용자의 알림 읽음으로 표시") - @PostMapping("/{account-id}") - public ResponseEntity readAlarms(@PathVariable("account-id") @Positive Long accountId) { - alarmService.readAlarms(accountId); - - return ResponseEntity.noContent().build(); - } -} \ No newline at end of file diff --git a/server/src/main/java/com/growstory/domain/alarm/dto/AlarmDto.java b/server/src/main/java/com/growstory/domain/alarm/dto/AlarmDto.java deleted file mode 100644 index 8ec5675d..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/dto/AlarmDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.growstory.domain.alarm.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Getter; - -import java.time.LocalDateTime; - -public class AlarmDto { - @Getter - @Builder - @Schema(name = "AlarmResponseDto") - public static class Response { - private Long id; - private String type; - private int num; - private Boolean isShow; - } -} diff --git a/server/src/main/java/com/growstory/domain/alarm/entity/Alarm.java b/server/src/main/java/com/growstory/domain/alarm/entity/Alarm.java deleted file mode 100644 index a748ee08..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/entity/Alarm.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.growstory.domain.alarm.entity; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.alarm.constants.AlarmType; -import com.growstory.global.audit.BaseTimeEntity; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import javax.persistence.*; - -@Getter -@RequiredArgsConstructor -@Entity -public class Alarm extends BaseTimeEntity { - // 구현 방법 SSE (server sent event) - - // 포인트 획득, 신고 알림 - // 확인되지 않은 알림이 존재하면 아이콘에 특별하게 표시 - // 아이콘을 클릭했을 경우 확인된 것으로 간주 - - // 포인트 획득은 포인트를 얻는 경우 - // 요청(출석 로그인, 게시글 등록, 일지 작성)의 응답에 알림이 추가되었다고 알려주기 - - // 신고 알림 - // 신고 받은 사람한테 현재 신고 누적 몇 회라는 알림 - - // 알림을 개별, 전체 삭제할 수 있고, 삭제하지 않은 알림은 - // (삭제 방법: 개수 제한 or 알림 생성되고 이후 시간흐름) - - // 알림 확인 api 추가하기 (isShow, true) - -// 알림 전체 조회 요청 -// req - userId -// res - id, type, num, isShow(false) - -// 알림 클릭 했을때 요청 -// 모든 알림의 isShow를 true로만 바꾸는 요청 -// -// 알림 개별 / 전체 삭제 요청 -// req -개별(알림id) / 전체(유저id) -// res - ok - -// "reportComment" num={1} /> -// "reportPost" num={2} /> -// "signup" num={500} /> -// "dailyLogin" num={10} /> -// "dailyQuiz" num={10} /> -// "writePost" num={30} /> -// "writeDiary" num={10} /> - - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long alarmId; - - private AlarmType alarmType; - - private Boolean isShow = false; - - @ManyToOne - @JoinColumn(name = "ACCOUNT_ID") - private Account account; - - public void updateIsShow(Boolean isShow) { - this.isShow = isShow; - } - - @Builder - public Alarm(Long alarmId, AlarmType alarmType, Boolean isShow, Account account) { - this.alarmId = alarmId; - this.alarmType = alarmType; - this.isShow = isShow; - this.account = account; - } -} diff --git a/server/src/main/java/com/growstory/domain/alarm/repository/AlarmRepository.java b/server/src/main/java/com/growstory/domain/alarm/repository/AlarmRepository.java deleted file mode 100644 index 620b61d3..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/repository/AlarmRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.growstory.domain.alarm.repository; - -import com.growstory.domain.alarm.entity.Alarm; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AlarmRepository extends JpaRepository { -} diff --git a/server/src/main/java/com/growstory/domain/alarm/service/AlarmService.java b/server/src/main/java/com/growstory/domain/alarm/service/AlarmService.java deleted file mode 100644 index 16e9f8a9..00000000 --- a/server/src/main/java/com/growstory/domain/alarm/service/AlarmService.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.growstory.domain.alarm.service; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.alarm.constants.AlarmType; -import com.growstory.domain.alarm.dto.AlarmDto; -import com.growstory.domain.alarm.entity.Alarm; -import com.growstory.domain.alarm.repository.AlarmRepository; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Transactional -@RequiredArgsConstructor -@Service -public class AlarmService { - private final AlarmRepository alarmRepository; - private final AccountRepository accountRepository; - - public void createAlarm(Long accountId, AlarmType alarmType) { - Account findAccount = accountRepository.findById(accountId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - Alarm savedAlarm = alarmRepository.save(Alarm.builder() - .alarmType(alarmType) - .isShow(false) - .account(findAccount) - .build()); - - findAccount.addAlarm(savedAlarm); - } - - public List findAlarms(Long accountId) { - Account findAccount = accountRepository.findById(accountId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - return findAccount.getAlarms().stream() - .map(this::getAlarmResponseDto) - .collect(Collectors.toList()); - } - - // 더티체킹 테스트해보기 - public void deleteAlarm(Long alarmId) { - Alarm findAlarm = findVerifiedAlarm(alarmId); - alarmRepository.delete(findAlarm); - - findAlarm.getAccount().getAlarms().remove(findAlarm); - } - - public void deleteAlarms(Long accountId) { - Account findAccount = accountRepository.findById(accountId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - alarmRepository.deleteAll(); - - findAccount.getAlarms().clear(); - } - - @Transactional(readOnly = true) - public Alarm findVerifiedAlarm(Long alarmId) { - return alarmRepository.findById(alarmId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ALARM_NOT_FOUND)); - } - - private AlarmDto.Response getAlarmResponseDto(Alarm findAlarm) { - return AlarmDto.Response.builder() - .id(findAlarm.getAlarmId()) - .type(findAlarm.getAlarmType().getStepDescription()) - .num(findAlarm.getAlarmType().getPoint()) - .isShow(findAlarm.getIsShow()) - .build(); - } - - public void readAlarms(Long accountId) { - Account findAccount = accountRepository.findById(accountId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - findAccount.getAlarms().forEach(alarm -> - alarm.updateIsShow(true)); - } -} diff --git a/server/src/main/java/com/growstory/domain/bannedAccount/entity/BannedAccount.java b/server/src/main/java/com/growstory/domain/bannedAccount/entity/BannedAccount.java deleted file mode 100644 index 5945b94b..00000000 --- a/server/src/main/java/com/growstory/domain/bannedAccount/entity/BannedAccount.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.growstory.domain.bannedAccount.entity; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - - -@Getter -@NoArgsConstructor -@Entity -public class BannedAccount { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long bannedAccountId; - - // 신고 받은 횟수 - private int reportNums; - - // 남은 정지 일수 - private int suspendedDays; - - private Long accountId; -} diff --git a/server/src/main/java/com/growstory/domain/board/controller/BoardController.java b/server/src/main/java/com/growstory/domain/board/controller/BoardController.java index bbdab83a..101ca614 100644 --- a/server/src/main/java/com/growstory/domain/board/controller/BoardController.java +++ b/server/src/main/java/com/growstory/domain/board/controller/BoardController.java @@ -7,6 +7,7 @@ import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.board_likes.service.BoardLikesRankService; import com.growstory.global.constants.HttpStatusCode; +import com.growstory.global.response.MultiResponseDto; import com.growstory.global.response.MultiResponseDto2; import com.growstory.global.response.SingleResponseDto; import com.growstory.global.utils.UriCreator; @@ -26,7 +27,7 @@ import java.net.URI; import java.util.List; -@Tag(name = "Boards API", description = "게시판 API") +@Tag(name = "Boards API", description = "게시판 기능") @Validated @RequiredArgsConstructor @RequestMapping("/v1/boards") @@ -68,7 +69,7 @@ public ResponseEntity> getBoard(@Positive @P public ResponseEntity> getBoards(@Positive @RequestParam(defaultValue = "1") int page, @Positive @RequestParam(defaultValue = "12") int size) { Page responseBoardDtos = boardService.findBoards(page - 1, size); - List responseBoardRankList = boardLikesRankService.findCurrentBoardLikesRanks(); + List responseBoardRankList = boardLikesRankService.findAllBoardLikesRanks(); return ResponseEntity.ok(MultiResponseDto2.builder() @@ -85,7 +86,7 @@ public ResponseEntity responseBoardDtos = boardService.findBoardsByKeyword(page - 1, size, keyword); - List responseBoardRankList = boardLikesRankService.findCurrentBoardLikesRanks(); + List responseBoardRankList = boardLikesRankService.findAllBoardLikesRanks(); return ResponseEntity.ok(MultiResponseDto2.builder() .status(HttpStatusCode.OK.getStatusCode()) diff --git a/server/src/main/java/com/growstory/domain/board/dto/RequestBoardDto.java b/server/src/main/java/com/growstory/domain/board/dto/RequestBoardDto.java index 3a8cf13a..1e497b44 100644 --- a/server/src/main/java/com/growstory/domain/board/dto/RequestBoardDto.java +++ b/server/src/main/java/com/growstory/domain/board/dto/RequestBoardDto.java @@ -2,7 +2,6 @@ import com.growstory.domain.account.entity.Account; import com.growstory.domain.board.entity.Board; -import com.growstory.global.badwords.dto.TextContainer; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -18,7 +17,7 @@ public class RequestBoardDto { @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) - public static class Post implements TextContainer { + public static class Post { @NotBlank private String title; @@ -43,21 +42,11 @@ public Board toEntity(Account account) { .account(account) .build(); } - - @Override - public String combineText() { - StringBuilder sb = new StringBuilder(); - sb.append(title+ " ").append(content+ " "); - for(String hash : hashTags) { - sb.append(hash + " "); - } - return sb.toString(); - } } @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter - public static class Patch implements TextContainer { + public static class Patch { // private Long boardId; @@ -82,15 +71,5 @@ public Patch(@Nullable String title, @Nullable String content, @Nullable List findTop3LikedBoards() { return response; } - // 좋아요 기준 상위 3개의 게시글을 랭킹과 함께 반환 + // 좋아요 기준 상위 3개의 게시글을 랭킹과 함께 반환 (🆘 추후 리팩토링) public List findTop3LikedBoardRanks() { LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7); List topBoardsWithLikes = boardRepository.findTop3LikedBoards(sevenDaysAgo); @@ -302,7 +297,7 @@ public List findTop3LikedBoardRanks() { .board(board) .likeNum(likeCount) .build(); - boardLikesRank.updateRank(uniqueLikeCounts.size()); //차등 등수 업데이트 + boardLikesRank.updateRank(uniqueLikeCounts.size()); boardLikesRanks.add(boardLikesRank); }); @@ -313,10 +308,9 @@ public List findTop3LikedBoardRanks() { private boolean checkSameLikesCondition(List boardLikesRanks) { int boardSize = boardLikesRanks.size(); - //게시글이 4개 이상이고 마지막 두 게시글의 순위가 서로 다르면 마지막 요소를 제거하고 false 반환 if(boardSize>=4 && - (boardLikesRanks.get(boardSize-1).getRankOrders().getPosition() != - boardLikesRanks.get(boardSize-2).getRankOrders().getPosition())) { + (boardLikesRanks.get(boardSize-1).getRankStatus().getRank() != + boardLikesRanks.get(boardSize-2).getRankStatus().getRank())) { boardLikesRanks.remove(boardLikesRanks.get(boardSize-1)); return false; } diff --git a/server/src/main/java/com/growstory/domain/comment/controller/CommentController.java b/server/src/main/java/com/growstory/domain/comment/controller/CommentController.java index 5f5bb17d..195adff3 100644 --- a/server/src/main/java/com/growstory/domain/comment/controller/CommentController.java +++ b/server/src/main/java/com/growstory/domain/comment/controller/CommentController.java @@ -43,7 +43,7 @@ public ResponseEntity postComment(@Positive @PathVariable("boardId") Long boa @PatchMapping("/{commentId}") public ResponseEntity patchComment(@Positive @PathVariable("commentId") Long commentId, @RequestBody CommentDto.Patch commentDto) { - commentService.updateComment(commentId, commentDto); + commentService.editComment(commentId, commentDto); return ResponseEntity.noContent().build(); } diff --git a/server/src/main/java/com/growstory/domain/comment/dto/CommentDto.java b/server/src/main/java/com/growstory/domain/comment/dto/CommentDto.java index 5cacfbd7..246da70d 100644 --- a/server/src/main/java/com/growstory/domain/comment/dto/CommentDto.java +++ b/server/src/main/java/com/growstory/domain/comment/dto/CommentDto.java @@ -3,7 +3,6 @@ import com.growstory.domain.account.entity.Account; import com.growstory.domain.board.entity.Board; import com.growstory.domain.comment.entity.Comment; -import com.growstory.global.badwords.dto.TextContainer; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -16,7 +15,7 @@ public class CommentDto { @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) - public static class Post implements TextContainer { + public static class Post { @NotBlank private String content; @@ -32,27 +31,17 @@ public Comment toEntity(Account account, Board board) { public Post(String content) { this.content = content; } - - @Override - public String combineText() { - return content; - } } @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) - public static class Patch implements TextContainer { + public static class Patch { @NotBlank private String content; public Patch(String content) { this.content = content; } - - @Override - public String combineText() { - return content; - } } diff --git a/server/src/main/java/com/growstory/domain/comment/service/CommentService.java b/server/src/main/java/com/growstory/domain/comment/service/CommentService.java index 072daff1..7224e6d5 100644 --- a/server/src/main/java/com/growstory/domain/comment/service/CommentService.java +++ b/server/src/main/java/com/growstory/domain/comment/service/CommentService.java @@ -60,7 +60,7 @@ private static List getResponseCommentDtoList(List } - public void updateComment(Long commentId, CommentDto.Patch commentDto) { + public void editComment(Long commentId, CommentDto.Patch commentDto) { findCommentsMatchCommentId(commentId); Comment comment = getVerifiedCommentByCommentId(commentId); diff --git a/server/src/main/java/com/growstory/domain/guest/service/GuestService.java b/server/src/main/java/com/growstory/domain/guest/service/GuestService.java deleted file mode 100644 index 5f184a82..00000000 --- a/server/src/main/java/com/growstory/domain/guest/service/GuestService.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.growstory.domain.guest.service; - - -import com.growstory.domain.account.constants.AccountGrade; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.images.entity.JournalImage; -import com.growstory.domain.images.repository.JournalImageRepository; -import com.growstory.domain.journal.entity.Journal; -import com.growstory.domain.journal.mapper.JournalMapper; -import com.growstory.domain.journal.repository.JournalRepository; -import com.growstory.domain.leaf.entity.Leaf; -import com.growstory.domain.leaf.repository.LeafRepository; -import com.growstory.domain.plant_object.dto.PlantObjDto; -import com.growstory.domain.plant_object.entity.PlantObj; -import com.growstory.domain.plant_object.location.dto.LocationDto; -import com.growstory.domain.plant_object.location.entity.Location; -import com.growstory.domain.plant_object.location.repository.LocationRepository; -import com.growstory.domain.plant_object.location.service.LocationService; -import com.growstory.domain.plant_object.mapper.PlantObjMapper; -import com.growstory.domain.plant_object.repository.PlantObjRepository; -import com.growstory.domain.plant_object.service.PlantObjService; -import com.growstory.domain.point.entity.Point; -import com.growstory.domain.product.entity.Product; -import com.growstory.domain.product.service.ProductService; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import javax.transaction.Transactional; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -@Service -@Transactional -@RequiredArgsConstructor -public class GuestService { - - // 식물 카드 - private final LeafRepository leafRepository; - - // 일지 - private final JournalRepository journalRepository; - private final JournalMapper journalMapper; - private final JournalImageRepository journalImageRepository; - - // 정원 - private final ProductService productService; - private final PlantObjMapper plantObjMapper; - private final PlantObjRepository plantObjRepository; - - // 정원 배치 - private final LocationRepository locationRepository; - - - public Leaf createGuestLeaf(Account account, String leafName, String content, String imageUrl) { - Leaf leaf = Leaf.builder() - .leafName(leafName) - .leafImageUrl(imageUrl) - .content(content) - .account(account) - .journals(new ArrayList<>()) - .build(); - - Leaf saveLeaf = leafRepository.save(leaf); - - account.addLeaf(saveLeaf); - account.updateGrade(updateAccountGrade(account)); -// account.updateGrade(updateAccountGrade(account)); - - return Leaf.builder() - .leafId(saveLeaf.getLeafId()) - .build(); - } - - private AccountGrade updateAccountGrade(Account findAccount) { - int leavesNum = findAccount.getLeaves().size(); - if (leavesNum < 50) { - return AccountGrade.GRADE_BRONZE; - } else if (leavesNum < 100) { - return AccountGrade.GRADE_SILVER; - } else { - return AccountGrade.GRADE_GOLD; - } - } - - - public void createGuestJournal(Leaf leaf, String title, String contents, File imageUrl) { - Journal journal = createGuestJournalWithNoImg(leaf, title, contents); - //image가 null이거나 비어있을 경우 ResponseDto로 변환하여 반환 - if(imageUrl==null) { - journalMapper.toResponseFrom(journal); - return; - } - //image가 null이 아닐 경우 이미지 업로드 및 DB 저장 - JournalImage savedJournalImage = createJournalImage(imageUrl, journal); - //imageUrl 정보 Journal에 업데이트 - journal.updateImg(savedJournalImage); - - leaf.getJournals().add(journal); - journal.updateLeaf(leaf); - - journalMapper.toResponseFrom(journalRepository.save(journal)); - } - - - private Journal createGuestJournalWithNoImg(Leaf findLeaf, String title, String contents) { - return journalRepository.save(Journal.builder() - .title(title) - .content(contents) - .leaf(findLeaf) - .journalImage(null) - .build()); - } - - - // 테이블 인스턴스 생성 및 S3 파일 업로드 - private JournalImage createJournalImage(File imageUrl, Journal journal) { - if(imageUrl == null || journal == null) - return null; - JournalImage journalImage = - JournalImage.builder() - .imageUrl("imageUrl.get") - .originName("test1") - .journal(journal) - .build(); - return journalImageRepository.save(journalImage); - } - - // POST : 유저 포인트로 오브젝트 구입 - public PlantObjDto.TradeResponse buyProduct(Account account, Long productId) { - - // 클라이언트에서 전송된 productId 기반으로 product 정보 조회 - Product findProduct = productService.findVerifiedProduct(productId); - - // 조회한 계정, 포인트, 상품정보를 바탕으로 구입 메서드 실행 - buy(account,findProduct); - - // 구입한 오브젝트 객체 생성 - PlantObj boughtPlantObj = PlantObj.builder() - .product(findProduct) - .leaf(null) - .location(new Location()) - .account(account) - .build(); - - //구입한 오브젝트를 DB에 저장 및 findAccount에 추가 - account.addPlantObj( - plantObjRepository.save( - boughtPlantObj - ) - ); - - Point afterPoint = boughtPlantObj.getAccount().getPoint(); - - // 이미 구입한 프로덕트, - return plantObjMapper.toTradeResponse(boughtPlantObj, afterPoint); - } - - private void buy(Account account, Product product) { - Point accountPoint = account.getPoint(); - int price = product.getPrice(); - int userPointScore = account.getPoint().getScore(); - if(price > userPointScore) { - throw new BusinessLogicException(ExceptionCode.NOT_ENOUGH_POINTS); - } else { // price <= userPointScore - int updatedScore = accountPoint.getScore()-price; - accountPoint.updateScore(updatedScore); - } - } - - - // POST : 오브젝트 배치 - public void saveLocation(PlantObjDto.Response plantObjA, PlantObjDto.Response plantObjB) { - List patchLocationList = new ArrayList<>(); - patchLocationList.add(PlantObjDto.PatchLocation.builder() - .plantObjId(plantObjA.getPlantObjId()) - .locationDto(LocationDto.Patch.builder() - .locationId(plantObjA.getLocation().getLocationId()) - .x(6) - .y(5) - .isInstalled(true) - .build()) - .build()); - patchLocationList.add(PlantObjDto.PatchLocation.builder() - .plantObjId(plantObjB.getPlantObjId()) - .locationDto(LocationDto.Patch.builder() - .locationId(plantObjB.getLocation().getLocationId()) - .x(3) - .y(3) - .isInstalled(true) - .build()) - .build()); - - patchLocationList.stream() - .forEach(patchLocationDto -> { - LocationDto.Patch locationPatchDto = patchLocationDto.getLocationDto(); - //프로덕트 id와 로케이션 id가 일치하지 않으면 예외 발생 -// if(patchLocationDto.getPlantObjId()!=locationPatchDto.getLocationId()) { -// throw new BusinessLogicException(ExceptionCode.LOCATION_NOT_ALLOW); -// } - if(locationPatchDto.getX()<0 || locationPatchDto.getX()>11 || - locationPatchDto.getY()<0 || locationPatchDto.getY()>7) { - throw new BusinessLogicException(ExceptionCode.INVALID_LOCATION); - } - // locationPatchDto와 기존 DB의 Location 정보가 일치하는지를 비교하여 다르다면 그 변화를 저장 - updateLocation(locationPatchDto); - }); - } - - private Location updateLocation(LocationDto.Patch locationDto) { - Location findLocation = locationRepository.findById(locationDto.getLocationId()).orElseThrow(()-> new BusinessLogicException(ExceptionCode.LOCATION_NOT_FOUND)); - - boolean isChangged = - findLocation.getX() != locationDto.getX() || findLocation.getY() != locationDto.getY() || findLocation.isInstalled() != locationDto.isInstalled(); - - if (isChangged) { - findLocation.update(locationDto.getX(), locationDto.getY(), locationDto.isInstalled()); - } - return findLocation; - } - - // PATCH : 오브젝트와 식물 카드 연결 / 해제 / 교체 - public void updateLeafConnection(Long plantObjId, Long leafId) { - PlantObj findPlantObj = findVerifiedPlantObj(plantObjId); - - if (leafId != null) { - // leafId가 null이 아닌경우 NPE에 대한 우려 없이 DB에서 조회 - Leaf findLeaf = leafRepository.findById(leafId) - .orElseThrow(()-> new BusinessLogicException(ExceptionCode.LEAF_NOT_FOUND)); - findPlantObj.updateLeaf(findLeaf); - findLeaf.updatePlantObj(findPlantObj); - } else { - Leaf beforeLeaf = findPlantObj.getLeaf(); - if (beforeLeaf != null) { - beforeLeaf.updatePlantObj(null); - } - findPlantObj.updateLeaf(null); - } - } - - private PlantObj findVerifiedPlantObj(long plantObjId) { - return plantObjRepository.findById(plantObjId).orElseThrow(() -> new BusinessLogicException(ExceptionCode.PLANT_OBJ_NOT_FOUND)); - } - -} diff --git a/server/src/main/java/com/growstory/domain/guestbook/controller/GuestBookController.java b/server/src/main/java/com/growstory/domain/guestbook/controller/GuestBookController.java deleted file mode 100644 index e3a6c741..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/controller/GuestBookController.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.growstory.domain.guestbook.controller; - - -import com.growstory.domain.guestbook.dto.GuestBookRequestDto; -import com.growstory.domain.guestbook.dto.GuestBookResponseDto; -import com.growstory.domain.guestbook.service.GuestBookService; -import com.growstory.global.constants.HttpStatusCode; -import com.growstory.global.response.MultiResponseDto; -import com.growstory.global.utils.UriCreator; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import javax.validation.constraints.Positive; -import java.net.URI; - -@Tag(name = "GuestBook API", description = "방명록 API") -@Validated -@RequiredArgsConstructor -@RequestMapping("/v1/guestbooks") -@RestController -public class GuestBookController { - - private final GuestBookService guestBookService; - - private final static String GUESTBOOK_DEFAULT_URL = "/v1/guestbooks"; - - - @Operation(summary = "Create GuestBook API", description = "방명록 추가 기능") - @PostMapping("/{accountId}") - public ResponseEntity postGuestBook(@Positive @PathVariable("accountId") Long accountId, - @Valid @RequestBody GuestBookRequestDto.Post requestDto) { - Long guestBookId = guestBookService.saveGuestBook(accountId, requestDto); - - URI location = UriCreator.createUri(GUESTBOOK_DEFAULT_URL, guestBookId); - - return ResponseEntity.created(location).build(); - } - - @Operation(summary = "Read Page GuestBook API", description = "방명록 페이지 조회 기능") - @GetMapping("{accountId}") - public ResponseEntity> readGuestBooks(@Positive @PathVariable("accountId") Long accountId, - @Positive @RequestParam(defaultValue = "1") int page, - @Positive @RequestParam(defaultValue = "12") int size) { - Page responseDtoPage = guestBookService.getGuestbookPage(accountId, page - 1, size); - - return ResponseEntity.ok(MultiResponseDto.builder() - .status(HttpStatusCode.OK.getStatusCode()) - .message(HttpStatusCode.OK.getMessage()) - .data(responseDtoPage.getContent()) - .page(responseDtoPage).build()); - } - - - @Operation(summary = "Update GuestBook API", description = "방명록 수정 기능") - @PatchMapping("{guestBookId}") - public ResponseEntity updateGuestBook(@Positive @PathVariable("guestBookId") Long guestBookId, - @Valid @RequestBody GuestBookRequestDto.Patch requestDto) { - guestBookService.updateGuestBook(guestBookId, requestDto); - - return ResponseEntity.noContent().build(); - } - - @Operation(summary = "Delete GuestBook API", description = "방명록 삭제 기능") - @DeleteMapping("/{guestBookId}") - public ResponseEntity deleteGuestBook(@Positive @PathVariable("guestBookId") Long guestBookId) { - guestBookService.deleteGuestbook(guestBookId); - - return ResponseEntity.noContent().build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookRequestDto.java b/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookRequestDto.java deleted file mode 100644 index de3dd125..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookRequestDto.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.growstory.domain.guestbook.dto; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.guestbook.entity.GuestBook; -import com.growstory.domain.plant_object.entity.PlantObj; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.NotBlank; - -public class GuestBookRequestDto { - - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - public static class Post { - @NotBlank - private String content; - - public GuestBook toEntity(Account author, Account receiver) { - return GuestBook.builder() - .content(content) - .author(author) - .receiver(receiver) - .build(); - } - - - public Post(String content) { - this.content = content; - } - } - - @Getter - @NoArgsConstructor(access = AccessLevel.PROTECTED) - public static class Patch { - - @NotBlank - private String content; - } -} diff --git a/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookResponseDto.java b/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookResponseDto.java deleted file mode 100644 index 45fc698c..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/dto/GuestBookResponseDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.growstory.domain.guestbook.dto; - - -import lombok.Builder; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@Builder -public class GuestBookResponseDto { - private Long guestbookId; - private String content; - - // account - private String displayName; - private String imageUrl; - private String accountGrade; - private LocalDateTime createdAt; - private LocalDateTime modifiedAt; -} diff --git a/server/src/main/java/com/growstory/domain/guestbook/entity/GuestBook.java b/server/src/main/java/com/growstory/domain/guestbook/entity/GuestBook.java deleted file mode 100644 index ee180e81..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/entity/GuestBook.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.growstory.domain.guestbook.entity; - -import com.growstory.domain.account.entity.Account; -import com.growstory.global.audit.BaseTimeEntity; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Entity -public class GuestBook extends BaseTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long guestbookId; - - private String content; - - @ManyToOne - @JoinColumn(name = "WRITER_ACCOUNT_ID") - private Account author; - - @ManyToOne - @JoinColumn(name = "RECEIVE_ACCOUNT_ID") - private Account receiver; - - - @Builder - public GuestBook(Long guestbookId, String content, Account author, Account receiver) { - this.guestbookId = guestbookId; - this.content = content; - this.author = author; - this.receiver = receiver; - } - - public void update(String content){ - this.content = content; - } -} - diff --git a/server/src/main/java/com/growstory/domain/guestbook/repository/GuestBookRepository.java b/server/src/main/java/com/growstory/domain/guestbook/repository/GuestBookRepository.java deleted file mode 100644 index 3608351c..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/repository/GuestBookRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.growstory.domain.guestbook.repository; - -import com.growstory.domain.guestbook.entity.GuestBook; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface GuestBookRepository extends JpaRepository { -// Optional> findGuestBooksByReceiverAccountId(Long accountId); -} diff --git a/server/src/main/java/com/growstory/domain/guestbook/service/GuestBookService.java b/server/src/main/java/com/growstory/domain/guestbook/service/GuestBookService.java deleted file mode 100644 index 60e9cadb..00000000 --- a/server/src/main/java/com/growstory/domain/guestbook/service/GuestBookService.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.growstory.domain.guestbook.service; - -import com.growstory.domain.account.dto.AccountDto; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.guestbook.dto.GuestBookRequestDto; -import com.growstory.domain.guestbook.dto.GuestBookResponseDto; -import com.growstory.domain.guestbook.entity.GuestBook; -import com.growstory.domain.guestbook.repository.GuestBookRepository; -import com.growstory.global.auth.utils.AuthUserUtils; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -@Transactional -@RequiredArgsConstructor -@Service -public class GuestBookService { - - private final AccountRepository accountRepository; - private final GuestBookRepository guestbookRepository; - private final AuthUserUtils authUserUtils; - - public Long saveGuestBook(Long accountId, GuestBookRequestDto.Post requestDto) { - // 방명록을 작성하는 사람 - Account author = authUserUtils.getAuthUser(); - - // 방명록을 받을 사용자 - Account receiver = accountRepository.findById(accountId) - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - GuestBook guestbook = guestbookRepository.save(requestDto.toEntity(author, receiver)); - - - return guestbook.getGuestbookId(); - } - - public Page getGuestbookPage(Long accountId, int page, int size) { - Account findAccount = accountRepository.findById(accountId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - List guestBookResponseDtoList = findAccount.getReceivedGuestBooks().stream() - .map(this::getGuestBookResponse) - .collect(Collectors.toList()); - - int startIdx = page * size; - int endIdx = Math.min(guestBookResponseDtoList.size(), (page + 1) * size); - return new PageImpl<>(guestBookResponseDtoList.subList(startIdx, endIdx), PageRequest.of(page, size), guestBookResponseDtoList.size()); - } - - public GuestBookResponseDto getGuestBookResponse(GuestBook guestBook) { - return GuestBookResponseDto.builder() - .guestbookId(guestBook.getGuestbookId()) - .displayName(guestBook.getAuthor().getDisplayName()) - .imageUrl(guestBook.getAuthor().getProfileImageUrl()) - .accountGrade(guestBook.getAuthor().getAccountGrade().getStepDescription()) - .createdAt(guestBook.getCreatedAt()) - .modifiedAt(guestBook.getModifiedAt()) - .content(guestBook.getContent()) - .build(); - } - - public void updateGuestBook(Long guestBookId, GuestBookRequestDto.Patch requestDto) { - findGuestBooksMatchGuestBookId(guestBookId); - - GuestBook findGuestBook = findVerifedGuestbook(guestBookId); - findGuestBook.update(requestDto.getContent()); - } - - public void deleteGuestbook(Long guestbookId) { - findGuestBooksMatchGuestBookId(guestbookId); - - GuestBook findGuestBook = findVerifedGuestbook(guestbookId); - - guestbookRepository.delete(findGuestBook); - - // 방명록 삭제 후 Account, PlantObj 업데이트 - findGuestBook.getAuthor().getWriterGuestBooks().remove(findGuestBook); - findGuestBook.getReceiver().getReceivedGuestBooks().remove(findGuestBook); - - } - - - private void findGuestBooksMatchGuestBookId(Long guestBookId) { - Account account = authUserUtils.getAuthUser(); - - boolean isGuestBook = account.getWriterGuestBooks().stream() - .map(GuestBook::getGuestbookId) - .anyMatch(id -> Objects.equals(id, guestBookId)); - - if (!isGuestBook) - throw new BusinessLogicException(ExceptionCode.GUESTBOOK_NOT_FOUND); - } - - - @Transactional(readOnly = true) - public GuestBook findVerifedGuestbook(Long guestbookId) { - return guestbookRepository.findById(guestbookId) - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.GUESTBOOK_NOT_FOUND)); - } -} diff --git a/server/src/main/java/com/growstory/domain/images/entity/ChatMessageImage.java b/server/src/main/java/com/growstory/domain/images/entity/ChatMessageImage.java deleted file mode 100644 index 59a96981..00000000 --- a/server/src/main/java/com/growstory/domain/images/entity/ChatMessageImage.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.growstory.domain.images.entity; - -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import lombok.*; - -import javax.persistence.*; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Entity -public class ChatMessageImage { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long chatImageId; - - @Column(name ="origin_name", nullable = false) - private String originName; - - @Column(name = "image_url", nullable = false) - private String imageUrl; - - @OneToOne (cascade = CascadeType.ALL) - @JoinColumn(name = "chat_message_id", nullable = false) - private ChatMessage chatMessage; - - @Builder - public ChatMessageImage(long chatImageId, String originName, String imageUrl, ChatMessage chatMessage) { - this.chatImageId = chatImageId; - this.originName = originName; - this.imageUrl = imageUrl; - this.chatMessage = chatMessage; - } - - /** - * 연관관계 메소드 - * @param chatMessage : ChatMessageImage가 참조하는 메시지 객체 - */ - public void updateChatMessage(ChatMessage chatMessage) { - this.chatMessage = chatMessage; - } -} diff --git a/server/src/main/java/com/growstory/domain/images/repository/ChatMessageImageRepository.java b/server/src/main/java/com/growstory/domain/images/repository/ChatMessageImageRepository.java deleted file mode 100644 index be74f7f4..00000000 --- a/server/src/main/java/com/growstory/domain/images/repository/ChatMessageImageRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.growstory.domain.images.repository; - -import com.growstory.domain.images.entity.ChatMessageImage; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ChatMessageImageRepository extends JpaRepository { -} diff --git a/server/src/main/java/com/growstory/domain/images/service/ChatMessageImageService.java b/server/src/main/java/com/growstory/domain/images/service/ChatMessageImageService.java deleted file mode 100644 index cd41d41f..00000000 --- a/server/src/main/java/com/growstory/domain/images/service/ChatMessageImageService.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.growstory.domain.images.service; - -import com.growstory.domain.images.entity.ChatMessageImage; -import com.growstory.domain.images.repository.ChatMessageImageRepository; -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.global.aws.service.S3Uploader; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Transactional -@Slf4j -@RequiredArgsConstructor -@Service -public class ChatMessageImageService { - - private final ChatMessageImageRepository chatMessageImageRepository; - private final S3Uploader s3Uploader; - - // 테이블 인스턴스 생성 및 S3 파일 업로드 - public ChatMessageImage createChatMessageImgWithS3(MultipartFile image, String type, ChatMessage chatMessage) { - if(image.isEmpty() || type == null || chatMessage == null) { - log.info("image:{}, type:{}, chatMessage:{}", image, type, chatMessage); - - throw new NullPointerException("챗 메세지 이미지 생성 중 NPE 발생"); - } - String imgUrl = s3Uploader.uploadImageToS3(image, type); - ChatMessageImage chatMessageImage = - ChatMessageImage.builder() - .imageUrl(imgUrl) - .originName(image.getOriginalFilename()) - .chatMessage(chatMessage) - .build(); - - return chatMessageImageRepository.save(chatMessageImage); - } - - // 테이블 인스턴스 삭제 및 S3 데이터 삭제 - public void deleteChatMessageImageWithS3(ChatMessageImage chatMessageImage, String type) { - if(chatMessageImage == null || type == null) { - log.info("chatMessageImage:{}, type:{}", chatMessageImage, type); - throw new NullPointerException("챗 메세지 이미지 삭제 중 NPE 발생"); - } - - ChatMessage chatMessage = chatMessageImage.getChatMessage(); -// chatMessage.removeChatMessageImage(chatMessageImage); - - s3Uploader.deleteImageFromS3(chatMessageImage.getImageUrl(), type); - - } -} diff --git a/server/src/main/java/com/growstory/domain/images/service/JournalImageService.java b/server/src/main/java/com/growstory/domain/images/service/JournalImageService.java index 3aa76d62..e89ac052 100644 --- a/server/src/main/java/com/growstory/domain/images/service/JournalImageService.java +++ b/server/src/main/java/com/growstory/domain/images/service/JournalImageService.java @@ -3,7 +3,6 @@ import com.growstory.domain.images.entity.JournalImage; import com.growstory.domain.images.repository.JournalImageRepository; import com.growstory.domain.journal.entity.Journal; -import com.growstory.global.auth.utils.AuthUserUtils; import com.growstory.global.aws.service.S3Uploader; import com.growstory.global.exception.BusinessLogicException; import com.growstory.global.exception.ExceptionCode; @@ -22,8 +21,7 @@ public class JournalImageService { // 테이블 인스턴스 생성 및 S3 파일 업로드 public JournalImage createJournalImgWithS3(MultipartFile image, String type, Journal journal) { - if(image.isEmpty() || type == null || journal == null) - throw new NullPointerException("저널 이미지 생성 중 NPE 발생"); + if(image.isEmpty() || type == null || journal == null) return null; String imgUrl = s3Uploader.uploadImageToS3(image, type); JournalImage journalImage = JournalImage.builder() @@ -36,7 +34,7 @@ public JournalImage createJournalImgWithS3(MultipartFile image, String type, Jou // 테이블 인스턴스 삭제 및 S3 데이터 삭제 public void deleteJournalImageWithS3(JournalImage journalImage, String type) { - if(journalImage == null || type == null) throw new NullPointerException("저널 이미지 삭제 중 NPE 발생"); + if(journalImage == null) return; Journal journal = journalImage.getJournal(); journal.removeJournalImage(journalImage); diff --git a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java index e6b37839..03617aa1 100644 --- a/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java +++ b/server/src/main/java/com/growstory/domain/journal/controller/JournalController.java @@ -1,11 +1,11 @@ package com.growstory.domain.journal.controller; +import com.growstory.domain.account.service.AccountService; import com.growstory.domain.journal.dto.JournalDto; import com.growstory.domain.journal.service.JournalService; import com.growstory.global.response.SingleResponseDto; import com.growstory.global.utils.UriCreator; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -17,7 +17,6 @@ import java.net.URI; import java.util.List; -@Slf4j @RestController @RequestMapping("/v1/leaves") @RequiredArgsConstructor @@ -25,7 +24,6 @@ public class JournalController { private final JournalService journalService; - private final UriCreator uriCreator; private static final String DEFAULT_URL = "/v1/leaves"; @@ -45,23 +43,25 @@ public ResponseEntity getJournals( // POST, 식물 일지를 등록 @PostMapping("/{leaf-id}/journals") - public ResponseEntity postJournal(@Positive @PathVariable("leaf-id") Long leafId, - @Valid @RequestPart(value = "postDto") JournalDto.Post postDto, - @RequestPart(required = false, value = "image") MultipartFile image) { - JournalDto.Response journal = journalService.createJournal(leafId, postDto, image); + public ResponseEntity postJournal(@RequestPart JournalDto.LeafAuthor leafAuthor, + @Positive @PathVariable("leaf-id") Long leafId, + @Valid @RequestPart JournalDto.Post postDto, + @RequestPart(required = false) MultipartFile image) { + JournalDto.Response journal = journalService.createJournal(leafAuthor.getAccountId(), leafId, postDto, image); - URI location = uriCreator.createUri_test(DEFAULT_URL, journal.getJournalId()); + URI location = UriCreator.createUri(DEFAULT_URL, journal.getJournalId()); return ResponseEntity.created(location).build(); } // PATCH, 식물 일지를 수정 @PatchMapping("/journals/{journal-id}") - public ResponseEntity patchJournal(@Positive @PathVariable("journal-id") Long journalId, + public ResponseEntity patchJournal(@RequestPart JournalDto.LeafAuthor leafAuthor, + @Positive @PathVariable("journal-id") Long journalId, @Valid @RequestPart JournalDto.Patch patchDto, @RequestPart(required = false) MultipartFile image) { - journalService.updateJournal(journalId, patchDto, image); + journalService.updateJournal(leafAuthor.getAccountId(), journalId, patchDto, image); return ResponseEntity.noContent().build(); } @@ -69,9 +69,9 @@ public ResponseEntity patchJournal(@Positive @PathVariable("journal- // DELETE, 식물 일지를 삭제 @DeleteMapping("/journals/{journal-id}") public ResponseEntity deleteJournal( - @RequestParam("leaf-author-id") long leafAuthorId, + @RequestBody JournalDto.LeafAuthor leafAuthor, @Positive @PathVariable("journal-id") Long journalId) { - journalService.deleteJournal(leafAuthorId, journalId); + journalService.deleteJournal(leafAuthor.getAccountId(), journalId); return ResponseEntity.noContent().build(); } diff --git a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java index 5f974335..f1b05b25 100644 --- a/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java +++ b/server/src/main/java/com/growstory/domain/journal/dto/JournalDto.java @@ -1,47 +1,32 @@ package com.growstory.domain.journal.dto; -import com.growstory.global.badwords.dto.TextContainer; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; import org.springframework.lang.Nullable; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Null; +import java.time.LocalDate; import java.time.LocalDateTime; public class JournalDto { @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class Post implements TextContainer { + public static class Post { @NotBlank String title; @NotBlank String content; - @NotNull - long leafAuthorId; - - @Override - public String combineText() { - StringBuilder sb = new StringBuilder(); - return sb.append(title+ " ").append(content).toString(); - } } @Getter - public static class Patch implements TextContainer { + public static class Patch { @Nullable String title; @Nullable String content; - @NotNull - long leafAuthorId; - - @Override - public String combineText() { - StringBuilder sb = new StringBuilder(); - return sb.append(title+ " ").append(content).toString(); - } } @Getter @@ -54,11 +39,8 @@ public static class Response { LocalDateTime createdAt; } -// @Getter -// @Builder -// @NoArgsConstructor -// @AllArgsConstructor -// public static class LeafAuthor { -// long accountId; -// } + @Getter + public static class LeafAuthor { + long accountId; + } } diff --git a/server/src/main/java/com/growstory/domain/journal/service/JournalService.java b/server/src/main/java/com/growstory/domain/journal/service/JournalService.java index 563a0102..f7c47dea 100644 --- a/server/src/main/java/com/growstory/domain/journal/service/JournalService.java +++ b/server/src/main/java/com/growstory/domain/journal/service/JournalService.java @@ -1,7 +1,6 @@ package com.growstory.domain.journal.service; import com.growstory.domain.account.service.AccountService; -import com.growstory.domain.alarm.constants.AlarmType; import com.growstory.domain.images.entity.JournalImage; import com.growstory.domain.images.service.JournalImageService; import com.growstory.domain.journal.dto.JournalDto; @@ -13,7 +12,6 @@ import com.growstory.domain.point.service.PointService; import com.growstory.global.exception.BusinessLogicException; import com.growstory.global.exception.ExceptionCode; -import com.growstory.global.sse.service.SseService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,7 +33,6 @@ public class JournalService { private final JournalMapper journalMapper; private final AccountService accountService; private final PointService pointService; - private final SseService sseService; private static final String JOURNAL_IMAGE_PROCESS_TYPE = "journal_image"; @@ -49,11 +46,10 @@ public List findAllJournals(Long accountId, Long leafId) { .collect(Collectors.toList()); } - public JournalDto.Response createJournal(Long leafId, JournalDto.Post postDto, MultipartFile image) { - accountService.checkAuthIdMatching(postDto.getLeafAuthorId()); + public JournalDto.Response createJournal(Long accountId, Long leafId, JournalDto.Post postDto, MultipartFile image) { + accountService.isAuthIdMatching(accountId); Leaf findLeaf = leafService.findLeafEntityBy(leafId); Journal journal = createJournalWithNoImg(findLeaf, postDto); - sseService.notify(postDto.getLeafAuthorId(), AlarmType.WRITE_DIARY); //image가 null이거나 비어있을 경우 ResponseDto로 변환하여 반환 if(image==null|| image.isEmpty()) { return journalMapper.toResponseFrom(journal); @@ -63,11 +59,9 @@ public JournalDto.Response createJournal(Long leafId, JournalDto.Post postDto, M //image 정보 Journal에 업데이트 journal.updateImg(savedJournalImage); - return journalMapper.toResponseFrom(journalRepository.save(journal)); } - private Journal createJournalWithNoImg(Leaf findLeaf, JournalDto.Post postDto) { pointService.updatePoint(findLeaf.getAccount().getPoint(), "journal"); return journalRepository.save(Journal.builder() @@ -78,9 +72,9 @@ private Journal createJournalWithNoImg(Leaf findLeaf, JournalDto.Post postDto) { .build()); } - public void updateJournal(Long journalId, JournalDto.Patch patchDto, MultipartFile image) { + public void updateJournal(Long accountId, Long journalId, JournalDto.Patch patchDto, MultipartFile image) { Journal findJournal = findVerifiedJournalBy(journalId); - accountService.checkAuthIdMatching(patchDto.getLeafAuthorId()); + accountService.isAuthIdMatching(accountId); Optional.ofNullable(patchDto.getTitle()) .ifPresent(findJournal::updateTitle); @@ -90,6 +84,8 @@ public void updateJournal(Long journalId, JournalDto.Patch patchDto, MultipartFi updateLoadImage(image, findJournal, JOURNAL_IMAGE_PROCESS_TYPE); } + //TODO: S3Uploader로 빼는 리팩토링 작업? (상위 클래스 Image를 이용한 형변환) + // 기존 DB와 S3에 저장된 이미지 정보를 업로드 이미지 여부에 따라 수정 private void updateLoadImage(MultipartFile image, Journal journal, String type) { JournalImage journalImage = journal.getJournalImage(); @@ -107,7 +103,7 @@ private void updateLoadImage(MultipartFile image, Journal journal, String type) } public void deleteJournal(Long accountId, Long journalId) { - accountService.checkAuthIdMatching(accountId); + accountService.isAuthIdMatching(accountId); Journal journal = findVerifiedJournalBy(journalId); //저널에 귀속되어 있는 이미지들도 S3에서 삭제해야 한다. JournalImage journalImage = journal.getJournalImage(); diff --git a/server/src/main/java/com/growstory/domain/leaf/dto/LeafDto.java b/server/src/main/java/com/growstory/domain/leaf/dto/LeafDto.java index f29e8f0d..c3b6071b 100644 --- a/server/src/main/java/com/growstory/domain/leaf/dto/LeafDto.java +++ b/server/src/main/java/com/growstory/domain/leaf/dto/LeafDto.java @@ -1,6 +1,5 @@ package com.growstory.domain.leaf.dto; -import com.growstory.global.badwords.dto.TextContainer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -12,7 +11,7 @@ public class LeafDto { @Getter @Schema(name = "LeafPostDto") - public static class Post implements TextContainer { + public static class Post { @NotBlank private String leafName; @@ -24,17 +23,11 @@ public Post(String leafName, String content) { this.leafName = leafName; this.content = content; } - - @Override - public String combineText() { - StringBuilder sb = new StringBuilder(); - return sb.append(this.leafName).append(" ").append(this.content).toString(); - } } @Getter @Schema(name = "LeafPatchDto") - public static class Patch implements TextContainer { + public static class Patch { @Positive private Long leafId; @@ -53,12 +46,6 @@ public Patch(Long leafId, String leafName, String content, Boolean isImageUpdate this.content = content; this.isImageUpdated = isImageUpdated; } - - @Override - public String combineText() { - StringBuilder sb = new StringBuilder(); - return sb.append(this.leafName).append(" ").append(this.content).toString(); - } } @Getter diff --git a/server/src/main/java/com/growstory/domain/leaf/entity/Leaf.java b/server/src/main/java/com/growstory/domain/leaf/entity/Leaf.java index ab3ec0f1..d5a69737 100644 --- a/server/src/main/java/com/growstory/domain/leaf/entity/Leaf.java +++ b/server/src/main/java/com/growstory/domain/leaf/entity/Leaf.java @@ -33,7 +33,7 @@ public class Leaf extends BaseTimeEntity { @JoinColumn(name = "ACCOUNT_ID") private Account account; - @OneToOne(mappedBy = "leaf", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(mappedBy = "leaf") private PlantObj plantObj; @OneToMany(mappedBy = "leaf", cascade = CascadeType.ALL, orphanRemoval = true) @@ -58,8 +58,6 @@ public void updatePlantObj(PlantObj plantObj) { this.plantObj = plantObj; } - - public void removePlantObj() { this.plantObj = null; } diff --git a/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java b/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java index b7c7b9e4..6bd4dc67 100644 --- a/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java +++ b/server/src/main/java/com/growstory/domain/leaf/service/LeafService.java @@ -1,6 +1,5 @@ package com.growstory.domain.leaf.service; -import com.growstory.domain.account.constants.AccountGrade; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.service.AccountService; import com.growstory.domain.images.service.JournalImageService; @@ -34,11 +33,11 @@ public class LeafService { private final AuthUserUtils authUserUtils; private final JournalImageService journalImageService; - public LeafDto.Response createLeaf(LeafDto.Post requestDto, MultipartFile leafImage) { + public LeafDto.Response createLeaf(LeafDto.Post leafPostDto, MultipartFile leafImage) { Account findAccount = authUserUtils.getAuthUser(); Leaf leaf = Leaf.builder() - .leafName(requestDto.getLeafName()) - .content(requestDto.getContent()) + .leafName(leafPostDto.getLeafName()) + .content(leafPostDto.getContent()) .account(findAccount) .build(); @@ -55,21 +54,21 @@ public LeafDto.Response createLeaf(LeafDto.Post requestDto, MultipartFile leafIm .build(); } - public void updateLeaf(LeafDto.Patch requestDto, MultipartFile leafImage) { + public void updateLeaf(LeafDto.Patch leafPatchDto, MultipartFile leafImage) { Account findAccount = authUserUtils.getAuthUser(); - Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), requestDto.getLeafId()); + Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), leafPatchDto.getLeafId()); String leafImageUrl = findLeaf.getLeafImageUrl(); - if (requestDto.getIsImageUpdated()) + if (leafPatchDto.getIsImageUpdated()) s3Uploader.deleteImageFromS3(leafImageUrl, LEAF_IMAGE_PROCESS_TYPE); if (Optional.ofNullable(leafImage).isPresent()) leafImageUrl = s3Uploader.uploadImageToS3(leafImage, LEAF_IMAGE_PROCESS_TYPE); leafRepository.save(findLeaf.toBuilder() - .leafName(Optional.ofNullable(requestDto.getLeafName()).orElse(findLeaf.getLeafName())) + .leafName(Optional.ofNullable(leafPatchDto.getLeafName()).orElse(findLeaf.getLeafName())) .leafImageUrl(leafImageUrl) - .content(Optional.ofNullable(requestDto.getContent()).orElse(findLeaf.getContent())) + .content(Optional.ofNullable(leafPatchDto.getContent()).orElse(findLeaf.getContent())) .build()); } @@ -98,7 +97,6 @@ public Leaf findLeafEntityBy(Long leafId) { public void deleteLeaf(Long leafId) { Account findAccount = authUserUtils.getAuthUser(); - Leaf findLeaf = findVerifiedLeafByAccount(findAccount.getAccountId(), leafId); s3Uploader.deleteImageFromS3(findLeaf.getLeafImageUrl(), LEAF_IMAGE_PROCESS_TYPE); @@ -126,14 +124,14 @@ private Leaf findVerifiedLeafByAccount(Long accountId, Long leafId) { else return findLeaf; } - public AccountGrade updateAccountGrade(Account findAccount) { + public Account.AccountGrade updateAccountGrade(Account findAccount) { int leavesNum = findAccount.getLeaves().size(); if (leavesNum < 50) { - return AccountGrade.GRADE_BRONZE; + return Account.AccountGrade.GRADE_BRONZE; } else if (leavesNum < 100) { - return AccountGrade.GRADE_SILVER; + return Account.AccountGrade.GRADE_SILVER; } else { - return AccountGrade.GRADE_GOLD; + return Account.AccountGrade.GRADE_GOLD; } } diff --git a/server/src/main/java/com/growstory/domain/plant_object/entity/PlantObj.java b/server/src/main/java/com/growstory/domain/plant_object/entity/PlantObj.java index fda9f681..a7e6a460 100644 --- a/server/src/main/java/com/growstory/domain/plant_object/entity/PlantObj.java +++ b/server/src/main/java/com/growstory/domain/plant_object/entity/PlantObj.java @@ -2,7 +2,6 @@ import com.growstory.domain.account.entity.Account; -import com.growstory.domain.guestbook.entity.GuestBook; import com.growstory.domain.leaf.entity.Leaf; import com.growstory.domain.plant_object.location.entity.Location; import com.growstory.domain.product.entity.Product; @@ -12,8 +11,6 @@ import lombok.NoArgsConstructor; import javax.persistence.*; -import java.util.ArrayList; -import java.util.List; @Getter diff --git a/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java b/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java index 37ede600..871e8c21 100644 --- a/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java +++ b/server/src/main/java/com/growstory/domain/plant_object/service/PlantObjService.java @@ -73,7 +73,7 @@ public PlantObjDto.GardenInfoResponse findAllGardenInfo(Long accountId) { // POST : 유저 포인트로 오브젝트 구입 public PlantObjDto.TradeResponse buyProduct(Long accountId, Long productId) { // 시큐리티 컨텍스트 인증정보 확인 - accountService.checkAuthIdMatching(accountId); + accountService.isAuthIdMatching(accountId); // 인증 정보를 바탕으로 Account 엔티티 조회 Account findAccount = authUserUtils.getAuthUser(); @@ -105,7 +105,7 @@ public PlantObjDto.TradeResponse buyProduct(Long accountId, Long productId) { // PATCH : 오브젝트 되팔기 public PointDto.Response refundPlantObj(Long accountId, Long plantObjId) { - accountService.checkAuthIdMatching(accountId); + accountService.isAuthIdMatching(accountId); Account findAccount = authUserUtils.getAuthUser(); PlantObj plantObj = findVerifiedPlantObj(plantObjId); @@ -124,15 +124,15 @@ public PointDto.Response refundPlantObj(Long accountId, Long plantObjId) { // POST : 오브젝트 배치 (편집 완료) public void saveLocation(Long accountId, List patchLocationDtos) { - accountService.checkAuthIdMatching(accountId); + accountService.isAuthIdMatching(accountId); patchLocationDtos.stream() .forEach(patchLocationDto -> { LocationDto.Patch locationPatchDto = patchLocationDto.getLocationDto(); //프로덕트 id와 로케이션 id가 일치하지 않으면 예외 발생 -// if(patchLocationDto.getPlantObjId()!=locationPatchDto.getLocationId()) { -// throw new BusinessLogicException(ExceptionCode.LOCATION_NOT_ALLOW); -// } + if(patchLocationDto.getPlantObjId()!=locationPatchDto.getLocationId()) { + throw new BusinessLogicException(ExceptionCode.LOCATION_NOT_ALLOW); + } if(locationPatchDto.getX()<0 || locationPatchDto.getX()>11 || locationPatchDto.getY()<0 || locationPatchDto.getY()>7) { throw new BusinessLogicException(ExceptionCode.INVALID_LOCATION); @@ -144,7 +144,7 @@ public void saveLocation(Long accountId, List patchLo // PATCH : 오브젝트와 식물 카드 연결 / 해제 / 교체 public PlantObjDto.Response updateLeafConnection(Long accountId, Long plantObjId, Long leafId) { - accountService.checkAuthIdMatching(accountId); + accountService.isAuthIdMatching(accountId); boolean isLeafNull = leafId == null; PlantObj findPlantObj = findVerifiedPlantObj(plantObjId); @@ -160,9 +160,10 @@ public PlantObjDto.Response updateLeafConnection(Long accountId, Long plantObjId return plantObjMapper.toPlantObjResponse(findPlantObj); } - public PlantObj findVerifiedPlantObj (long plantObjId) { + private PlantObj findVerifiedPlantObj (long plantObjId) { return plantObjRepository.findById(plantObjId).orElseThrow(() -> new BusinessLogicException(ExceptionCode.PLANT_OBJ_NOT_FOUND)); + } private void buy(Account account, Product product) { diff --git a/server/src/main/java/com/growstory/domain/point/controller/PointController.java b/server/src/main/java/com/growstory/domain/point/controller/PointController.java deleted file mode 100644 index 9dbcff1e..00000000 --- a/server/src/main/java/com/growstory/domain/point/controller/PointController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.growstory.domain.point.controller; - -import com.growstory.domain.point.service.PointService; -import com.growstory.global.response.SingleResponseDto; -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import javax.validation.constraints.Positive; - -@RequiredArgsConstructor -@RequestMapping("/v1/points") -@RestController -public class PointController { - - private final PointService pointService; - - @Operation(summary = "Patch All Event Point", description = "Points awarded through events to everyone") - @PatchMapping("/all") - public ResponseEntity patchAllEventPoints(@Positive @RequestParam("point") int pointScore, - @RequestParam("event-key") String eventKey) { - - pointService.updateAllEventPoint(pointScore, eventKey); - - return ResponseEntity.noContent().build(); - } - - @Operation(summary = "Patch Someone Event Point", description = "Points awarded through events to someone") - @PatchMapping - public ResponseEntity patchEventPoint( - @Positive @RequestParam("account-id") Long accountId, - @Positive @RequestParam("point") int point, - @RequestParam("event-key")String eventKey) { - - pointService.updateEventPoint(accountId, point, eventKey); - - return ResponseEntity.noContent().build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/point/service/PointService.java b/server/src/main/java/com/growstory/domain/point/service/PointService.java index 10f611bc..a296af5d 100644 --- a/server/src/main/java/com/growstory/domain/point/service/PointService.java +++ b/server/src/main/java/com/growstory/domain/point/service/PointService.java @@ -1,44 +1,29 @@ package com.growstory.domain.point.service; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.entity.Point; import com.growstory.domain.point.repository.PointRepository; import com.growstory.global.exception.BusinessLogicException; import com.growstory.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Transactional -@RequiredArgsConstructor -@Slf4j @Service +@RequiredArgsConstructor public class PointService { @Value("${mail.admin.address}") private String adminMailAddress; - @Value("${mail.guest}") - private String guest; - private static final int ADMIN_POINT = 100000000; - private static final int GUEST_POINT = 12300; // 게스트 시작 시 지급 포인트(10,000 + 2,300) - private static final int REGISTER_POINT = 2500; // 회원 가입시 지급 포인트 + private static final int REGISTER_POINT = 500; // 회원 가입시 지급 포인트 private static final int POSTING_POINT = 30; // 게시글 등록시 지급 포인트 private static final int DAILY_LOGIN_POINT = 10; // 일일 로그인시 지급 포인트 (출석 체크 창이나 버튼이 필요, 로그인을 지속하며 날짜가 갱신되었을 때 판별이 어려워보임) private static final int JOURNAL_WRITTEN_POINT = 10; // 저널 작성 포인트 - @Value("${event.key}") - private String EVENT_KEY; - private final PointRepository pointRepository; - private final AccountRepository accountRepository; public Point createPoint(String email) { // pointRepo.save 없이 account의 cascade로 자동 저장됨(account가 삭제되면 자동 삭제) @@ -46,10 +31,6 @@ public Point createPoint(String email) { return Point.builder() .score(ADMIN_POINT) .build(); - if (email.equals(guest)) - return Point.builder() - .score(GUEST_POINT) - .build(); return Point.builder() .score(REGISTER_POINT) @@ -89,26 +70,4 @@ public Point updatePoint(Point presentPoint, int updateScore) { .score(presentPoint.getScore() + updateScore) .build()); } - - // 계정 전원 포인트 지급 - public void updateAllEventPoint(int updateScore, String eventKey) { - if(!EVENT_KEY.equals(eventKey)) { - log.info("eventError"); - return; - } - - List findPoints = pointRepository.findAll(); - findPoints - .forEach(point -> updatePoint(point, updateScore)); - } - - // 특정 계정 포인트 지급 - public void updateEventPoint(Long accountId, Integer updateScore, String eventKey) { - if(!EVENT_KEY.equals(eventKey)) { - log.info("eventError"); - return; - } - Account findAccount = accountRepository.findById(accountId).get(); - updatePoint(findAccount.getPoint(), updateScore); - } } diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/constant/ChatMessageConstants.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/constant/ChatMessageConstants.java deleted file mode 100644 index f492af92..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/constant/ChatMessageConstants.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.constant; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -public class ChatMessageConstants { - - @Getter - @AllArgsConstructor - @NoArgsConstructor - public enum EnumChatMessage { - QNA_ENTERED("님, 문의하신 내용을 자세하게 적어주시면 담당자가 훨씬 빠르게 내용을 파악하고 답변드릴 수 있습니다."); - - private String value; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageController.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageController.java deleted file mode 100644 index f1486764..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageController.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.controller; - -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageRequestDto; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageResponseDto; -import com.growstory.domain.qnachat.chatmessage.service.ChatMessageService; -import com.growstory.domain.qnachat.chatroom.dto.SimpChatRoomRequestDto; -import com.growstory.global.response.PageResponse; -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.Valid; -import javax.validation.constraints.Positive; -import java.util.List; - -@Validated -@RequestMapping("v1/chat-messages") -@RequiredArgsConstructor -@RestController -public class ChatMessageController { - - private final ChatMessageService chatMessageService; - - @Operation(summary = "채팅방의 전체 메시지 조회", description = "채팅방의 전체 메시지를 조회합니다.") - @GetMapping("/{chatroom-id}") - public ResponseEntity>> getAllChatMessages(@PageableDefault Pageable pageable, - @Positive @PathVariable("chatroom-id") Long chatRoomId) { - return ResponseEntity.ok(chatMessageService.getAllChatMessage(pageable, chatRoomId)); - } - - @Operation(summary = "입장 메시지 만들기 테스트", description = "메시지 만들기 테스트") - @PostMapping("/enter-test") - public ResponseEntity createEnterMessage(@Valid @RequestBody SimpChatRoomRequestDto chatMessageRequest) { - return ResponseEntity.ok(chatMessageService.createEnterMessage(chatMessageRequest)); - } - - @Operation(summary = "보내는 메시지 만들기 테스트", description = "메시지 만들기 테스트") - @PostMapping("/send-test") - public ResponseEntity createSendMessage(@Valid @RequestPart ChatMessageRequestDto chatMessageRequest, - @RequestPart(required = false, value = "image") MultipartFile image) { - return ResponseEntity.ok(chatMessageService.createSendMessage(chatMessageRequest, image)); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageStompController.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageStompController.java deleted file mode 100644 index 21dda1a4..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/controller/ChatMessageStompController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.controller; - -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageRequestDto; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageResponseDto; -import com.growstory.domain.qnachat.chatmessage.service.ChatMessageService; -import com.growstory.domain.qnachat.chatroom.dto.SimpChatRoomRequestDto; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Controller; - -@Slf4j -@Controller -@RequiredArgsConstructor -public class ChatMessageStompController { - - private final SimpMessagingTemplate simpMessagingTemplate; - private final ChatMessageService chatMessageService; - - @MessageMapping(value = "/chatRoom/enter") - public void enterChatRoom(SimpChatRoomRequestDto enterMessageRequest) { - ChatMessageResponseDto chatMessageResponse = chatMessageService.createEnterMessage(enterMessageRequest); - simpMessagingTemplate.convertAndSend("/sub/chatRoom/" + enterMessageRequest.getChatRoomId(), chatMessageResponse); - } - - @MessageMapping(value = "/chatRoom/send") - public void sendMessageToChatRoom(ChatMessageRequestDto chatMessageRequest) { - ChatMessageResponseDto chatMessageResponse = chatMessageService.createSendMessage(chatMessageRequest, null); - simpMessagingTemplate.convertAndSend("/sub/chatRoom/" + chatMessageRequest.getChatRoomId(), chatMessageResponse); - } - - @MessageMapping(value = "/chatRoom/exit") - public void exitChatRoom(SimpChatRoomRequestDto deleteChatRoomRequest) { - ChatMessageResponseDto chatMessageResponse = chatMessageService.sendExitChatRoomMessage(deleteChatRoomRequest); - simpMessagingTemplate.convertAndSend("/sub/chatRoom/"+deleteChatRoomRequest.getChatRoomId(), chatMessageResponse); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageRequestDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageRequestDto.java deleted file mode 100644 index d8f33fa7..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageRequestDto.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.dto; - -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -@Getter -@NoArgsConstructor -public class ChatMessageRequestDto { - @NotNull @Min(1) - private Long senderId; - @NotNull @Min(1) - private Long chatRoomId; - @NotBlank - private String message; - - @Builder - public ChatMessageRequestDto(Long senderId, String message, Long chatRoomId) { - this.senderId = senderId; - this.message = message; - this.chatRoomId = chatRoomId; - } - - public static ChatMessageRequestDto of(Long chatRoomId, Long senderId, String message) { - return ChatMessageRequestDto.builder() - .chatRoomId(chatRoomId) - .senderId(senderId) - .message(message) - .build(); - } - -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageResponseDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageResponseDto.java deleted file mode 100644 index 0b513dc9..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/dto/ChatMessageResponseDto.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.dto; - -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import lombok.*; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -@Getter -@Builder -@AllArgsConstructor -public class ChatMessageResponseDto { - private Long messageId; - private Long senderId; - private String senderName; - private String message; - private String imageUrl; - private LocalDateTime createdAt; - private LocalDateTime modifiedAt; - - public static ChatMessageResponseDto from(ChatMessage chatMessage) { - String chatImgUrl = chatMessage.getChatMessageImage() == null ? null : chatMessage.getChatMessageImage().getImageUrl(); - return ChatMessageResponseDto.builder() - .messageId(chatMessage.getMessageId()) - .senderId(chatMessage.getAccount().getAccountId()) - .senderName(chatMessage.getAccount().getDisplayName()) - .message(chatMessage.getMessage()) - .imageUrl(chatImgUrl) - .createdAt(chatMessage.getCreatedAt().withNano(0)) - .modifiedAt(chatMessage.getModifiedAt().withNano(0)) - .build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/entity/ChatMessage.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/entity/ChatMessage.java deleted file mode 100644 index 7374f22b..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/entity/ChatMessage.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.entity; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.images.entity.ChatMessageImage; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.global.audit.BaseTimeEntity; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; - -@NoArgsConstructor -@Getter -@Entity -public class ChatMessage extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long messageId; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "sender_id", nullable = false) - Account account; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "chat_room_id", nullable = false) - private ChatRoom chatRoom; - - @Lob - @Column(nullable = false) - private String message; - - @OneToOne(mappedBy = "chatMessage", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true) - private ChatMessageImage chatMessageImage; - - /** - * 생성자 - */ - - @Builder - public ChatMessage(Account account, String message, ChatMessageImage chatMessageImage, ChatRoom chatRoom) { - this.account = account; - this.message = message; - this.chatMessageImage = chatMessageImage; - this.chatRoom = chatRoom; - } - - /** - * 연관관계 메서드 - */ - public void updateChatMessageImage(ChatMessageImage chatMessageImage) { - this.chatMessageImage = chatMessageImage; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/mapper/ChatMessageMapper.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/mapper/ChatMessageMapper.java deleted file mode 100644 index 0ba6d99c..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/mapper/ChatMessageMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.mapper; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.service.AccountService; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageRequestDto; -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.domain.qnachat.chatroom.service.ChatRoomService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -public class ChatMessageMapper { - - private final AccountService accountService; - private final ChatRoomService chatRoomService; - - public ChatMessage toEntityFrom(ChatMessageRequestDto requestDto) { - if(requestDto == null) return null; - - Account findAccount = accountService.findVerifiedAccount(requestDto.getSenderId()); - ChatRoom findChatRoom = chatRoomService.findVerifiedChatRoom(requestDto.getChatRoomId()); - - return ChatMessage.builder() - .account(findAccount) - .chatRoom(findChatRoom) - .message(requestDto.getMessage()) - .build(); - }; -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/repository/ChatMessageRepository.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/repository/ChatMessageRepository.java deleted file mode 100644 index df97f248..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/repository/ChatMessageRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.repository; - -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface ChatMessageRepository extends JpaRepository { - //채팅방 페이지 조회 - Page findByChatRoom(Pageable pageable, ChatRoom chatRoom); - - - //채팅방 조건 메시지 조회 - List findAllByChatRoom(ChatRoom chatRoom); - - List findAllByChatRoom(ChatRoom chatRoom, Sort sort); - - //메시지 내용 조건 메시지 조회 - List findAllByMessageContaining(String message); - - List findAllByMessageContaining(String message, Sort sort); -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageService.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageService.java deleted file mode 100644 index 52213da4..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.service; - -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageRequestDto; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageResponseDto; -import com.growstory.domain.qnachat.chatroom.dto.SimpChatRoomRequestDto; -import com.growstory.global.response.PageResponse; -import org.springframework.data.domain.Pageable; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -public interface ChatMessageService { - PageResponse> getAllChatMessage(Pageable pageable, Long chatRoomId); - ChatMessageResponseDto createSendMessage(ChatMessageRequestDto chatMessageRequest, MultipartFile image); - ChatMessageResponseDto createEnterMessage(SimpChatRoomRequestDto chatMessageRequest); - ChatMessageResponseDto sendExitChatRoomMessage(SimpChatRoomRequestDto deleteChatRoomRequest); -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageServiceImpl.java b/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageServiceImpl.java deleted file mode 100644 index 247111b8..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatmessage/service/ChatMessageServiceImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.growstory.domain.qnachat.chatmessage.service; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.service.AccountService; -import com.growstory.domain.images.entity.ChatMessageImage; -import com.growstory.domain.images.service.ChatMessageImageService; -import com.growstory.domain.qnachat.chatmessage.constant.ChatMessageConstants; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageRequestDto; -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageResponseDto; -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatmessage.repository.ChatMessageRepository; -import com.growstory.domain.qnachat.chatroom.constants.ChatRoomConstants; -import com.growstory.domain.qnachat.chatroom.dto.SimpChatRoomRequestDto; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.domain.qnachat.chatroom.service.ChatRoomService; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import com.growstory.global.response.PageResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.stream.Collectors; - -@RequiredArgsConstructor -@Transactional -@Service -public class ChatMessageServiceImpl implements ChatMessageService{ - private final ChatRoomService chatRoomService; - private final ChatMessageRepository chatMessageRepository; - private final AccountService accountService; - private final ChatMessageImageService chatMessageImageService; - - private static final String CHAT_MESSAGE_IMAGE_PROCESS_TYPE = "chat_message_image"; - - // chatRoomId -> 메시지응답 페이지 반환 - @Override - @Transactional(readOnly = true) - public PageResponse> getAllChatMessage(Pageable pageable, Long chatRoomId) { - ChatRoom chatRoom = this.chatRoomService.findVerifiedChatRoom(chatRoomId); - Page page = this.chatMessageRepository.findByChatRoom(pageable, chatRoom); - List data = page.get().map(ChatMessageResponseDto::from).collect(Collectors.toList()); - return PageResponse.of(page, data); - } - - // 메시지 전송 요청 -> Account, ChatRoom과 매핑 후 저장 및 응답 - @Override - public ChatMessageResponseDto createSendMessage(ChatMessageRequestDto chatMessageRequest, MultipartFile image) { - Account account = accountService.findVerifiedAccount(chatMessageRequest.getSenderId()); - ChatRoom chatRoom = chatRoomService.findVerifiedChatRoom(chatMessageRequest.getChatRoomId()); - ChatMessage chatMessage = - ChatMessage.builder() - .account(account) - .chatRoom(chatRoom) - .message(chatMessageRequest.getMessage()) - .build(); - chatMessageRepository.save(chatMessage); - - // image가 null이 아닐 경우 이미지 업로드 및 DB 저장 - if(image!=null && !image.isEmpty()) { - mapChatMessageWithImage(image, chatMessage); - } - - return ChatMessageResponseDto.from(chatMessage); - } - - // 채팅방 입장 메시지 매핑 및 저장, 응답 - @Override - public ChatMessageResponseDto createEnterMessage(SimpChatRoomRequestDto chatMessageRequest) { - Long questionerId = chatMessageRequest.getSenderId(); - Long chatRoomId = chatMessageRequest.getChatRoomId(); - Account questioner = accountService.findVerifiedAccount(questionerId); - ChatRoom chatRoom = chatRoomService.findVerifiedChatRoom(chatRoomId); - AccountChatRoom accountChatRoom = chatRoomService.validateIsEntered(questionerId, chatRoomId); - chatRoomService.validateAlreadyEnter(questionerId, chatRoomId); - accountChatRoom.updateEntryCheck(true); - - // 대화 상대방 (관리자) 매핑 - AccountChatRoom reviewerChatRoom = chatRoom.getAccountChatRooms().stream() - .filter(accChatRoom -> accChatRoom.getAccount().getAccountId() != questionerId) - .filter(accChatRoom -> accChatRoom.getAccount().getRoles().contains("ADMIN")) - .findFirst() - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.ACCOUNT_UNAUTHORIZED)); - - ChatMessage chatMessage = - ChatMessage.builder() - .message(setEntryMessage(questioner.getDisplayName())) - .account(reviewerChatRoom.getAccount()) - .chatRoom(chatRoom) - .build(); - - chatMessageRepository.save(chatMessage); - - return ChatMessageResponseDto.from(chatMessage); - } - - private void mapChatMessageWithImage(MultipartFile image, ChatMessage chatMessage) { - ChatMessageImage chatMessageImage - = chatMessageImageService.createChatMessageImgWithS3(image, CHAT_MESSAGE_IMAGE_PROCESS_TYPE, chatMessage); - chatMessage.updateChatMessageImage(chatMessageImage); - } - - private String setEntryMessage(String displayName) { - StringBuilder sb = new StringBuilder(); - return sb.append("안녕하세요 ") - .append(displayName) - .append(ChatMessageConstants.EnumChatMessage.QNA_ENTERED.getValue()) - .toString(); - } - - // 채팅방 삭제 메시지 전송 & 채팅방 떠나기 - @Override - public ChatMessageResponseDto sendExitChatRoomMessage(SimpChatRoomRequestDto deleteChatRoomRequest) { - Account account = accountService.findVerifiedAccount(deleteChatRoomRequest.getSenderId()); - ChatRoom chatRoom = chatRoomService.findVerifiedChatRoom(deleteChatRoomRequest.getChatRoomId()); - AccountChatRoom accountChatRoom = chatRoomService.getAccountChatRoomByAccountIdAndChatRoomId(account.getAccountId(), chatRoom.getChatRoomId()); - // 채팅방 떠나기 - chatRoomService.deleteChatRoom(accountChatRoom); - return createSendMessage(ChatMessageRequestDto.of(chatRoom.getChatRoomId(), account.getAccountId(), - account.getDisplayName() + ChatRoomConstants.EnumChatRoomMessage.enumExitChatRoomMessage.getValue()), null); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/constants/ChatRoomConstants.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/constants/ChatRoomConstants.java deleted file mode 100644 index f8e2c75b..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/constants/ChatRoomConstants.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.constants; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -public class ChatRoomConstants { - - @Getter - @AllArgsConstructor - @NoArgsConstructor - public enum EnumChatRoomMessage { - enumExitChatRoomMessage("님이 채팅방을 퇴장하셨습니다."); - private String value; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/controller/ChatRoomController.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/controller/ChatRoomController.java deleted file mode 100644 index 7c4a779a..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/controller/ChatRoomController.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.controller; - -import com.growstory.domain.qnachat.chatmessage.dto.ChatMessageResponseDto; -import com.growstory.domain.qnachat.chatmessage.service.ChatMessageService; -import com.growstory.domain.qnachat.chatroom.dto.*; -import com.growstory.domain.qnachat.chatroom.service.ChatRoomService; -import com.growstory.global.response.PageResponse; -import com.growstory.global.utils.UriCreator; -import io.swagger.v3.oas.annotations.Operation; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.net.URI; -import java.util.List; - -@RequiredArgsConstructor -@RequestMapping("v1/chat-rooms") -@RestController -public class ChatRoomController { - private final ChatRoomService chatRoomService; - private final ChatMessageService chatMessageService; - - private static final String DEFAULT_URL = "/v1/chat-rooms"; - - @Operation(summary = "유저의 모든 채팅방 조회", description = "특정 유저의 모든 채팅방을 조회합니다.") - @GetMapping("/{account-id}") - public ResponseEntity>> getAllChatRooms( - @PathVariable("account-id") Long accountId, - @PageableDefault Pageable pageable) { - return ResponseEntity.ok(chatRoomService.getAllChatRooms(accountId, pageable)); - } - - @Operation(summary = "qna 채팅방 생성", description = "관리자와 문의자의 1:1 문의 채팅방을 생성합니다.") - @PostMapping - public ResponseEntity createQnaChatRoom(@RequestBody CreateQnaChatRequest createQnaChatRequest) { - Long chatRoomId = chatRoomService.createQnaChatRoom(createQnaChatRequest); - URI location = UriCreator.createUri(DEFAULT_URL, chatRoomId); - return ResponseEntity.created(location).body(CreateQnaChatResponse.builder().chatRoomId(chatRoomId).build()); - } - - @Operation(summary = "유저의 채팅방 입장 여부 조회", description = "특정 유저가 채팅방에 입장했는지 여부를 조회합니다.") - @PostMapping("/entry-check") - public ResponseEntity checkEntry(@RequestBody EntryCheckRequestDto entryCheckRequest) { - return ResponseEntity.ok(chatRoomService.checkEntry(entryCheckRequest)); - } - - - @Operation(summary = "채팅방 나가기", description = "특정 유저의 채팅방을 삭제 상태로 변경 합니다.") - @DeleteMapping("/out") - public ResponseEntity deleteChatRoom(@RequestBody SimpChatRoomRequestDto deleteChatRoomRequest) { - return ResponseEntity.ok(chatMessageService.sendExitChatRoomMessage(deleteChatRoomRequest)); - } - - @Operation(summary = "문의 답변 여부 갱신", description = "해당 채팅방의 답변 여부를 최신 상태로 갱신합니다.") - @PatchMapping("/qna-answer-renewal") - public ResponseEntity patchAnswer(@RequestBody SimpChatRoomRequestDto answerRenewalRequest) { - chatRoomService.updateAnswer(answerRenewalRequest); - return ResponseEntity.ok().build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomRequestDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomRequestDto.java deleted file mode 100644 index 18422d8a..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomRequestDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -public class ChatRoomRequestDto { - private String roomName; - - @Builder - public ChatRoomRequestDto(String roomName) { - this.roomName = roomName; - } - - public ChatRoom toEntity() { - return ChatRoom.builder() - .roomName(this.roomName) - .build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomResponseDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomResponseDto.java deleted file mode 100644 index 7cec337d..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/ChatRoomResponseDto.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Comparator; -import java.util.List; -import java.util.Set; - -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Getter -public class ChatRoomResponseDto implements Comparable { - private Long chatRoomId; - private String roomName; - private Long otherAccountId; - private String otherAccountName; - private String createdAt; - private String status; - private Boolean isAnswered; - private String latestMessage; - private String latestTime; - - public static ChatRoomResponseDto from(AccountChatRoom accountChatRoom, AccountChatRoom otherAccountChatRoom) { - - List chatMessages = accountChatRoom.getChatRoom().getChatMessages(); - LocalDateTime tempLatestTime = LocalDateTime.of(2000, 1, 1, 1, 1, 1); - String tempLatestMessage = null; - for (ChatMessage chatMessage : chatMessages) { - if (chatMessage.getCreatedAt().isAfter(tempLatestTime)) { - tempLatestTime = chatMessage.getCreatedAt().withNano(0); - tempLatestMessage = chatMessage.getMessage(); - } - } - return ChatRoomResponseDto.builder() - .chatRoomId(accountChatRoom.getChatRoom().getChatRoomId()) - .roomName(accountChatRoom.getChatRoom().getRoomName()) - .otherAccountId(otherAccountChatRoom.getAccount().getAccountId()) - .otherAccountName(otherAccountChatRoom.getAccount().getDisplayName()) - .status(accountChatRoom.getChatRoom().getStatus().getMessage()) - .createdAt(accountChatRoom.getChatRoom().getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) - .isAnswered(accountChatRoom.getChatRoom().getIsAnswered()) - .latestMessage(tempLatestMessage) - .latestTime(tempLatestTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) - .build(); - } - - public static ChatRoomResponseDto from(AccountChatRoom accountChatRoom) { - - List chatMessages = accountChatRoom.getChatRoom().getChatMessages(); - LocalDateTime tempLatestTime = LocalDateTime.of(2000, 1, 1, 1, 1, 1); - String tempLatestMessage = null; - - for(ChatMessage chatMessage : chatMessages) { - if(chatMessage.getCreatedAt().isAfter(tempLatestTime)) { - tempLatestTime = chatMessage.getCreatedAt().withNano(0); - tempLatestMessage = chatMessage.getMessage(); - } - } - return ChatRoomResponseDto.builder() - .chatRoomId(accountChatRoom.getChatRoom().getChatRoomId()) - .latestMessage(tempLatestMessage) - .latestTime(tempLatestTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) - .build(); - - } - - public void trimLatestTime() { - this.latestTime = this.latestTime.split(" ")[0]; -// return this; - } - - - @Override - public int compareTo(ChatRoomResponseDto other) { - // latestTime을 기준으로 내림차순 정렬 - return Comparator.comparing(ChatRoomResponseDto::getLatestTime, Comparator.nullsLast(Comparator.reverseOrder())) - .compare(this, other); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatRequest.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatRequest.java deleted file mode 100644 index e4268e87..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class CreateQnaChatRequest { - @NotNull @Min(1) - private Long questionerId; - @NotNull @Min(1) - private Long reviewerId; - @NotNull - private String qnaTitle; -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatResponse.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatResponse.java deleted file mode 100644 index 68f3c552..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/CreateQnaChatResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -@Getter -@NoArgsConstructor -public class CreateQnaChatResponse { - private Long chatRoomId; - - @Builder - public CreateQnaChatResponse(Long chatRoomId) { - this.chatRoomId = chatRoomId; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckRequestDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckRequestDto.java deleted file mode 100644 index 31513039..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckRequestDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -@Getter -@NoArgsConstructor -public class EntryCheckRequestDto { - @NotNull(message = "채팅방id를 입력해주세요.") @Min(1) - private Long chatRoomId; - @NotNull(message = "메시지를 전송할 계정 id를 입력해주세요.") @Min(1) - private Long accountId; - - @Builder - public EntryCheckRequestDto(Long chatRoomId, Long accountId) { - this.chatRoomId = chatRoomId; - this.accountId = accountId; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckResponseDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckResponseDto.java deleted file mode 100644 index 27d2b5be..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/EntryCheckResponseDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@Getter -public class EntryCheckResponseDto { - - private boolean entryCheck; - - @Builder - public EntryCheckResponseDto(boolean entryCheck) { - this.entryCheck = entryCheck; - } - - public EntryCheckResponseDto from(boolean entryCheck) { - return EntryCheckResponseDto - .builder() - .entryCheck(entryCheck) - .build(); - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/SimpChatRoomRequestDto.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/SimpChatRoomRequestDto.java deleted file mode 100644 index 394dd283..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/dto/SimpChatRoomRequestDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.dto; - -import lombok.*; - -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class SimpChatRoomRequestDto { - @NotNull @Min(1) - private Long chatRoomId; - - @NotNull @Min(1) - private Long senderId; -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/AccountChatRoom.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/AccountChatRoom.java deleted file mode 100644 index dcb51278..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/AccountChatRoom.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.entity; - - -import com.growstory.domain.account.entity.Account; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; - -@NoArgsConstructor -@Getter -@Entity -public class AccountChatRoom { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "account_chat_room_id") - private Long accountChatRoomId; - - @ManyToOne - @JoinColumn(name = "account_id") - private Account account; - - @ManyToOne - @JoinColumn(name = "chatroom_id") - private ChatRoom chatRoom; - - @Column(nullable = false) - private boolean entryCheck; - - /** - * 생성자 - */ - @Builder - public AccountChatRoom(Long accountChatRoomId, Account account, ChatRoom chatRoom, boolean entryCheck) { - this.accountChatRoomId = accountChatRoomId; - this.account = account; - this.chatRoom = chatRoom; - this.entryCheck = entryCheck; - } - - /** - * 연관관계 메서드 - */ - - public void updateAccount(Account account) { - this.account = account; - account.getAccountChatRooms().add(this); - } - - public void updateChatRoom(ChatRoom chatRoom) { - this.chatRoom = chatRoom; - chatRoom.getAccountChatRooms().add(this); - } - - public void updateEntryCheck(boolean check) { - this.entryCheck = check; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/ChatRoom.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/ChatRoom.java deleted file mode 100644 index 5425902a..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/entity/ChatRoom.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.entity; - -import com.growstory.domain.qnachat.chatmessage.entity.ChatMessage; -import com.growstory.domain.qnachat.chatroom.dto.ChatRoomRequestDto; -import com.growstory.global.audit.BaseTimeEntity; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; -import java.util.ArrayList; -import java.util.List; - -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Entity -public class ChatRoom extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long chatRoomId; - - @Column(nullable = false) - private String roomName; - - @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, orphanRemoval = true) - private List accountChatRooms = new ArrayList<>(); - - @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, orphanRemoval = true) - private List chatMessages = new ArrayList<>(); - - @Enumerated(EnumType.STRING) - private ChatRoomStatus status; - - @Column(nullable = false) - private Boolean isAnswered; - - @Builder - public ChatRoom(String roomName) { - this.roomName = roomName; - this.status = ChatRoomStatus.EXISTS; - this.isAnswered = false; - } - - /** - * 연관관계 매핑 - */ - - public Long updateRoomName(ChatRoomRequestDto requestDto) { - this.roomName = requestDto.getRoomName(); - return this.chatRoomId; - } - - public void updateAnswered(boolean answered) { - this.isAnswered = answered; - } - - /** - * enum 타입 : 채팅방 상태 - */ - - @Getter - public enum ChatRoomStatus { - EXISTS(1, "EXISTS"), - DELETED(2, "DELETED"), - ANSWER_COMPLETED(3, "ANSWER COMPLETED"); - private final int status; - private final String message; - - ChatRoomStatus(int status, String message) { - this.status = status; - this.message = message; - } - } - public void updateStatus(ChatRoomStatus chatRoomStatus) { - this.status = chatRoomStatus; - } -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/AccountChatRoomRepository.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/AccountChatRoomRepository.java deleted file mode 100644 index da46937f..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/AccountChatRoomRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.repository; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface AccountChatRoomRepository extends JpaRepository { - List findAllByAccount(Account account); -// List findAllByChatRoomId(Long chatRoomId); - //TODO: 리팩토링 - Optional findOneByAccountAccountIdAndChatRoomChatRoomId(Long accountId, Long chatRoomId); -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/ChatRoomRepository.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/ChatRoomRepository.java deleted file mode 100644 index 039d2930..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/repository/ChatRoomRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.repository; - -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface ChatRoomRepository extends JpaRepository { - // ChatRoom 상세 조회 - ChatRoom findByRoomName(String roomName); - - // ChatRoom 목록 조회 - List findAllByRoomName(String roomName); - - List findAllByRoomName(String roomName, Sort sort); - - // ChatRoom 목록 조회, 포함 일치 - List findAllByRoomNameContaining(String roomName); - - List findAllByRoomNameContaining(String roomName, Sort sort); -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomService.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomService.java deleted file mode 100644 index cdd53f66..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.service; - -import com.growstory.domain.qnachat.chatroom.dto.*; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.global.response.PageResponse; -import org.springframework.data.domain.Pageable; - -import java.util.List; - -public interface ChatRoomService { - PageResponse> getAllChatRooms(Long accountId, Pageable pageable); - ChatRoom findVerifiedChatRoom(Long chatRoomId); - Long createQnaChatRoom(CreateQnaChatRequest chatRoomRequestDto); - EntryCheckResponseDto checkEntry(EntryCheckRequestDto entryCheckRequestDto); - AccountChatRoom validateIsEntered(Long accountId, Long chatRoomId); - void validateAlreadyEnter(Long accountId, Long chatRoomId); - void deleteChatRoom(AccountChatRoom deleteAccChatRoomRequest); - void completeChatRoom(AccountChatRoom completeAccChatRoomRequest); - AccountChatRoom getAccountChatRoomByAccountIdAndChatRoomId(Long accountId, Long chatRoomId); - void updateAnswer(SimpChatRoomRequestDto answerRenewalRequest); -} diff --git a/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomServiceImpl.java b/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomServiceImpl.java deleted file mode 100644 index cc85d026..00000000 --- a/server/src/main/java/com/growstory/domain/qnachat/chatroom/service/ChatRoomServiceImpl.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.growstory.domain.qnachat.chatroom.service; - -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.service.AccountService; -import com.growstory.domain.images.service.ChatMessageImageService; -import com.growstory.domain.qnachat.chatroom.dto.*; -import com.growstory.domain.qnachat.chatroom.entity.AccountChatRoom; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.domain.qnachat.chatroom.repository.AccountChatRoomRepository; -import com.growstory.domain.qnachat.chatroom.repository.ChatRoomRepository; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import com.growstory.global.response.PageResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.*; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -@RequiredArgsConstructor -@Transactional -@Service -public class ChatRoomServiceImpl implements ChatRoomService { - private final ChatRoomRepository chatRoomRepository; - private final ChatMessageImageService chatMessageImageService; - private final AccountChatRoomRepository accountChatRoomRepository; - private final AccountService accountService; - - private static final String CHAT_MESSAGE_IMAGE_PROCESS_TYPE = "chat_message_image"; - - // GET, 계정 아이디로 전체 채팅방 조회 - @Override - @Transactional(readOnly = true) - public PageResponse> getAllChatRooms(Long accountId, Pageable pageable) { - Account findAccount = accountService.findVerifiedAccount(accountId); - List accountChatRoomList = accountChatRoomRepository.findAllByAccount(findAccount); - validateCountIsNotZero(accountChatRoomList); - List chatRoomResponses = new ArrayList<>(); - for (AccountChatRoom accountChatRoom : accountChatRoomList) { - // 일대일 채팅일 경우 - if(accountChatRoom.getChatRoom().getAccountChatRooms().size()==2) { - accountChatRoom.getChatRoom().getAccountChatRooms().stream() - .filter(tempAccountChatRoom -> !accountChatRoom.equals(tempAccountChatRoom)) - .filter(tempAccountChatRoom -> isValidateStatus(tempAccountChatRoom.getChatRoom())) - .forEach(tempAccountChatRoom ->chatRoomResponses.add(ChatRoomResponseDto.from(accountChatRoom, tempAccountChatRoom))); - } else // 그룹 채팅의 경우 - { - accountChatRoom.getChatRoom().getAccountChatRooms().stream() - .filter(tempAccountChatRoom -> isValidateStatus(tempAccountChatRoom.getChatRoom())) - .forEach(tempAccountChatRoom -> chatRoomResponses.add(ChatRoomResponseDto.from(accountChatRoom))); - } - } - - //최근 메시지 수신일을 기준으로 내림차순정렬 -// Collections.sort(chatRoomResponses, Comparator.comparing(ChatRoomResponseDto::getLatestTime).reversed()); - Collections.sort(chatRoomResponses); - - // 페이지네이션 처리 - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), chatRoomResponses.size()); - Page page = new PageImpl<>(chatRoomResponses.subList(start, end), pageable, chatRoomResponses.size()); - - page.getContent().forEach(content -> content.trimLatestTime()); - - return PageResponse.of(page, page.getContent()); - } - - //채팅방 id와 계정 id를 이용해 입장 여부를 조회 - @Override - public EntryCheckResponseDto checkEntry(EntryCheckRequestDto entryCheckRequestDto) { - Long accountId = entryCheckRequestDto.getAccountId(); - Long chatRoomId = entryCheckRequestDto.getChatRoomId(); - AccountChatRoom accountChatRoom = validateAccountIdAndChatRoomId(accountId, chatRoomId); - if(accountChatRoom.isEntryCheck()) return EntryCheckResponseDto.builder().entryCheck(true).build(); - else // 해당 계정이 채팅방에 입장하지 않았을 경우 - return EntryCheckResponseDto.builder().entryCheck(false).build(); - } - - - // 질문자 id와 검토자 id를 이용해 채팅방 및 어카운트 채팅방 생성 - @Override - public Long createQnaChatRoom(CreateQnaChatRequest createQnaChatRequest) { - Long questionerId = createQnaChatRequest.getQuestionerId(); - Long reviewerId = createQnaChatRequest.getReviewerId(); - - Account questioner = accountService.findVerifiedAccount(questionerId); - Account reviewer = accountService.findVerifiedAccount(reviewerId); - - ChatRoom chatRoom = ChatRoom.builder().roomName(createQnaChatRequest.getQnaTitle()).build(); - ChatRoom createdChatRoom = chatRoomRepository.save(chatRoom); - - AccountChatRoom accountChatRoomByQuestioner = AccountChatRoom.builder().account(questioner).chatRoom(createdChatRoom).build(); - AccountChatRoom accountChatRoomByReviewer = AccountChatRoom.builder().account(reviewer).chatRoom(createdChatRoom).build(); - - accountChatRoomByQuestioner.updateChatRoom(chatRoom); - accountChatRoomByReviewer.updateChatRoom(chatRoom); - - accountChatRoomRepository.save(accountChatRoomByQuestioner); - accountChatRoomRepository.save(accountChatRoomByReviewer); - - return createdChatRoom.getChatRoomId(); - } - - - @Override - public AccountChatRoom validateIsEntered(Long accountId, Long chatRoomId) { - return this.accountChatRoomRepository.findOneByAccountAccountIdAndChatRoomChatRoomId(accountId, chatRoomId).orElseThrow( - () -> new BusinessLogicException(ExceptionCode.ACCOUNT_CHATROOM_NOT_FOUND) - ); - } - - @Override - public void validateAlreadyEnter(Long accountId, Long chatRoomId) { - if(accountChatRoomRepository.findOneByAccountAccountIdAndChatRoomChatRoomId(accountId, chatRoomId).get().isEntryCheck()) { - throw new BusinessLogicException(ExceptionCode.ALREADY_CHATROOM_ENTERED); - } - } - - // ChatRoom 삭제 - @Override - public void deleteChatRoom(AccountChatRoom deleteAccChatRoomRequest) { - ChatRoom chatRoom = deleteAccChatRoomRequest.getChatRoom(); - chatRoom.getChatMessages().stream() - .forEach(chatMessage -> chatMessageImageService.deleteChatMessageImageWithS3(chatMessage.getChatMessageImage(),CHAT_MESSAGE_IMAGE_PROCESS_TYPE)); - - chatRoomRepository.delete(chatRoom); - } - - // ChatRoom 상태 completed 업데이트 - @Override - public void completeChatRoom(AccountChatRoom completeAccChatRoomRequest) { - ChatRoom chatRoom = completeAccChatRoomRequest.getChatRoom(); - chatRoom.updateStatus(ChatRoom.ChatRoomStatus.ANSWER_COMPLETED); - completeAccChatRoomRequest.updateChatRoom(chatRoom); - } - - // accountId와 chatRoomId를 통해 AccountChatRoom 조회 - @Override - public AccountChatRoom getAccountChatRoomByAccountIdAndChatRoomId(Long accountId, Long chatRoomId) { - return validateAccountIdAndChatRoomId(accountId, chatRoomId); - } - - // 답변 여부 업데이트 - @Override - public void updateAnswer(SimpChatRoomRequestDto answerRenewalRequest) { - Long lastSenderId = answerRenewalRequest.getSenderId(); - Long chatRoomId = answerRenewalRequest.getChatRoomId(); - - Account findAccount = accountService.findVerifiedAccount(lastSenderId); - ChatRoom findChatRoom = findVerifiedChatRoom(chatRoomId); - - boolean answered = false; - if(findAccount.getRoles().contains("ADMIN")) - answered = true; - - findChatRoom.updateAnswered(answered); - } - - // chatRoomId로 채팅방 조회 - @Override - public ChatRoom findVerifiedChatRoom(Long chatRoomId) { - return chatRoomRepository.findById(chatRoomId).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.CHATROOM_NOT_FOUND)); - } - - - private AccountChatRoom validateAccountIdAndChatRoomId(Long accountId, Long chatRoomId) { - return this.accountChatRoomRepository.findOneByAccountAccountIdAndChatRoomChatRoomId(accountId, chatRoomId) - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.ALREADY_CHATROOM_ENTERED)); - } - - // accountChatRoom의 인원이 0이면 예외 던지기 - private void validateCountIsNotZero(List accountChatRoomList) { - if (accountChatRoomList.size() == 0) - throw new BusinessLogicException(ExceptionCode.ACCOUNT_CHATROOM_NOT_FOUND); - } - - // 채팅방 상태가 유효(존재 || 답변 완료)한지 체크 - private boolean isValidateStatus(ChatRoom chatRoom) { - return chatRoom.getStatus().equals(ChatRoom.ChatRoomStatus.EXISTS) || - chatRoom.getStatus().equals(ChatRoom.ChatRoomStatus.ANSWER_COMPLETED); - } -} diff --git a/server/src/main/java/com/growstory/domain/rank/RankService.java b/server/src/main/java/com/growstory/domain/rank/RankService.java index 6cba0264..4c533097 100644 --- a/server/src/main/java/com/growstory/domain/rank/RankService.java +++ b/server/src/main/java/com/growstory/domain/rank/RankService.java @@ -24,7 +24,7 @@ public void compensateWeeklyPoints(Rank rank) { // 주간 랭킹별 포인트 보상액 산정 private int calculateWeeklyPoints(Rank rank) { int score; - switch (rank.getRankOrders().getPosition()) { + switch (rank.getRankStatus().getRank()) { case 1: score = 3000; break; diff --git a/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java b/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java index cb3f3b86..29cbbe46 100644 --- a/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java +++ b/server/src/main/java/com/growstory/domain/rank/board_likes/entity/BoardLikesRank.java @@ -4,6 +4,8 @@ import com.growstory.domain.board.entity.Board; import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.entity.Rank; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -29,13 +31,12 @@ public BoardLikesRank(Board board, Long likeNum, Account account) { super(account); this.board = board; this.likeNum = likeNum; - super.updateRankStat(RankStat.CURRENT); } public BoardLikesRankDto.Response toResponseDto() { return BoardLikesRankDto.Response .builder() - .rank(this.getRankOrders().getPosition()) //Rank 에서 상속 받은 rank, account 정보 활용 + .rank(this.getRankStatus().getRank()) //Rank 에서 상속 받은 rank, account 정보 활용 .displayName(this.getAccount().getDisplayName()) .boardId(this.board.getBoardId()) .title(this.board.getTitle()) diff --git a/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java b/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java index 072a4ecb..7e42f4b8 100644 --- a/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java +++ b/server/src/main/java/com/growstory/domain/rank/board_likes/service/BoardLikesRankService.java @@ -4,9 +4,9 @@ import com.growstory.domain.rank.RankService; import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; +import com.growstory.domain.rank.board_likes.history.entity.BoardLikesRankHistory; import com.growstory.domain.rank.board_likes.history.repository.BoardLikesRankHistoryRepository; import com.growstory.domain.rank.board_likes.repository.BoardLikesRankRepository; -import com.growstory.domain.rank.entity.Rank; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -32,55 +32,48 @@ public class BoardLikesRankService { @Value("${my.scheduled.cron}") private String cronExpression; - // 현재 주간 랭킹 조회 - public List findCurrentBoardLikesRanks() { - return repository.findAll().stream() - //이번 주의 유효한 랭킹만 조회 - .filter(boardLikesRank -> boardLikesRank.getRankStat()== Rank.RankStat.CURRENT) + // 주간 랭킹 조회 + public List findAllBoardLikesRanks() { + List boardLikesRanks = repository.findAll(); + return boardLikesRanks.stream() .map(BoardLikesRank::toResponseDto) .collect(Collectors.toList()); } - //TODO: 이력 테이블 삭제 - // 주 1회 랭킹 업데이트 및 이력 관리, 포인트 보상 @Scheduled(cron = "${my.scheduled.cron}") public void updateFindTop3LikedBoards() { log.info("# Scheduled task findTop3LikedBoards started at {}", LocalDateTime.now()); log.info("# and Cron expression is: {}", cronExpression); - // 기존 랭킹 데이터 상태 Previous로 전환 - repository.findAll() - .forEach(boardLikesRank -> boardLikesRank.updateRankStat(Rank.RankStat.PREVIOUS)); - // 좋아요 개수 상위 3등 까지의 게시글 조회 List boardLikesRanks = boardService.findTop3LikedBoardRanks(); - // 해당 게시글의 유저에게 보상 포인트 제공 + // 이전 주의 랭킹 이력 테이블에 저장 + saveHistories(boardLikesRanks); + + // 유저에게 보상 포인트 제공 boardLikesRanks .forEach(rankService::compensateWeeklyPoints); - // 이번 주 랭킹 저장 - repository.saveAll(boardLikesRanks); - // 이전 주의 랭킹 삭제 및 이번 주 랭킹 저장 -// repository.deleteAll(); -// List newBoardLikesRanks = boardService.findTop3LikedBoardRanks(); -// repository.saveAll(newBoardLikesRanks); + repository.deleteAll(); + List newBoardLikesRanks = boardService.findTop3LikedBoardRanks(); + repository.saveAll(newBoardLikesRanks); } // 이전 게시글 좋아요 랭킹을 이력 테이블로서 저장 -// private void saveHistories(List boardLikesRanks) { -// List histories -// = boardLikesRanks.stream() -// .map(rank -> { -// return BoardLikesRankHistory.builder() -// .accountId(rank.getAccount().getAccountId()) -// .boardId(rank.getBoard().getBoardId()) -// .likesNum(rank.getLikeNum()) -// .build(); -// }).collect(Collectors.toList()); -// historyRepository.saveAll(histories); -// } + private void saveHistories(List boardLikesRanks) { + List histories + = boardLikesRanks.stream() + .map(rank -> { + return BoardLikesRankHistory.builder() + .accountId(rank.getAccount().getAccountId()) + .boardId(rank.getBoard().getBoardId()) + .likesNum(rank.getLikeNum()) + .build(); + }).collect(Collectors.toList()); + historyRepository.saveAll(histories); + } } diff --git a/server/src/main/java/com/growstory/domain/rank/controller/RankController.java b/server/src/main/java/com/growstory/domain/rank/controller/RankController.java index 63071ddd..3c355d05 100644 --- a/server/src/main/java/com/growstory/domain/rank/controller/RankController.java +++ b/server/src/main/java/com/growstory/domain/rank/controller/RankController.java @@ -25,7 +25,7 @@ public class RankController { @GetMapping("/weekly-board-likes") public ResponseEntity getWeeklyBoardLikesRanks() { - List responseDto = boardLikesRankService.findCurrentBoardLikesRanks(); + List responseDto = boardLikesRankService.findAllBoardLikesRanks(); return ResponseEntity.ok(SingleResponseDto.builder() .data(responseDto) diff --git a/server/src/main/java/com/growstory/domain/rank/entity/Rank.java b/server/src/main/java/com/growstory/domain/rank/entity/Rank.java index 742cde25..34b128cb 100644 --- a/server/src/main/java/com/growstory/domain/rank/entity/Rank.java +++ b/server/src/main/java/com/growstory/domain/rank/entity/Rank.java @@ -25,74 +25,52 @@ public abstract class Rank extends BaseTimeEntity { private Account account; @Enumerated(EnumType.STRING) - private RankOrders rankOrders; - - @Enumerated(EnumType.STRING) - private RankStat rankStat; + private RankStatus rankStatus; public Rank(Account account) { this.account = account; } - protected void updateRank(RankOrders rankOrders) { - this.rankOrders = rankOrders; + protected void updateRank(RankStatus rankStatus) { + this.rankStatus = rankStatus; } @Getter - public enum RankOrders { - FIRST("rank_no_1", 1), - SECOND("rank_no_2", 2), - THIRD("rank_no_3", 3), - FOURTH("rank_no_4", 4), - FIFTH("rank_no_5", 5); - - private final String name; - private final int position; - - RankOrders(String name, int position) { - this.name = name; - this.position = position; + public enum RankStatus { + RANK_NO_1("rank_no_1", 1), + RANK_NO_2("rank_no_2", 2), + RANK_NO_3("rank_no_3", 3), + RANK_NO_4("rank_no_4", 4), + RANK_NO_5("rank_no_5", 5); + + private String status; + private int rank; + + RankStatus(String status, int rank) { + this.status = status; + this.rank = rank; } } - @Getter - public enum RankStat { - CURRENT("Current Record", 1), - PREVIOUS("Previous Record", 2); - - private final String recordLabel; - private final int typeCode; - - RankStat(String recordLabel, int typeCode) { - this.recordLabel = recordLabel; - this.typeCode = typeCode; - } - } - - public void updateRank(int rank) { switch (rank) { case 1 : - updateRank(RankOrders.FIRST); + updateRank(RankStatus.RANK_NO_1); break; case 2 : - updateRank(RankOrders.SECOND); + updateRank(RankStatus.RANK_NO_2); break; case 3 : - updateRank(RankOrders.THIRD); + updateRank(RankStatus.RANK_NO_3); break; case 4 : - updateRank(RankOrders.FOURTH); + updateRank(RankStatus.RANK_NO_4); break; case 5 : - updateRank(RankOrders.FIFTH); + updateRank(RankStatus.RANK_NO_5); break; default: throw new BusinessLogicException(ExceptionCode.RANK_NOT_FOUND); } } - - public void updateRankStat(RankStat rankStat) { - this.rankStat = rankStat; - } } diff --git a/server/src/main/java/com/growstory/domain/report/controller/ReportController.java b/server/src/main/java/com/growstory/domain/report/controller/ReportController.java deleted file mode 100644 index 1baefd35..00000000 --- a/server/src/main/java/com/growstory/domain/report/controller/ReportController.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.growstory.domain.report.controller; - -import com.growstory.domain.report.dto.ReportDto; -import com.growstory.domain.report.service.ReportService; -import com.growstory.global.constants.HttpStatusCode; -import com.growstory.global.response.MultiResponseDto; -import com.growstory.global.utils.UriCreator; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import javax.validation.constraints.Positive; -import java.net.URI; - -@Tag(name = "Report", description = "Report Controller") -@Validated -@RequiredArgsConstructor -@RequestMapping("/v1/reports") -@RestController -public class ReportController { - private static final String REPORT_DEFAULT_URL = "/v1/reports"; - - private final ReportService reportService; - - @Operation(summary = "신고 기능", description = "사용자 정보를 입력받아 그 사용자에 대한 신고 기능") - @PostMapping - public ResponseEntity postReport(@Valid @RequestBody ReportDto.Post reportPostDto) { - ReportDto.Response responseDto = reportService.createReport(reportPostDto); - URI location = UriCreator.createUri(REPORT_DEFAULT_URL, responseDto.getReportId()); - - return ResponseEntity.created(location).build(); - } - - @Operation(summary = "전체 신고 조회", description = "전체 신고 조회") - @GetMapping("/all") - public ResponseEntity> getReports(@Positive @RequestParam(defaultValue = "1") int page, - @Positive @RequestParam(defaultValue = "10") int size) { - Page responseDto = reportService.getReports(page - 1, size); - - return ResponseEntity.ok(MultiResponseDto.builder() - .status(HttpStatusCode.OK.getStatusCode()) - .message(HttpStatusCode.OK.getMessage()) - .data(responseDto.getContent()) - .page(responseDto) - .build()); - } -} diff --git a/server/src/main/java/com/growstory/domain/report/dto/ReportDto.java b/server/src/main/java/com/growstory/domain/report/dto/ReportDto.java deleted file mode 100644 index e9901d58..00000000 --- a/server/src/main/java/com/growstory/domain/report/dto/ReportDto.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.growstory.domain.report.dto; - -import com.growstory.domain.point.entity.Point; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import lombok.Getter; - -import javax.validation.constraints.NotBlank; -import java.time.LocalDateTime; - -public class ReportDto { - @Getter - @Builder - @Schema(name = "ReportPostDto") - public static class Post { - @NotBlank - private Long accountId; - - @NotBlank - private Long reportedAccountId; - - @NotBlank - private String title; - - @NotBlank - private String content; - } - - @Getter - @Builder - @Schema(name = "ReportResponseDto") - public static class Response { - private Long reportId; - private String displayName; - private String reportedDisplayName; - private String content; - private LocalDateTime createdAt; - } -} diff --git a/server/src/main/java/com/growstory/domain/report/entity/Report.java b/server/src/main/java/com/growstory/domain/report/entity/Report.java deleted file mode 100644 index 9f1208ca..00000000 --- a/server/src/main/java/com/growstory/domain/report/entity/Report.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.growstory.domain.report.entity; - -import com.growstory.domain.account.entity.Account; -import com.growstory.global.audit.BaseTimeEntity; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; - -@Getter -@NoArgsConstructor -@Entity -public class Report extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long reportId; - - @Lob - @Column(nullable = false) - private String content; - - @ManyToOne - @JoinColumn(name = "ACCOUNT_ID") - private Account account; - - private Long reportedAccountId; - - @Builder - public Report(Long reportId, String content, Account account, Long reportedAccountId) { - this.reportId = reportId; - this.content = content; - this.account = account; - this.reportedAccountId = reportedAccountId; - } -} diff --git a/server/src/main/java/com/growstory/domain/report/repository/ReportRepository.java b/server/src/main/java/com/growstory/domain/report/repository/ReportRepository.java deleted file mode 100644 index 7328aa8f..00000000 --- a/server/src/main/java/com/growstory/domain/report/repository/ReportRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.growstory.domain.report.repository; - -import com.growstory.domain.report.entity.Report; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ReportRepository extends JpaRepository { -} diff --git a/server/src/main/java/com/growstory/domain/report/service/ReportService.java b/server/src/main/java/com/growstory/domain/report/service/ReportService.java deleted file mode 100644 index 71f9cda0..00000000 --- a/server/src/main/java/com/growstory/domain/report/service/ReportService.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.growstory.domain.report.service; - -import com.growstory.domain.account.dto.AccountDto; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.service.AccountService; -import com.growstory.domain.report.dto.ReportDto; -import com.growstory.domain.report.entity.Report; -import com.growstory.domain.report.repository.ReportRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Transactional -@RequiredArgsConstructor -@Service -public class ReportService { - private final ReportRepository reportRepository; - private final AccountService accountService; - - public ReportDto.Response createReport(ReportDto.Post requestDto) { - Account findAccount = accountService.findVerifiedAccount(requestDto.getAccountId()); - Account findReportedAccount = accountService.findVerifiedAccount(requestDto.getReportedAccountId()); - - Report savedReport = reportRepository.save(Report.builder() - .account(findAccount) - .reportedAccountId(requestDto.getReportedAccountId()) - .content(requestDto.getContent()) - .build()); - findAccount.addReport(savedReport); - findReportedAccount.updateReportsNum(); - - return ReportDto.Response.builder() - .reportId(savedReport.getReportId()) - .build(); - } - - @Transactional(readOnly = true) - public Page getReports(int page, int size) { - List reports = reportRepository.findAll(); - List responses = reports.stream() - .map(report -> { - Account findReportedAccount = accountService.findVerifiedAccount(report.getReportedAccountId()); - - return ReportDto.Response.builder() - .reportId(report.getReportId()) - .displayName(report.getAccount().getDisplayName()) - .reportedDisplayName(findReportedAccount.getDisplayName()) - .content(report.getContent()) - .createdAt(report.getCreatedAt()) - .build(); - }) - .collect(Collectors.toList()); - - int startIdx = page * size; - int endIdx = Math.min(responses.size(), (page + 1) * size); - return new PageImpl<>(responses.subList(startIdx, endIdx), PageRequest.of(page, size), responses.size()); - } -} diff --git a/server/src/main/java/com/growstory/global/advice/GlobalExceptionAdvice.java b/server/src/main/java/com/growstory/global/advice/GlobalExceptionAdvice.java index c7328ec3..b0f37f6d 100644 --- a/server/src/main/java/com/growstory/global/advice/GlobalExceptionAdvice.java +++ b/server/src/main/java/com/growstory/global/advice/GlobalExceptionAdvice.java @@ -1,7 +1,6 @@ package com.growstory.global.advice; import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; import com.growstory.global.response.ErrorResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -11,7 +10,6 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolationException; -import java.util.Objects; @RestControllerAdvice public class GlobalExceptionAdvice { @@ -32,11 +30,10 @@ public ErrorResponse handleConstraintViolationException(ConstraintViolationExcep @ExceptionHandler(BusinessLogicException.class) // BusinessLogicException 처리 public ResponseEntity handleBusinessLogicException(BusinessLogicException e) { - ErrorResponse response = ErrorResponse.of(e.getExceptionCode()); - if(!Objects.isNull(e.getProfanityDto())) { - response = ErrorResponse.of(e.getExceptionCode(), e.getProfanityDto()); - } - final ErrorResponse errorResponse = response; + final ErrorResponse errorResponse = ErrorResponse.of(e.getExceptionCode()); + +// HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse(); +// ErrorResponder.sendErrorResponse(response, e.getExceptionCode().getStatus() ,e.getExceptionCode().getMessage()); return new ResponseEntity(errorResponse, HttpStatus.valueOf(e.getExceptionCode().getStatus())); } diff --git a/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java b/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java deleted file mode 100644 index a466bc54..00000000 --- a/server/src/main/java/com/growstory/global/auth/config/PasswordEncoderConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.growstory.global.auth.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; - -@Configuration -public class PasswordEncoderConfig { - @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } -} diff --git a/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java b/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java index 78cc6fa1..805fcffa 100644 --- a/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java +++ b/server/src/main/java/com/growstory/global/auth/config/SecurityConfiguration.java @@ -1,7 +1,6 @@ package com.growstory.global.auth.config; import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.repository.PointRepository; import com.growstory.domain.point.service.PointService; import com.growstory.global.auth.filter.JwtAuthenticationFilter; @@ -18,6 +17,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; @@ -26,7 +27,6 @@ @RequiredArgsConstructor public class SecurityConfiguration { private final JwtTokenizer jwtTokenizer; - private final AccountService accountService; private final AccountRepository accountRepository; private final CustomAuthorityUtils authorityUtils; private final SecurityCorsConfig corsConfig; @@ -57,19 +57,15 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .anyRequest().permitAll()) .oauth2Login(oauth2 -> { oauth2.failureHandler(new OAuth2AccountFailureHandler()); - oauth2.successHandler(new OAuth2AccountSuccessHandler(jwtTokenizer, authorityUtils, accountService, accountRepository, pointService, pointRepository)); + oauth2.successHandler(new OAuth2AccountSuccessHandler(jwtTokenizer, authorityUtils, accountRepository, pointService, pointRepository)); }) .build(); } - // securityConfiguration 내부에서 passwordEncoder를 Bean으로 등록하기 때문에 - // accountService와 순환참조 발생 - // 따라서 PasswordEncoderConfig파일을 따로 만들어 - // 외부에서 passwordEncoder를 Bean으로 등록 -// @Bean -// public PasswordEncoder passwordEncoder() { -// return PasswordEncoderFactories.createDelegatingPasswordEncoder(); -// } + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } public class CustomFilterConfigurer extends AbstractHttpConfigurer { @Override @@ -80,7 +76,7 @@ public void configure(HttpSecurity builder) throws Exception { JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer); // JwtAuthenticationFilter 객체 생성하며 DI하기 // AbstractAuthenticationProcessingFilter에서 상속받은 filterProcessurl을 설정 (설정하지 않으면 default 값인 /Login) jwtAuthenticationFilter.setFilterProcessesUrl("/v1/accounts/authentication"); - jwtAuthenticationFilter.setAuthenticationSuccessHandler(new AccountAuthenticationSuccessHandler(accountRepository, accountService)); + jwtAuthenticationFilter.setAuthenticationSuccessHandler(new AccountAuthenticationSuccessHandler()); jwtAuthenticationFilter.setAuthenticationFailureHandler(new AccountAuthenticationFailureHandler()); JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenizer, authorityUtils); diff --git a/server/src/main/java/com/growstory/global/auth/config/SecurityCorsConfig.java b/server/src/main/java/com/growstory/global/auth/config/SecurityCorsConfig.java index c5650600..aa491020 100644 --- a/server/src/main/java/com/growstory/global/auth/config/SecurityCorsConfig.java +++ b/server/src/main/java/com/growstory/global/auth/config/SecurityCorsConfig.java @@ -23,6 +23,7 @@ public CorsFilter corsFilter() { config.addAllowedOriginPattern("http://localhost:80"); // 로컬 아파치 환경에서 접근하는 CORS 허용 config.addAllowedOriginPattern("http://localhost:3000"); // 로컬 프론트 환경에서 접근하는 CORS 허용 config.addAllowedOriginPattern("https://growstory.vercel.app"); // 배포 환경 + config.addAllowedOriginPattern("https://grows-tory.vercel.app"); // 배포 환경 // //응답 헤더에 Authorization 헤더를 노출하도록 설정 config.addExposedHeader("Authorization"); @@ -30,7 +31,6 @@ public CorsFilter corsFilter() { config.addExposedHeader("DisplayName"); config.addExposedHeader("AccountId"); config.addExposedHeader("ProfileImageUrl"); - config.addExposedHeader("Location"); config.addAllowedHeader("*"); //모든 header 허용 diff --git a/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java b/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java index 0c6b3cf0..c7767786 100644 --- a/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java +++ b/server/src/main/java/com/growstory/global/auth/dto/LoginDto.java @@ -17,6 +17,5 @@ public static class Response { private String email; private String displayName; private String profileImageUrl; - private String status; } } diff --git a/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java b/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java index fbf80951..d419b972 100644 --- a/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java +++ b/server/src/main/java/com/growstory/global/auth/filter/JwtAuthenticationFilter.java @@ -74,7 +74,6 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR .email(account.getEmail()) .displayName(URLEncoder.encode(account.getDisplayName(), "UTF-8")) .profileImageUrl(account.getProfileImageUrl()) - .status(account.getStatus().getStepDescription()) .build(); // response body에 유저 정보 저장 diff --git a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java index e81b4cc7..68b4bf47 100644 --- a/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java +++ b/server/src/main/java/com/growstory/global/auth/filter/JwtVerificationFilter.java @@ -2,6 +2,9 @@ import com.growstory.global.auth.jwt.JwtTokenizer; import com.growstory.global.auth.utils.CustomAuthorityUtils; +import com.growstory.global.exception.BusinessLogicException; +import com.growstory.global.exception.ExceptionCode; +import com.growstory.global.response.ErrorResponder; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.SignatureException; @@ -9,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.json.BasicJsonParser; import org.springframework.boot.json.JsonParser; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -22,6 +26,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,8 +69,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // 이걸 저장할 때 설정 Date accessTokenExpiration = new Date((Long) accessTokenClaims.get("exp") * 1000L); Date refreshTokenExpiration = new Date((Long) refreshTokenClaims.get("exp") * 1000L); -// System.out.println("before:" + accessTokenExpiration); -// System.out.println("before:" + refreshTokenExpiration); + System.out.println("before:" + accessTokenExpiration); + System.out.println("before:" + refreshTokenExpiration); Date now = new Date(); // accessToken 만료시간이 지금보다 이전이면(accessToken 만료 O), refreshToken 만료시간이 지금보다 이후라면(refreshToken 만료 X) @@ -83,6 +88,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Map recreatedAccessTokenClaims = verifyJws(accessToken); // JWT 검증 verifyJws(refreshToken); setAuthenticationToContext(recreatedAccessTokenClaims); + //jwt 검증에 실패할 경우 발생하는 예외를 HttpServletRequest의 속성(Attribute)으로 추가 } catch (SignatureException se) { request.setAttribute("exception", se); diff --git a/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java b/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java index e9512e5e..3cbfd1f4 100644 --- a/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java +++ b/server/src/main/java/com/growstory/global/auth/handler/AccountAuthenticationSuccessHandler.java @@ -1,10 +1,5 @@ package com.growstory.global.auth.handler; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.account.service.AccountService; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -14,22 +9,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Map; @Slf4j @RequiredArgsConstructor public class AccountAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - private final AccountRepository accountRepository; - private final AccountService accountService; - @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("# Authenticated successfully !"); - - Account account = accountRepository.findById(((Account) authentication.getPrincipal()).getAccountId()).orElseThrow(() -> - new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - - accountService.attendanceCheck(account); } } diff --git a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java index 3081eb64..8150d719 100644 --- a/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java +++ b/server/src/main/java/com/growstory/global/auth/handler/OAuth2AccountSuccessHandler.java @@ -1,9 +1,7 @@ package com.growstory.global.auth.handler; -import com.growstory.domain.account.constants.Status; import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.account.service.AccountService; import com.growstory.domain.point.entity.Point; import com.growstory.domain.point.repository.PointRepository; import com.growstory.domain.point.service.PointService; @@ -11,15 +9,21 @@ import com.growstory.global.auth.utils.CustomAuthorityUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.util.UriComponentsBuilder; import org.yaml.snakeyaml.util.UriEncoder; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.*; @@ -31,7 +35,6 @@ public class OAuth2AccountSuccessHandler extends SimpleUrlAuthenticationSuccessH private final JwtTokenizer jwtTokenizer; private final CustomAuthorityUtils authorityUtils; - private final AccountService accountService; private final AccountRepository accountRepository; private final PointService pointService; private final PointRepository pointRepository; @@ -57,14 +60,11 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .profileImageUrl(profileImageUrl) .point(point) .roles(authorities) - //status social로 추가 - .status(Status.SOCIAL_USER) + .accountGrade(Account.AccountGrade.GRADE_BRONZE) .build()); point.updateAccount(savedAccount); pointRepository.save(point); - } else { - accountService.attendanceCheck(optionalAccount.get()); } redirect(request, response, optionalAccount.orElse(savedAccount), authorities); @@ -135,6 +135,9 @@ private Object createURI(String accessToken, String refreshToken, Account accoun .newInstance() .scheme("https") .host("growstory.vercel.app") +// .port(3000) +// .host("growstory.s3-website.ap-northeast-2.amazonaws.com") +// .port(80) //S3는 80포트 .port(443) .path("/signin") .queryParam("access_token", accessToken) @@ -142,29 +145,28 @@ private Object createURI(String accessToken, String refreshToken, Account accoun .queryParam("accountId", account.getAccountId()) .queryParam("displayName", UriEncoder.encode(account.getDisplayName())) .queryParam("profileIamgeUrl", account.getProfileImageUrl()) - .queryParam("status", account.getStatus().getStepDescription()) .build() .toUri(); } -// private HttpServletResponse addCookies(HttpServletResponse response, Account account, String accessToken, String refreshToken) { -// response.addHeader("Set-Cookie", createCookie("access_token", accessToken).toString()); -// response.addHeader("Set-Cookie", createCookie("refresh_token", refreshToken).toString()); -// response.addHeader("Set-Cookie", createCookie("account_id", account.getAccountId().toString()).toString()); -// response.addHeader("Set-Cookie", createCookie("displayName", UriEncoder.encode(account.getDisplayName())).toString()); -// response.addHeader("Set-Cookie", createCookie("profileImageUrl", account.getProfileImageUrl()).toString()); -// -// return response; -// } -// -// private ResponseCookie createCookie(String key, String value) { -// ResponseCookie cookie = ResponseCookie.from(key, value) -// .sameSite("") -//// .domain("seb45-main-011.vercel.app") -// .path("/") -//// .secure(true) -// .build(); -// -// return cookie; -// } + private HttpServletResponse addCookies(HttpServletResponse response, Account account, String accessToken, String refreshToken) { + response.addHeader("Set-Cookie", createCookie("access_token", accessToken).toString()); + response.addHeader("Set-Cookie", createCookie("refresh_token", refreshToken).toString()); + response.addHeader("Set-Cookie", createCookie("account_id", account.getAccountId().toString()).toString()); + response.addHeader("Set-Cookie", createCookie("displayName", UriEncoder.encode(account.getDisplayName())).toString()); + response.addHeader("Set-Cookie", createCookie("profileImageUrl", account.getProfileImageUrl()).toString()); + + return response; + } + + private ResponseCookie createCookie(String key, String value) { + ResponseCookie cookie = ResponseCookie.from(key, value) + .sameSite("") +// .domain("seb45-main-011.vercel.app") + .path("/") +// .secure(true) + .build(); + + return cookie; + } } diff --git a/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java b/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java index 462a6bbf..53a9f5ce 100644 --- a/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java +++ b/server/src/main/java/com/growstory/global/auth/jwt/JwtTokenizer.java @@ -71,7 +71,7 @@ public Jws getClaims(String jws, String base64EncodedSecretKey) { .build() .parseClaimsJws(jws); // 토큰의 유효성 검사 -// System.out.println("after:" + claims.getBody().getExpiration()); + System.out.println("after:" + claims.getBody().getExpiration()); return claims; } diff --git a/server/src/main/java/com/growstory/global/auth/utils/CustomAuthorityUtils.java b/server/src/main/java/com/growstory/global/auth/utils/CustomAuthorityUtils.java index 3d283555..12d8ddfc 100644 --- a/server/src/main/java/com/growstory/global/auth/utils/CustomAuthorityUtils.java +++ b/server/src/main/java/com/growstory/global/auth/utils/CustomAuthorityUtils.java @@ -12,13 +12,8 @@ public class CustomAuthorityUtils { @Value("${mail.admin.address}") private String adminMailAddress; - - @Value("${mail.guest}") - private String guest; - private final List ADMIN_ROLES_STRING = List.of("ADMIN", "USER"); private final List USER_ROLES_STRING = List.of("USER"); - private final List GUEST_ROLE_STRING = List.of("GUEST", "USER"); // DB 저장된 Role 기반 권한 정보 생성 public List createAuthorities(List roles) { @@ -33,8 +28,6 @@ public List createRoles(String email) { if(email.equals(adminMailAddress)) { return ADMIN_ROLES_STRING; } - if (email.equals(guest)) return GUEST_ROLE_STRING; - return USER_ROLES_STRING; } } diff --git a/server/src/main/java/com/growstory/global/aws/config/S3Config.java b/server/src/main/java/com/growstory/global/aws/config/S3Config.java index c0ad3517..ab600427 100644 --- a/server/src/main/java/com/growstory/global/aws/config/S3Config.java +++ b/server/src/main/java/com/growstory/global/aws/config/S3Config.java @@ -3,7 +3,7 @@ import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -21,10 +21,10 @@ public class S3Config { private String region; @Bean - public AmazonS3Client amazonS3Client() { + public AmazonS3 amazonS3Client() { AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); - return (AmazonS3Client) AmazonS3ClientBuilder + return AmazonS3ClientBuilder .standard() .withCredentials(new AWSStaticCredentialsProvider(credentials)) .withRegion(region) diff --git a/server/src/main/java/com/growstory/global/aws/service/S3Uploader.java b/server/src/main/java/com/growstory/global/aws/service/S3Uploader.java index 6ed90037..cbf49214 100644 --- a/server/src/main/java/com/growstory/global/aws/service/S3Uploader.java +++ b/server/src/main/java/com/growstory/global/aws/service/S3Uploader.java @@ -1,6 +1,6 @@ package com.growstory.global.aws.service; -import com.amazonaws.services.s3.AmazonS3Client; +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; @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -18,7 +19,7 @@ @Service @RequiredArgsConstructor public class S3Uploader { - private final AmazonS3Client amazonS3Client; + private final AmazonS3 amazonS3; @Value("${cloud.aws.s3.bucket}") private String bucket; @@ -33,7 +34,7 @@ public String uploadImageToS3(MultipartFile image, String type) { metadata.setContentType(ext); try { - PutObjectResult putObjectRequest = amazonS3Client.putObject(new PutObjectRequest( + PutObjectResult putObjectRequest = amazonS3.putObject(new PutObjectRequest( bucket + "/" + type, changedName, image.getInputStream(), metadata) .withCannedAcl(CannedAccessControlList.PublicRead) ); @@ -41,14 +42,14 @@ public String uploadImageToS3(MultipartFile image, String type) { throw new RuntimeException(e); } - String imageUrl = amazonS3Client.getUrl(bucket + "/" + type, changedName).toString(); + String imageUrl = amazonS3.getUrl(bucket + "/" + type, changedName).toString(); return imageUrl; } public void deleteImageFromS3(String imageUrl, String type) { if (imageUrl.contains("https://s3.ap-northeast-2.amazonaws.com/"+ bucket)) - amazonS3Client.deleteObject(bucket + "/" + type, imageUrl.split("/")[6]); + amazonS3.deleteObject(bucket + "/" + type, imageUrl.split("/")[6]); } // 이미지 이름 변경 diff --git a/server/src/main/java/com/growstory/global/badwords/aspect/BadWordsAspect.java b/server/src/main/java/com/growstory/global/badwords/aspect/BadWordsAspect.java deleted file mode 100644 index 04e05e26..00000000 --- a/server/src/main/java/com/growstory/global/badwords/aspect/BadWordsAspect.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.growstory.global.badwords.aspect; - -import com.growstory.global.badwords.dto.ProfanityDto; -import com.growstory.global.badwords.dto.TextContainer; -import com.growstory.global.badwords.service.BlackListService; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.stereotype.Component; - -import java.util.Set; - -@Slf4j -@RequiredArgsConstructor -@Component -@Aspect -public class BadWordsAspect { - - private final BlackListService badWordsService; - - @Pointcut("execution(public * com.growstory..journal.controller.*.postJournal(..)) || " + - "execution(public * com.growstory..journal.controller.*.patchJournal(..)) || " + - "execution(public * com.growstory..board.controller.*.postBoard(..)) || " + - "execution(public * com.growstory..board.controller.*.patchBoard(..)) || " + - "execution(public * com.growstory..comment.controller.*.postComment(..)) || " + - "execution(public * com.growstory..comment.controller.*.patchComment(..)) || " + - "execution(public * com.growstory..leaf.controller.*.postLeaf(..)) || " + - "execution(public * com.growstory..leaf.controller.*.patchLeaf(..)) || " + - "execution(public * com.growstory..account.controller.*.postAccount(..)) || " + - "execution(public * com.growstory..account.controller.*.patchDisplayName(..)) " ) - public void beforeWritten() { - } - - @Before("beforeWritten()") - public void badWordsFiltering(JoinPoint joinPoint) throws Throwable { - log.info("# 욕설 필터링 동작"); - Object[] args = joinPoint.getArgs(); - - String content = ""; - for(Object container : args) { - if(container instanceof TextContainer) { - TextContainer dto = (TextContainer) container; - content = dto.combineText(); - break; - } - } - - ProfanityDto profanityDto = badWordsService.getProfanityWords(content); - Set profanityWords = profanityDto.getInputProfanityWords(); - //욕설이 포함되어 있다면 - if(!profanityWords.isEmpty()) { - throw new BusinessLogicException(ExceptionCode.BAD_WORD_INCLUDED, profanityDto); - } - } - -} diff --git a/server/src/main/java/com/growstory/global/badwords/dto/ProfanityDto.java b/server/src/main/java/com/growstory/global/badwords/dto/ProfanityDto.java deleted file mode 100644 index 17532efc..00000000 --- a/server/src/main/java/com/growstory/global/badwords/dto/ProfanityDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.growstory.global.badwords.dto; - -import lombok.Builder; -import lombok.Getter; - -import java.util.HashSet; -import java.util.Set; - -@Getter -@Builder -public class ProfanityDto { - Set inputProfanityWords = new HashSet<>(); - Set bannedWords = new HashSet<>(); - - @Override - public String toString() { - StringBuilder response = new StringBuilder(); - - String inputProfanityWords = this.getInputProfanityWords().toString(); - String bannedWords = this.getBannedWords().toString(); - - return response.append(inputProfanityWords.substring(1, inputProfanityWords.length()-1)) - .append("/") - .append(bannedWords.substring(1, bannedWords.length()-1)).toString(); - } -} diff --git a/server/src/main/java/com/growstory/global/badwords/dto/TextContainer.java b/server/src/main/java/com/growstory/global/badwords/dto/TextContainer.java deleted file mode 100644 index 9ac0be35..00000000 --- a/server/src/main/java/com/growstory/global/badwords/dto/TextContainer.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.growstory.global.badwords.dto; - -public interface TextContainer { - // 유저 작성글 내용을 하나의 텍스트로 통합 - public String combineText(); -} diff --git a/server/src/main/java/com/growstory/global/badwords/filterlist/BlackList.java b/server/src/main/java/com/growstory/global/badwords/filterlist/BlackList.java deleted file mode 100644 index c057ba17..00000000 --- a/server/src/main/java/com/growstory/global/badwords/filterlist/BlackList.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.growstory.global.badwords.filterlist; - -public interface BlackList { - String[] koreanSlangs = { - "ㅅㅂ", "씨발", "씨바", "개세끼", "18년", "18놈", "18새끼", "ㄱㅐㅅㅐㄲl", "ㄱㅐㅈㅏ", "가슴만져", "가슴빨아", "가슴빨어", "가슴조물락", "가슴주물럭", "가슴쪼물딱", - "가슴쪼물락", "가슴핧아", "가슴핧어", "강간", "개가튼년", "개가튼뇬", "개같은년", "개걸레", "개고치", "개너미", "개넘", "개년", "개놈", "개늠", "개똥", "개떵", "개떡", - "개라슥", "개보지", "개부달", "개부랄", "개불랄", "개붕알", "개새", "개세", "개쓰래기", "개쓰레기", "개씁년", "개씁블", "개씁자지", "개씨발", "개씨블", "개자식", "개자지", - "개잡년", "개젓가튼넘", "개좆", "개지랄", "개후라년", "개후라들놈", "개후라새끼", "걔잡년", "거시기", "걸래년", "걸레같은년", "걸레년", "걸레핀년", "게부럴", "게세끼", "게이", - "게새끼", "게늠", "게자식", "게지랄놈", "고환", "공지", "공지사항", "귀두", "깨쌔끼", "난자마셔", "난자먹어", "난자핧아", "내꺼빨아", "내꺼핧아", "내버지", "내자지", "내잠지", - "내조지", "너거애비", "노옴", "누나강간", "니기미", "니뿡", "니뽕", "니씨브랄", "니아범", "니아비", "니애미", "니애뷔", "니애비", "니할애비", "닝기미", "닌기미", "니미", - "닳은년", "덜은새끼", "돈새끼", "돌으년", "돌은넘", "돌은새끼", "동생강간", "동성애자", "딸딸이", "똥구녁", "똥꾸뇽", "똥구뇽", "똥", "띠발뇬", "띠팔", "띠펄", "띠풀", "띠벌", - "띠벨", "띠빌", "마스터", "막간년", "막대쑤셔줘", "막대핧아줘", "맛간년", "맛없는년", "맛이간년", "멜리스", "미친구녕", "미친구멍", "미친넘", "미친년", "미친놈", "미친눔", - "미친새끼", "미친쇄리", "미친쇠리", "미친쉐이", "미친씨부랄", "미튄", "미티넘", "미틴", "미틴넘", "미틴년", "미틴놈", "미틴것", "백보지", "버따리자지", "버지구녕", "버지구멍", - "버지냄새", "버지따먹기", "버지뚫어", "버지뜨더", "버지물마셔", "버지벌려", "버지벌료", "버지빨아", "버지빨어", "버지썰어", "버지쑤셔", "버지털", "버지핧아", "버짓물", "버짓물마셔", - "벌창같은년", "벵신", "병닥", "병딱", "병신", "보쥐", "보지", "보지핧어", "보짓물", "보짓물마셔", "봉알", "부랄", "불알", "붕알", "붜지", "뷩딱", "븅쉰", "븅신", "빙띤", - "빙신", "빠가십새", "빠가씹새", "빠구리", "빠굴이", "뻑큐", "뽕알", "뽀지", "뼝신", "사까시", "상년", "새꺄", "새뀌", "새끼", "색갸", "색끼", "색스", "색키", "샤발", - "써글", "써글년", "성교", "성폭행", "세꺄", "세끼", "섹스", "섹스하자", "섹스해", "섹쓰", "섹히", "수셔", "쑤셔", "쉐끼", "쉑갸", "쉑쓰", "쉬발", "쉬방", "쉬밸년", "쉬벌", - "쉬불", "쉬붕", "쉬빨", "쉬이발", "쉬이방", "쉬이벌", "쉬이불", "쉬이붕", "쉬이빨", "쉬이팔", "쉬이펄", "쉬이풀", "쉬팔", "쉬펄", "쉬풀", "쉽쌔", "시댕이", "시발", "시발아", "시발이", "시발년", - "시발놈", "시발새끼", "시방새", "시밸", "시벌", "시불", "시붕", "시이발", "시이벌", "시이불", "시이붕", "시이팔", "시이펄", "시이풀", "시팍새끼", "시팔", "시팔넘", "시팔년", - "시팔놈", "시팔새끼", "시펄", "실프", "십8", "십때끼", "십떼끼", "십버지", "십부랄", "십부럴", "십새", "십세이", "십셰리", "십쉐", "십자석", "십자슥", "십지랄", "십창녀", - "십창", "십탱", "십탱구리", "십탱굴이", "십팔새끼", "ㅆㅂ", "ㅆㅂㄹㅁ", "ㅆㅂㄻ", "ㅆㅣ", "쌍넘", "쌍년", "쌍놈", "쌍눔", "쌍보지", - "쌔끼", "쌔리", "쌕스", "쌕쓰", "썅년", "썅놈", "썅뇬", "썅늠", "쓉새", "쓰바새끼", "쓰브랄쉽세", "씌발", "씌팔", "씨가랭넘", "씨가랭년", "씨가랭놈", "씨발", - "씨발년", "씨발롬", "씨발병신", "씨방새", "씨방세", "씨밸", "씨뱅가리", "씨벌", "씨벌년", "씨벌쉐이", "씨부랄", "씨부럴", "씨불", "씨불알", "씨붕", "씨브럴", "씨블", - "씨블년", "씨븡새끼", "씨빨", "씨이발", "씨이벌", "씨이불", "씨이붕", "씨이팔", "씨파넘", "씨팍새끼", "씨팍세끼", "씨팔", "씨펄", "씨퐁넘", "씨퐁뇬", "씨퐁보지", - "씨퐁자지", "씹년", "씹물", "씹미랄", "씹버지", "씹보지", "씹부랄", "씹브랄", "씹빵구", "씹뽀지", "씹새", "씹새끼", "씹세", "씹쌔끼", "씹자석", "씹자슥", "씹자지", - "씹지랄", "씹창", "씹창녀", "씹탱", "씹탱굴이", "씹탱이", "씹팔", "아가리", "애무", "애미", "애미랄", "애미보지", "애미씨뱅", "애미자지", "애미잡년", "애미좃물", - "애비", "애자", "양아치", "어미강간", "어미따먹자", "어미쑤시자", "영자", "엄창", "에미", "에비", "엔플레버", "엠플레버", "염병", "염병할", "염뵹", "엿먹어라", "오랄", - "오르가즘", "왕버지", "왕자지", "왕잠지", "왕털버지", "왕털보지", "왕털자지", "왕털잠지", "우미쑤셔", "운디네", "운영자", "유두", "유두빨어", "유두핧어", "유방", "유방만져", - "유방빨아", "유방주물럭", "유방쪼물딱", "유방쪼물럭", "유방핧아", "유방핧어", "육갑", "이그니스", "이년", "이프리트", "자기핧아", "자지", "자지구녕", "자지구멍", "자지꽂아", - "자지넣자", "자지뜨더", "자지뜯어", "자지박어", "자지빨아", "자지빨아줘", "자지빨어", "자지쑤셔", "자지쓰레기", "자지정개", "자지짤라", "자지털", "자지핧아", "자지핧아줘", - "자지핧어", "작은보지", "잠지", "잠지뚫어", "잠지물마셔", "잠지털", "잠짓물마셔", "잡년", "잡놈", "저년", "점물", "젓가튼", "젓가튼쉐이", "젓같내", "젓같은", "젓까", "젓나", - "젓냄새", "젓대가리", "젓떠", "젓마무리", "젓만이", "젓물", "젓물냄새", "젓밥", "정액마셔", "정액먹어", "정액발사", "정액짜", "정액핧아", "정자마셔", "정자먹어", "정자핧아", - "젖같은", "젖까", "젖밥", "젖탱이", "조개넓은년", "조개따조", "조개마셔줘", "조개벌려조", "조개속물", "조개쑤셔줘", "조개핧아줘", "조까", "조또", "족같내", "족까", "족까내", - "존나", "존나게", "존니", "졸라", "좀마니", "좀물", "좀쓰레기", "좁빠라라", "좃가튼뇬", "좃간년", "좃까", "좃까리", "좃깟네", "좃냄새", "좃넘", "좃대가리", "좃도", "좃또", - "좃만아", "좃만이", "좃만한것", "좃만한쉐이", "좃물", "좃물냄새", "좃보지", "좃부랄", "좃빠구리", "좃빠네", "좃빠라라", "좃털", "좆같은놈", "좆같은새끼", "좆까", "좆까라", - "좆나", "좆년", "좆도", "좆만아", "좆만한년", "좆만한놈", "좆만한새끼", "좆먹어", "좆물", "좆밥", "좆빨아", "좆새끼", "좆털", "좋만한것", "주글년", "주길년", "쥐랄", "지랄", - "지랼", "지럴", "지뢀", "쪼까튼", "쪼다", "쪼다새끼", "찌랄", "찌질이", "창남", "창녀", "창녀버지", "창년", "처먹고", "처먹을", "쳐먹고", "쳐쑤셔박어", "촌씨브라리", - "촌씨브랑이", "촌씨브랭이", "크리토리스", "큰보지", "클리토리스", "트랜스젠더", "페니스", "항문수셔", "항문쑤셔", "허덥", "허버리년", "허벌년", "허벌보지", "허벌자식", "허벌자지", - "허접", "허젚", "허졉", "허좁", "헐렁보지", "혀로보지핧기", "호냥년", "호로", "호로새끼", "호로자슥", "호로자식", "호로짜식", "호루자슥", "호모", "호졉", "호좁", "후라덜넘", - "후장", "후장꽂아", "후장뚫어", "흐접", "흐젚", "흐졉", "bitch", "fuck", "fuckyou", "nflavor", "penis", "pennis", "pussy", "sex" - }; -} diff --git a/server/src/main/java/com/growstory/global/badwords/filterlist/WhiteList.java b/server/src/main/java/com/growstory/global/badwords/filterlist/WhiteList.java deleted file mode 100644 index c376c0ec..00000000 --- a/server/src/main/java/com/growstory/global/badwords/filterlist/WhiteList.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.growstory.global.badwords.filterlist; - -public interface WhiteList { - String[] whiteList = { - "시발점" - }; -} diff --git a/server/src/main/java/com/growstory/global/badwords/service/BlackListService.java b/server/src/main/java/com/growstory/global/badwords/service/BlackListService.java deleted file mode 100644 index 113ec9ab..00000000 --- a/server/src/main/java/com/growstory/global/badwords/service/BlackListService.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.growstory.global.badwords.service; - -import com.growstory.global.badwords.dto.ProfanityDto; -import com.growstory.global.badwords.filterlist.BlackList; -import com.growstory.global.badwords.filterlist.WhiteList; -import org.springframework.stereotype.Service; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -@Service -public class BlackListService implements BlackList, WhiteList { - - private HashSet blackSet = new HashSet<>(); //블랙 리스트 - private HashSet whiteSet = new HashSet<>(); //화이트 리스트 - private HashSet bannedWords = new HashSet<>(); // 포함된 금지 단어 목록 - - //대체 문자 지정 - //기본값 : * - private String substituteValue = "*"; - - public BlackListService() { - //블랙 리스트 포함 - blackSet.addAll(List.of(koreanSlangs)); - //화이트 리스트 포함 - whiteSet.addAll(List.of(whiteList)); - } - - public BlackListService(String substituteValue) { - this.substituteValue = substituteValue; - } - - //비속어 있다면 대체 - public String change(String text) { - String[] words = blackSet.stream().filter(text::contains).toArray(String[]::new); - for (String v : words) { - String sub = this.substituteValue.repeat(v.length()); - text = text.replace(v, sub); - } - return text; - } - -// public String change(String text, String[] sings) { -// StringBuilder singBuilder = new StringBuilder("["); -// for (String sing : sings) singBuilder.append(Pattern.quote(sing)); -// singBuilder.append("]*"); -// String patternText = singBuilder.toString(); -// -// for (String word : this) { -// if (word.length() == 1) text = text.replace(word, substituteValue); -// String[] chars = word.chars().mapToObj(Character::toString).toArray(String[]::new); -// text = Pattern.compile(String.join(patternText, chars)) -// .matcher(text) -// .replaceAll(v -> substituteValue.repeat(v.group().length())); -// } -// -// return text; -// } - - //금지된 욕설인지 여부 - public boolean isForbidden(String text) { - boolean isWhite = whiteSet.stream().anyMatch(text::contains); - boolean isBlack = blackSet.stream().anyMatch(blackWord -> { - // contains, 블랙 리스트에 포함되어 있으면서 화이트 리스트에서 제외되었는지 여부 판단 - boolean contains = text.contains(blackWord) && !isWhite; - if (contains) { - bannedWords.add(blackWord); // anyMatch가 true일 때 해당 단어를 추가 - } - return contains; - }); - - return isBlack && !isWhite; - } - - //사용자가 입력한 욕설 리스트 반환 - public ProfanityDto getProfanityWords(String text) { - String[] contents = text.split(" "); - bannedWords.clear(); - - Set inputProfanityWords = Arrays.stream(contents) - .filter(this::isForbidden) - .collect(Collectors.toSet()); - - return ProfanityDto.builder() - .inputProfanityWords(inputProfanityWords) - .bannedWords(bannedWords).build(); - - //TODO: findFirst()를 이용해 속도 효율을 증가 시키는 방법도 고려할 수 있다 - } - - //공백이 없는 상태에서 욕설 인식 및 반환 - public ProfanityDto getProfanityWordsWithNoBlanks(String text) { - return getProfanityWords(text.replace(" ", "")); - } -} diff --git a/server/src/main/java/com/growstory/global/config/S3Config.java b/server/src/main/java/com/growstory/global/config/S3Config.java new file mode 100644 index 00000000..968ce203 --- /dev/null +++ b/server/src/main/java/com/growstory/global/config/S3Config.java @@ -0,0 +1,31 @@ +package com.growstory.global.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; + +public class S3Config { + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3Client() { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return AmazonS3ClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region) + .build(); + } +} diff --git a/server/src/main/java/com/growstory/global/email/controller/EmailController.java b/server/src/main/java/com/growstory/global/email/controller/EmailController.java index d6b276cb..7cbfae90 100644 --- a/server/src/main/java/com/growstory/global/email/controller/EmailController.java +++ b/server/src/main/java/com/growstory/global/email/controller/EmailController.java @@ -1,6 +1,5 @@ package com.growstory.global.email.controller; -import com.growstory.domain.account.service.AccountService; import com.growstory.global.constants.HttpStatusCode; import com.growstory.global.email.dto.EmailDto; import com.growstory.global.email.service.EmailService; @@ -23,17 +22,11 @@ @RequestMapping("/v1/emails") public class EmailController { private final EmailService emailService; - private final AccountService accountService; @Operation(summary = "회원가입 시 메일 인증", description = "회원가입 시 입력받은 이메일로 메일 전송") @PostMapping("/signup") public ResponseEntity> postAuthCodeMail(@Valid @RequestBody EmailDto.Post emailPostDto) { - Boolean isDuplicated = accountService.verifyExistsEmail(emailPostDto.getEmail()); - EmailDto.SignUpResponse responseDto = EmailDto.SignUpResponse.builder().isDuplicated(isDuplicated).build(); - - if (!isDuplicated) { - responseDto = emailService.sendAuthCodeMail(emailPostDto); - } + EmailDto.SignUpResponse responseDto = emailService.sendAuthCodeMail(emailPostDto); return ResponseEntity.ok(SingleResponseDto.builder() .status(HttpStatusCode.OK.getStatusCode()) @@ -53,16 +46,4 @@ public ResponseEntity> postPassword .data(responseDto) .build()); } - - @Operation(summary = "QnA 답변 여부 전송" , description = "QnA 관리자 답변 완료 여부 안내") - @PostMapping("/qna-answer") - public ResponseEntity> postQnaAnswerMail(@Valid @RequestBody EmailDto.QnaAnswer emailQnaDto) { - EmailDto.QnaAnswerResponse responseDto = emailService.sendQnaAnswerMail(emailQnaDto); - - return ResponseEntity.ok(SingleResponseDto.builder() - .status(HttpStatusCode.OK.getStatusCode()) - .message(HttpStatusCode.OK.getMessage()) - .data(responseDto) - .build()); - } } diff --git a/server/src/main/java/com/growstory/global/email/dto/EmailDto.java b/server/src/main/java/com/growstory/global/email/dto/EmailDto.java index 0fea784b..c3eedefe 100644 --- a/server/src/main/java/com/growstory/global/email/dto/EmailDto.java +++ b/server/src/main/java/com/growstory/global/email/dto/EmailDto.java @@ -20,7 +20,6 @@ public static class Post { @Getter @Builder public static class SignUpResponse { - private Boolean isDuplicated; private String authCode; } @@ -28,20 +27,5 @@ public static class SignUpResponse { @Builder(toBuilder = true) public static class PasswordResponse { private Boolean isMatched; - private Boolean isSocial; - } - - @Getter - @Builder - public static class QnaAnswer { - private Long chatRoomId; - private Long questionerId; - private String mailSubject; - } - - @Getter - @Builder - public static class QnaAnswerResponse { - private String receiverEmail; } } diff --git a/server/src/main/java/com/growstory/global/email/service/EmailService.java b/server/src/main/java/com/growstory/global/email/service/EmailService.java index b379685b..a68d74bc 100644 --- a/server/src/main/java/com/growstory/global/email/service/EmailService.java +++ b/server/src/main/java/com/growstory/global/email/service/EmailService.java @@ -2,12 +2,7 @@ import com.growstory.domain.account.entity.Account; import com.growstory.domain.account.repository.AccountRepository; -import com.growstory.domain.qnachat.chatroom.entity.ChatRoom; -import com.growstory.domain.qnachat.chatroom.repository.ChatRoomRepository; -import com.growstory.domain.qnachat.chatroom.service.ChatRoomService; import com.growstory.global.email.dto.EmailDto; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.mail.javamail.JavaMailSender; @@ -33,10 +28,8 @@ public class EmailService { private final SpringTemplateEngine templateEngine; private final AccountRepository accountRepository; private final PasswordEncoder passwordEncoder; - private final ChatRoomRepository chatRoomRepository; - // 부하 테스트 때 비동기 처리 - public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post requsetDto) { + public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post emailPostDto) { try { String authCode = getAuthCode(); @@ -46,7 +39,7 @@ public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post requsetDto) { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(requsetDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 회원가입 인증번호 안내"); // 이메일 제목 mimeMessageHelper.setText(finalText, true); // 이메일 본문 @@ -57,7 +50,6 @@ public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post requsetDto) { }); return EmailDto.SignUpResponse.builder() - .isDuplicated(false) .authCode(authCode) .build(); } catch (Exception e) { @@ -66,17 +58,12 @@ public EmailDto.SignUpResponse sendAuthCodeMail(EmailDto.Post requsetDto) { } } - public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post requestDto) { - Optional optionalAccount = accountRepository.findByEmail(requestDto.getEmail()); + public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post emailPostDto) { + Optional optionalAccount = accountRepository.findByEmail(emailPostDto.getEmail()); if (optionalAccount.isEmpty()) { return EmailDto.PasswordResponse.builder() .isMatched(false) - .isSocial(false) - .build(); - } else if (optionalAccount.get().getStatus().getStepDescription().equals("SOCIAL_USER")) { - return EmailDto.PasswordResponse.builder() - .isSocial(true) .build(); } @@ -89,7 +76,7 @@ public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post requestDto) { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - mimeMessageHelper.setTo(requestDto.getEmail()); // 수신 이메일 + mimeMessageHelper.setTo(emailPostDto.getEmail()); // 수신 이메일 mimeMessageHelper.setSubject("[GrowStory] 임시 비밀번호 안내"); // 이메일 제목 mimeMessageHelper.setText(setContext(password, "password"), true); // 이메일 본문 mailSender.send(mimeMessage); @@ -105,43 +92,14 @@ public EmailDto.PasswordResponse sendPasswordMail(EmailDto.Post requestDto) { return EmailDto.PasswordResponse.builder() .isMatched(true) - .isSocial(false) .build(); } - // Qna 답변 여부 이메일 발송 - public EmailDto.QnaAnswerResponse sendQnaAnswerMail(EmailDto.QnaAnswer emailQnaDto) { - Account findAccount = accountRepository.findById(emailQnaDto.getQuestionerId()) - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.ACCOUNT_NOT_FOUND)); - ChatRoom findChatRoom = chatRoomRepository.findById(emailQnaDto.getChatRoomId()) - .orElseThrow(() -> new BusinessLogicException(ExceptionCode.CHATROOM_NOT_FOUND)); - - String chatRoomLink = "https://growstory.vercel.app/"; - - CompletableFuture.runAsync(() -> { - try { - MimeMessage mimeMessage = mailSender.createMimeMessage(); - MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); - - mimeMessageHelper.setTo(findAccount.getEmail()); // 수신 이메일 - mimeMessageHelper.setSubject(findChatRoom.getRoomName() + "에 대한 답변이 작성되었습니다."); - mimeMessageHelper.setText(setContext(chatRoomLink, "qnaResponse"), true); - - mailSender.send(mimeMessage); - - } catch (MessagingException e) { - throw new RuntimeException(e); - } - }); - - return EmailDto.QnaAnswerResponse.builder() - .receiverEmail(findAccount.getEmail()).build(); - } // 인증 번호 겸 임시 비밀번호 - public String getAuthCode() { + private String getAuthCode() { Random random = new Random(); - StringBuilder authCode = new StringBuilder(); + StringBuffer authCode = new StringBuffer(); for (int i = 0; i < 8; i++) { int index = random.nextInt(4); @@ -161,6 +119,4 @@ private String setContext(String code, String type) { context.setVariable(type, code); return templateEngine.process(type, context); } - - } \ No newline at end of file diff --git a/server/src/main/java/com/growstory/global/exception/BusinessLogicException.java b/server/src/main/java/com/growstory/global/exception/BusinessLogicException.java index d8ac3539..3707c98f 100644 --- a/server/src/main/java/com/growstory/global/exception/BusinessLogicException.java +++ b/server/src/main/java/com/growstory/global/exception/BusinessLogicException.java @@ -1,24 +1,13 @@ package com.growstory.global.exception; -import com.growstory.global.badwords.dto.ProfanityDto; import lombok.Getter; -import java.util.List; - @Getter public class BusinessLogicException extends RuntimeException { private ExceptionCode exceptionCode; - private ProfanityDto profanityDto; public BusinessLogicException(ExceptionCode exceptionCode) { super(exceptionCode.getMessage()); this.exceptionCode = exceptionCode; } - - //비속어 필터링 관련 BLE - public BusinessLogicException(ExceptionCode exceptionCode, ProfanityDto profanityDto) { - super(exceptionCode.getMessage()); - this.exceptionCode = exceptionCode; - this.profanityDto = profanityDto; - } } diff --git a/server/src/main/java/com/growstory/global/exception/ExceptionCode.java b/server/src/main/java/com/growstory/global/exception/ExceptionCode.java index b4ecd29a..b0226495 100644 --- a/server/src/main/java/com/growstory/global/exception/ExceptionCode.java +++ b/server/src/main/java/com/growstory/global/exception/ExceptionCode.java @@ -1,7 +1,6 @@ package com.growstory.global.exception; import lombok.Getter; -import org.springframework.http.HttpStatus; @Getter public enum ExceptionCode { @@ -22,10 +21,6 @@ public enum ExceptionCode { COMMENT_NOT_ALLOW(405, "That Comment doesn't have authority"), COMMENT_ALREADY_EXISTS(409, "Comment already exists"), - GUESTBOOK_NOT_FOUND(404, "Guestbook not found"), - GUESTBOOK_NOT_ALLOW(405, "That Guestbook doesn't have authority"), - GUESTBOOK_ALREADY_EXISTS(409, "Guestbook already exists"), - PLANT_OBJECT_NOT_FOUND(404, "Plant Object not found"), PLANT_OBJECT_NOT_ALLOW(405, "That Plant Object doesn't have authority"), PLANT_OBJECT_ALREADY_EXISTS(409, "Plant Object already exists"), @@ -50,16 +45,7 @@ public enum ExceptionCode { NOT_ENOUGH_POINTS(403, "Not Enough Points"), - RANK_NOT_FOUND(404, "Rank not found"), - - BAD_WORD_INCLUDED(406, "Bad word Included"), - - ALARM_NOT_FOUND(404, "Alarm not found"), - - CHATROOM_NOT_FOUND(404, "ChatRoom does not Found"), - ACCOUNT_CHATROOM_NOT_FOUND(404, "AccountChatRoom does not Found"), - ALREADY_CHATROOM_ENTERED(409, "Already entered this ChatRoom"), - WEBSOCKET_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "Exception occurred at Websocket Process"); + RANK_NOT_FOUND(404, "Rank not found"); private final int status; private final String message; diff --git a/server/src/main/java/com/growstory/global/response/ErrorResponse.java b/server/src/main/java/com/growstory/global/response/ErrorResponse.java index bc7d80c9..546e3edf 100644 --- a/server/src/main/java/com/growstory/global/response/ErrorResponse.java +++ b/server/src/main/java/com/growstory/global/response/ErrorResponse.java @@ -1,7 +1,6 @@ package com.growstory.global.response; -import com.growstory.global.badwords.dto.ProfanityDto; import com.growstory.global.exception.ExceptionCode; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -42,11 +41,6 @@ public static ErrorResponse of(ExceptionCode exceptionCode) { return new ErrorResponse(exceptionCode.getStatus(), exceptionCode.getMessage()); } - // 비속어 필터링 관련 예외 응답 처리 - public static ErrorResponse of(ExceptionCode exceptionCode, ProfanityDto profanityDto) { - return new ErrorResponse(exceptionCode.getStatus(), profanityDto.toString()); - } - public static ErrorResponse of(HttpStatus httpStatus) { return new ErrorResponse(httpStatus.value(), httpStatus.getReasonPhrase()); } diff --git a/server/src/main/java/com/growstory/global/response/PageResponse.java b/server/src/main/java/com/growstory/global/response/PageResponse.java deleted file mode 100644 index 246f8bec..00000000 --- a/server/src/main/java/com/growstory/global/response/PageResponse.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.growstory.global.response; - -import lombok.Builder; -import lombok.Getter; -import org.springframework.data.domain.Page; - -@Getter -@Builder -public class PageResponse { - private int totalPage; - private int currentPage; - private Long totalElements; - private int currentElements; - private boolean hasPrevious; - private boolean hasNext; - private boolean isLast; - private T data; - - // 정적 팩토리 메서드, page와 data로부터 ResponseDto 생성 - public static PageResponse of(Page page, T data) { - return PageResponse.builder() - .totalPage(page.getTotalPages()) - .currentPage(page.getNumber()) - .totalElements(page.getTotalElements()) - .currentElements(page.getNumberOfElements()) - .hasPrevious(page.hasPrevious()) - .hasNext(page.hasNext()) - .isLast(page.isLast()) - .data(data) - .build(); - } -} diff --git a/server/src/main/java/com/growstory/global/sse/controller/SseController.java b/server/src/main/java/com/growstory/global/sse/controller/SseController.java deleted file mode 100644 index 43a77d03..00000000 --- a/server/src/main/java/com/growstory/global/sse/controller/SseController.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.growstory.global.sse.controller; - -import com.growstory.domain.alarm.constants.AlarmType; -import com.growstory.global.sse.service.SseService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -@Tag(name = "Server-Sent-Event", description = "SSE Controller") -@Validated -@RequiredArgsConstructor -@RequestMapping("/v1/sse") -@RestController -public class SseController { - private final SseService sseService; - - @Operation(summary = "구독 연결", description = "클라이언트 측과 서버 연결") - @GetMapping(value = "/subscribe/{account-id}", produces = "text/event-stream") // text/event-stream => sse 형식 - public SseEmitter subscribe(@PathVariable("account-id") Long accountId, - @RequestHeader(value = "Last-Event-ID", required = false, defaultValue = "") String lastEventId) { // lastEventId는 타임아웃 이후 재연결 요청을 할 때 헤더로 들어오는 값 - return sseService.subscribe(accountId, lastEventId); - } - -// const eventSource = new EventSource('http://localhost:8888/v1/sse/subscribe/1'); -// eventSource.addEventListener('sse', event => { -// console.log(event); -// }); - - - // 테스트 용 - @Operation(summary = "알림 전송", description = "클라이언트 측으로 알림 전송") - @PostMapping("/send/{account-id}") - public void sendAlarm(@PathVariable("account-id") Long accountId) { - sseService.notify(accountId, AlarmType.DAILY_QUIZ); - } -} diff --git a/server/src/main/java/com/growstory/global/sse/repository/SseEmitterRepository.java b/server/src/main/java/com/growstory/global/sse/repository/SseEmitterRepository.java deleted file mode 100644 index 41e71379..00000000 --- a/server/src/main/java/com/growstory/global/sse/repository/SseEmitterRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.growstory.global.sse.repository; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@RequiredArgsConstructor -@Repository -public class SseEmitterRepository { - // 동시성 문제 thread-safe한 concurrentHashMap 사용 - private final Map emitters = new ConcurrentHashMap<>(); - - public void save(Long accountId, SseEmitter emitter) { - emitters.put(accountId, emitter); - } - - public void deleteById(Long accountId) { - emitters.remove(accountId); - } - - public SseEmitter get(Long accountId) { - return emitters.get(accountId); - } -} diff --git a/server/src/main/java/com/growstory/global/sse/service/SseService.java b/server/src/main/java/com/growstory/global/sse/service/SseService.java deleted file mode 100644 index 333eb546..00000000 --- a/server/src/main/java/com/growstory/global/sse/service/SseService.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.growstory.global.sse.service; - -import com.growstory.domain.alarm.constants.AlarmType; -import com.growstory.domain.alarm.service.AlarmService; -import com.growstory.global.sse.repository.SseEmitterRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.IOException; - -// 추가로, SSE 서비스단에 트랜잭션이 걸려있다면 SSE연결 동안 트랜잭션을 계속 물고 있어 -// 커넥션 낭비가 일어날 수 있으니 SSE 서비스로직에는 트랜잭션을 걸지 않아야 합니다. -@RequiredArgsConstructor -@Service -public class SseService { - // 연결 타임아웃(ms) - private static final Long DEFAULT_TIMEOUT = 60 * 60 * 1000L; -// private static final Long DEFAULT_TIMEOUT = 60L; - - private final SseEmitterRepository sseEmitterRepository; - private final AlarmService alarmService; - - // 클라이언트와 서버 연결(구독) - public SseEmitter subscribe(Long accountId, String lastEventId) { - SseEmitter emitter = createEmitter(accountId); - - sendToClient(accountId, "서버와 연결 완료"); - -// if (hasLostData(lastEventId)) { -// sendLostData(lastEventId, memberId, emitterId, emitter); -// } - - return emitter; - } - -// private boolean hasLostData(String lastEventId) { -// return !lastEventId.isEmpty(); -// } - -// private void sendLostData(String lastEventId, Long memberId, String emitterId, SseEmitter emitter) { -// Map eventCaches = sseEmitterRepository.findAllEventCacheStartWithByMemberId(String.valueOf(memberId)); -// eventCaches.entrySet().stream() -// .filter(entry -> lastEventId.compareTo(entry.getKey()) < 0) -// .forEach(entry -> sendNotification(emitter, entry.getKey(), emitterId, entry.getValue())); -// } - - // 서버의 이벤트를 클라이언트 측으로 전송하기 위해 - // 서버의 다른 서비스에서 호출 - public void notify(Long accountId, AlarmType alarmType) { - alarmService.createAlarm(accountId, alarmType); - - sendToClient(accountId, alarmType.getStepDescription()); - } - - // 클라이언트 측으로 데이터 전송 - private void sendToClient(Long accountId, String data) { - SseEmitter emitter = sseEmitterRepository.get(accountId); - - if (emitter != null) { - try { - emitter.send(SseEmitter.event() - .id(String.valueOf(accountId)) - .name("sse") - .data(data)); - } catch (IOException exception) { - sseEmitterRepository.deleteById(accountId); - emitter.completeWithError(exception); - } - } - } - - private SseEmitter createEmitter(Long accountId) { - SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); - sseEmitterRepository.save(accountId, emitter); - - emitter.onCompletion(() -> sseEmitterRepository.deleteById(accountId)); - emitter.onTimeout(() -> sseEmitterRepository.deleteById(accountId)); - - return emitter; - } -} diff --git a/server/src/main/java/com/growstory/global/utils/UriCreator.java b/server/src/main/java/com/growstory/global/utils/UriCreator.java index b7d0bfc5..16b9c516 100644 --- a/server/src/main/java/com/growstory/global/utils/UriCreator.java +++ b/server/src/main/java/com/growstory/global/utils/UriCreator.java @@ -1,11 +1,9 @@ package com.growstory.global.utils; -import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; -@Component public class UriCreator { public static URI createUri(String defaultUrl, long resourceId) { return UriComponentsBuilder @@ -22,13 +20,4 @@ public static URI creatUri(String defaultUrl, long resourceId1, String anotherRe .buildAndExpand(resourceId1, resourceId2) .toUri(); } - - // 테스트용 - public URI createUri_test(String defaultUrl, long resourceId) { - return UriComponentsBuilder - .newInstance() - .path(defaultUrl+ "/{resource-id}") - .buildAndExpand(resourceId) - .toUri(); - } } \ No newline at end of file diff --git a/server/src/main/java/com/growstory/global/websocket/FilterChannelInterceptor.java b/server/src/main/java/com/growstory/global/websocket/FilterChannelInterceptor.java deleted file mode 100644 index affce50d..00000000 --- a/server/src/main/java/com/growstory/global/websocket/FilterChannelInterceptor.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.growstory.global.websocket; - -import com.growstory.global.auth.filter.JwtVerificationFilter; -import com.growstory.global.auth.jwt.JwtTokenizer; -import com.growstory.global.exception.BusinessLogicException; -import com.growstory.global.exception.ExceptionCode; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.security.SignatureException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.simp.stomp.StompCommand; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.Objects; - -@Order(Ordered.HIGHEST_PRECEDENCE + 99) -@Slf4j -@RequiredArgsConstructor -@Component -public class FilterChannelInterceptor implements ChannelInterceptor { - private final JwtTokenizer jwtTokenizer; - private final JwtVerificationFilter jwtVerificationFilter; - - @Override - public Message preSend(Message message, MessageChannel channel) { - StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message); - log.info("## StompHeaderAccessor: " + headerAccessor); - - if(StompCommand.CONNECT.equals(headerAccessor.getCommand())) { // 웹소켓 연결 요청 -> JWT 인증 - log.info("## CONNECT : 소켓 연결 "); - String accessToken = getAccessTokenFrom(headerAccessor); - String refreshToken = getRefreshTokenFrom(headerAccessor); - //TODO: refresh 토큰 관련 로직 개선 - try { - Map claims = verifyJws(accessToken); - putValue(headerAccessor, "senderId", claims.get("accountId")); - putValue(headerAccessor, "displayName", claims.get("displayName")); - log.info("## Claims : {}", claims); - } catch (SignatureException | MalformedJwtException sme) { - log.info("잘못된 JWT 서명입니다."); - putValue(headerAccessor, "exception", sme); - } catch (ExpiredJwtException ee) { - log.info("만료된 JWT 토큰입니다."); - putValue(headerAccessor, "exception", ee); - } catch (Exception e) { - putValue(headerAccessor, "exception", e); - } - } else if (StompCommand.SEND == (headerAccessor.getCommand())) { - String payload = new String((byte[]) message.getPayload()); - log.info("Message Payload : "+ payload); - } else if (StompCommand.SUBSCRIBE == (headerAccessor.getCommand())) { - String chatRoomId = getChatRoomIdFrom(headerAccessor); - putValue(headerAccessor, "chatRoomId", chatRoomId); - log.info("## SUBSCRIBE: accountId ({})번, ({})님이 ({})번 채팅방을 구독하셨습니다." + headerAccessor - , getValue(headerAccessor, "senderId"), getValue(headerAccessor, "displayName") - , getValue(headerAccessor, "chatRoomId")); - } else if (StompCommand.DISCONNECT == (headerAccessor.getCommand())) { - log.info("## DISCONNECT: accountId ({})번, ({})님이 ({})번 채팅방을 떠났습니다." + headerAccessor - , getValue(headerAccessor, "senderId"), getValue(headerAccessor, "displayName") - , getValue(headerAccessor, "chatRoomId")); - } - - return message; - } - - - private String getAccessTokenFrom(StompHeaderAccessor headerAccessor) { - String accessToken = headerAccessor.getNativeHeader("Authorization").toString(); - accessToken = accessToken.substring(1, accessToken.length()-1).replace("Bearer ", ""); - putValue(headerAccessor, "Authorization", accessToken); - log.info("## accessToken: " + accessToken); - return accessToken; - } - private String getRefreshTokenFrom(StompHeaderAccessor headerAccessor) { - String refreshToken = headerAccessor.getNativeHeader("refresh").toString(); - refreshToken = refreshToken.substring(1, refreshToken.length()-1); - putValue(headerAccessor, "refresh", refreshToken); - log.info("## refreshToken: " + refreshToken); - return refreshToken; - } - private static String getChatRoomIdFrom(StompHeaderAccessor headerAccessor) { - String destination = headerAccessor.getDestination(); - String chatRoomId = destination.substring(destination.lastIndexOf('/')+1); - return chatRoomId; - } - - private Map verifyJws(String token) { - //request의 header에서 JWT 얻기 - // String jws = request.getHeader("Authorization").replace("Bearer ", ""); - //서버에 저장된 비밀키 호출 - String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey()); - //Claims (JWT의 Payload, 사용자 정보인 username, roles 얻기) < - 내부적으로 서명(Signature) 검증에 성공한 상태 - Map claims = jwtTokenizer.getClaims(token, base64EncodedSecretKey).getBody(); - - return claims; - } - - private void putValue(StompHeaderAccessor accessor, String key, Object value) { - Map sessionAttributes = getSessionAttributes(accessor); - sessionAttributes.put(key, value); - } - - private Object getValue(StompHeaderAccessor accessor, String key) { - Map sessionAttributes = getSessionAttributes(accessor); - Object value = sessionAttributes.get(key); - - if(Objects.isNull(value)) { - log.info("sessionAttributes doesnt has a key named {}", key); - throw new BusinessLogicException(ExceptionCode.WEBSOCKET_EXCEPTION); - } - return value; - } - - private Map getSessionAttributes(StompHeaderAccessor accessor) { - Map sessionAttributes = accessor.getSessionAttributes(); - - if (Objects.isNull(sessionAttributes)) { - log.info("SessionAttributes is Null"); - throw new BusinessLogicException(ExceptionCode.WEBSOCKET_EXCEPTION); - } - return sessionAttributes; - } - -} diff --git a/server/src/main/java/com/growstory/global/websocket/WebSocketConfig.java b/server/src/main/java/com/growstory/global/websocket/WebSocketConfig.java deleted file mode 100644 index 279fb5aa..00000000 --- a/server/src/main/java/com/growstory/global/websocket/WebSocketConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.growstory.global.websocket; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.simp.config.ChannelRegistration; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -@Configuration -@RequiredArgsConstructor -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - private final FilterChannelInterceptor filterChannelInterceptor; - - // 엔드 포인트 및 CORS 설정 - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // STOMP 접속 주소 - registry.addEndpoint("/v1/wss") - // CORS 설정 - .setAllowedOrigins("https://growstory.vercel.app", "http://localhost:3000", "http://localhost:8888") - // SockJS 라이브러리 사용 - .withSockJS(); - } - - // 메시지 브로커 설정 - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - // 서버 -> 클라이언트, '/sub'이 prefix로 붙은 destination의 클라이언트에게 메시지 전송 - registry.enableSimpleBroker("/sub"); - // 클라이언트 -> 서버, '/pub'이 prefix로 붙은 메시지들은 @MessageMapping이 붙은 메소드로 바운드 된다. - registry.setApplicationDestinationPrefixes("/pub"); - } - - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(filterChannelInterceptor); - } -} diff --git a/server/src/main/resources/application-prod.yml b/server/src/main/resources/application-prod.yml index cf062f25..606af3e3 100644 --- a/server/src/main/resources/application-prod.yml +++ b/server/src/main/resources/application-prod.yml @@ -87,15 +87,11 @@ springdoc: mail: admin: address: ${ADMIN_EMAIL} - guest: ${GUEST_EMAIL} my: scheduled: cron: 0 0 0 ? * MON -event: - key: ${EVENT_KEY} - server: port: 443 ssl: diff --git a/server/src/main/resources/images/test1.jpg b/server/src/main/resources/images/test1.jpg deleted file mode 100644 index 5d4800a3bbd2c2913aa20009554c3d70b2057f5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139670 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

9Kw* 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%0Ah2HeOQA4dUSWb$|*-smp(WWTjK#*#*kONrm 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 V`~4FF5QSigAc#Kb*h&zC|Je~gyj1`I diff --git a/server/src/main/resources/images/test2.jpg b/server/src/main/resources/images/test2.jpg deleted file mode 100644 index ebe0e2ad63ccc68ff651a7d26149922ab9ec41f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54151 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 BXe0mt diff --git a/server/src/main/resources/templates/qnaResponse.html b/server/src/main/resources/templates/qnaResponse.html deleted file mode 100644 index f7e2f8d1..00000000 --- a/server/src/main/resources/templates/qnaResponse.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - Grow Story 문의 답변 안내 - - - - - - - - - - - - - - - -
- Grow Story Logo -
- - - - - - - - - - - - - - - - -
안녕하세요.
- 자신이 키우는 식물에 대해 얘기하고 가상의 정원을 가꿔보는 - 플랫폼
- Grow Story입니다. -
 
- 문의하신 내용에 대한 답변이 작성되었습니다. -
- 아래의 링크에서 상세 답변 내용을 확인해주세요. -
-
- -
- Copyright@Team Grow Story -
- - diff --git a/server/src/test/java/com/growstory/GrowstoryApplicationTests.java b/server/src/test/java/com/growstory/GrowstoryApplicationTests.java deleted file mode 100644 index c12ebb06..00000000 --- a/server/src/test/java/com/growstory/GrowstoryApplicationTests.java +++ /dev/null @@ -1,9 +0,0 @@ -//package com.growstory; -// -//import org.junit.jupiter.api.Test; -//import org.springframework.boot.test.context.SpringBootTest; -// -//@SpringBootTest -//class GrowstoryApplicationTests { -// -//} diff --git a/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java b/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java deleted file mode 100644 index b3166709..00000000 --- a/server/src/test/java/com/growstory/domain/account/controller/AccountControllerTest.java +++ /dev/null @@ -1,339 +0,0 @@ -//package com.growstory.domain.account.controller; -// -//import com.google.gson.Gson; -//import com.growstory.domain.account.dto.AccountDto; -//import com.growstory.domain.account.service.AccountService; -//import com.growstory.domain.point.entity.Point; -//import org.junit.jupiter.api.Test; -//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.context.SpringBootTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageImpl; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.http.HttpMethod; -//import org.springframework.http.MediaType; -//import org.springframework.mock.web.MockMultipartFile; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.ResultActions; -//import org.springframework.web.multipart.MultipartFile; -// -//import java.io.FileInputStream; -//import java.util.ArrayList; -//import java.util.List; -// -//import static org.hamcrest.Matchers.is; -//import static org.hamcrest.Matchers.not; -//import static org.mockito.BDDMockito.given; -//import static org.mockito.BDDMockito.willDoNothing; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -// -//@SpringBootTest -//@AutoConfigureMockMvc -//public class AccountControllerTest { -// @Autowired -// private MockMvc mockMvc; -// -// @Autowired -// private Gson gson; -// -// @MockBean -// private AccountService accountService; -// -// @Test -// void 회원가입() throws Exception { -// // given -// AccountDto.Post requestDto = AccountDto.Post.builder() -// .email("user1@gmail.com") -// .displayName("user1") -// .password("user1234") -// .build(); -// -// AccountDto.Response responseDto = getResponseDto(1L, "user1@gmail.com", "user1"); -// -// given(accountService.createAccount(Mockito.any(AccountDto.Post.class))) -// .willReturn(responseDto); -// -// // when -// ResultActions actions = mockMvc.perform( -// post("/v1/accounts/signup") -// .contentType(MediaType.APPLICATION_JSON) // contentType은 default가 application/octet-stream -// .content(gson.toJson(requestDto))); -// -// // then -// actions -// .andExpect(status().isCreated()) -// .andExpect(header().string("Location", is("/v1/accounts/" + responseDto.getAccountId().toString()))) -// .andDo(print()); -// } -// -// @Test -// void 프로필_사진_변경() throws Exception { -// // given -// MockMultipartFile testImage = new MockMultipartFile("profileImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// willDoNothing().given(accountService).updateProfileImage(Mockito.any(MultipartFile.class)); -// -// // when -// ResultActions actions = mockMvc.perform( -// multipart(HttpMethod.PATCH, "/v1/accounts/profileimage") -// .file(testImage)); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// @Test -// void 닉네임_수정() throws Exception { -// // given -// AccountDto.DisplayNamePatch requestDto = AccountDto.DisplayNamePatch.builder() -// .displayName("user2") -// .build(); -// -// willDoNothing().given(accountService).updateDisplayName(Mockito.any(AccountDto.DisplayNamePatch.class)); -// -// // when -// ResultActions actions = mockMvc.perform( -// patch("/v1/accounts/displayname") -// .contentType("application/json") -// .content(gson.toJson(requestDto))); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// @Test -// void 비밀번호_수정() throws Exception { -// // given -// AccountDto.PasswordPatch requestDto = AccountDto.PasswordPatch.builder() -// .presentPassword("user1234") -// .changedPassword("user4321") -// .build(); -// -// willDoNothing().given(accountService).updatePassword(Mockito.any(AccountDto.PasswordPatch.class)); -// -// // when -// ResultActions actions = mockMvc.perform( -// patch("/v1/accounts/password") -// .contentType("application/json") -// .content(gson.toJson(requestDto))); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// @Test -// void 나의_계정_조회() throws Exception { -// // given -// Long accountId = 1L; -// -// AccountDto.Response responseDto = getResponseDto(accountId, "user1@gmail.com", "user1"); -// -// given(accountService.getAccount(Mockito.anyLong())) -// .willReturn(responseDto); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/accounts/" + accountId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.email", is("user1@gmail.com"))) -// .andDo(print()); -// } -// -// @Test -// void 전체_계정_조회() throws Exception { -// // given -// List responseDtos = getResponseDtos(); -// -// given(accountService.getAccounts()) -// .willReturn(responseDtos); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/accounts/all")); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data[0].email", is("user1@gmail.com"))) -// .andExpect(jsonPath("$.data[1].email", is("user2@gmail.com"))) -// .andDo(print()); -// } -// -// @Test -// void 계정이_작성한_게시글_조회() throws Exception { -// // given -// Long accountId = 1L; -// int page = 1; -// -// List responseDtos = getBoardResponseDtos(); -// -// Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); -// -// given(accountService.getAccountBoardWritten(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) -// .willReturn(responsePage); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/accounts/boardWritten/" + accountId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data[0].title", is("제목1"))) -// .andExpect(jsonPath("$.data[1].title", is("제목2"))) -// .andExpect(jsonPath("$.pageInfo.page", is(page))) -// .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) -// .andDo(print()); -// } -// -// @Test -// void 계정이_좋아요_누른_게시글_조회() throws Exception { -// // given -// Long accountId = 1L; -// int page = 1; -// -// List responseDtos = getBoardResponseDtos(); -// -// Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); -// -// given(accountService.getAccountBoardLiked(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) -// .willReturn(responsePage); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/accounts/boardLiked/" + accountId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data[*]..likes[?(1 == @)]").exists()) -// .andExpect(jsonPath("$.pageInfo.page", is(page))) -// .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) -// .andDo(print()); -// } -// -// @Test -// void 계정이_댓글_작성한_게시글_조회() throws Exception { -// // given -// Long accountId = 1L; -// int page = 1; -// -// List responseDtos = getBoardResponseDtos(); -// -// Page responsePage = new PageImpl<>(responseDtos, PageRequest.of(page - 1,12), responseDtos.size()); -// -// given(accountService.getAccountCommentWrittenBoard(Mockito.anyInt(), Mockito.anyInt(), Mockito.anyLong())) -// .willReturn(responsePage); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/accounts/commentWritten/" + accountId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data[*].commentNums", is(not(0)))) -// .andExpect(jsonPath("$.pageInfo.page", is(page))) -// .andExpect(jsonPath("$.pageInfo.totalElements", is(responseDtos.size()))) -// .andDo(print()); -// } -// -// @Test -// void 비밀번호_검증() throws Exception { -// // given -// AccountDto.PasswordVerify requestDto = AccountDto.PasswordVerify.builder() -// .password("user1234") -// .build(); -// -// Boolean isMatched = true; -// -// given(accountService.verifyPassword(Mockito.any(AccountDto.PasswordVerify.class))) -// .willReturn(isMatched); -// -// // when -// ResultActions actions = mockMvc.perform( -// post("/v1/accounts/password/verification") -// .contentType("application/json") -// .content(gson.toJson(requestDto))); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data", is(isMatched))) -// .andDo(print()); -// } -// -// @Test -// void 회원탈퇴() throws Exception { -// // given -// willDoNothing().given(accountService).deleteAccount(); -// -// // when -// ResultActions actions = mockMvc.perform( -// delete("/v1/accounts")); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// private static AccountDto.Response getResponseDto(Long accountId, String email, String displayName) { -// return AccountDto.Response.builder() -// .accountId(accountId) -// .email(email) -// .displayName(displayName) -// .point(Point.builder().score(500).build()) -// .build(); -// } -// -// private static List getResponseDtos() { -// List responseDtos = new ArrayList<>(); -// -// AccountDto.Response responseDto1 = getResponseDto(1L, "user1@gmail.com", "user1"); -// AccountDto.Response responseDto2 = getResponseDto(2L, "user2@gmail.com", "user2"); -// -// responseDtos.add(responseDto1); -// responseDtos.add(responseDto2); -// -// return responseDtos; -// } -// -// private static AccountDto.BoardResponse getBoardResponseDto(Long boardId, String title, List likes, int commentNums) { -// return AccountDto.BoardResponse.builder() -// .boardId(boardId) -// .title(title) -// .likes(likes) -// .commentNums(commentNums) -// .build(); -// } -// -// private static List getBoardResponseDtos() { -// List responseDtos = new ArrayList<>(); -// -// AccountDto.BoardResponse responseDto1 = getBoardResponseDto(1L, "제목1", List.of(1L), 1); -// AccountDto.BoardResponse responseDto2 = getBoardResponseDto(2L, "제목2", List.of(1L, 2L), 2); -// -// responseDtos.add(responseDto1); -// responseDtos.add(responseDto2); -// -// return responseDtos; -// } -//} diff --git a/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java b/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java deleted file mode 100644 index 8461d43b..00000000 --- a/server/src/test/java/com/growstory/domain/account/service/AccountServiceTest.java +++ /dev/null @@ -1,617 +0,0 @@ -//package com.growstory.domain.account.service; -// -//import com.growstory.domain.account.dto.AccountDto; -//import com.growstory.domain.account.entity.Account; -//import com.growstory.domain.account.repository.AccountRepository; -//import com.growstory.domain.board.entity.Board; -//import com.growstory.domain.comment.entity.Comment; -//import com.growstory.domain.likes.entity.BoardLike; -//import com.growstory.domain.point.entity.Point; -//import com.growstory.domain.point.service.PointService; -//import com.growstory.global.auth.utils.AuthUserUtils; -//import com.growstory.global.auth.utils.CustomAuthorityUtils; -//import com.growstory.global.aws.service.S3Uploader; -//import com.growstory.global.exception.BusinessLogicException; -//import com.growstory.global.exception.ExceptionCode; -//import org.junit.jupiter.api.*; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.Mockito; -//import org.mockito.junit.jupiter.MockitoExtension; -//import org.springframework.data.domain.Page; -//import org.springframework.mock.web.MockMultipartFile; -//import org.springframework.security.authentication.BadCredentialsException; -//import org.springframework.security.core.Authentication; -//import org.springframework.security.core.context.SecurityContext; -//import org.springframework.security.core.context.SecurityContextHolder; -//import org.springframework.security.crypto.password.PasswordEncoder; -// -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.util.*; -// -//import static org.hamcrest.MatcherAssert.assertThat; -//import static org.hamcrest.Matchers.contains; -//import static org.hamcrest.Matchers.is; -//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -//import static org.junit.jupiter.api.Assertions.assertThrows; -//import static org.mockito.BDDMockito.*; -// -//@ExtendWith(MockitoExtension.class) -//public class AccountServiceTest { -// @InjectMocks // @Mock으로 만들어진 객체를 의존성 주입받는 객체 -// private AccountService accountService; -// @Mock -// private PasswordEncoder passwordEncoder; -// @Mock -// private CustomAuthorityUtils customAuthorityUtils; -// @Mock -// private PointService pointService; -// @Mock -// private S3Uploader s3Uploader; -// @Mock -// private AuthUserUtils authUserUtils; -// @Mock -// private static Authentication authentication; -// @Mock -// private AccountRepository accountRepository; -// @Mock -// private static SecurityContext securityContext = mock(SecurityContext.class); -// @Mock -// private Point point = mock(Point.class); -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 회원가입 { -// // given -// AccountDto.Post requestDto = AccountDto.Post.builder() -// .email("user@gmail.com") -// .displayName("user1") -// .password("user1234") -// .build(); -// List roles = List.of("USER"); -// -// @Test -// @Order(1) -// public void 중복된_이메일이면_회원가입_실패() { -// given(accountRepository.findByEmail(Mockito.anyString())) -// .willReturn(Optional.of(Account.builder().build())); -// -// // when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.createAccount(requestDto)); -// assertThat(exception.getExceptionCode().getStatus(), is(409)); -// assertThat(exception.getExceptionCode().getMessage(), is("Account already exists")); -// } -// -// @Test -// @Order(2) -// public void 아니면_회원가입_성공() { -// given(accountRepository.findByEmail(Mockito.anyString())) -// .willReturn(Optional.empty()); -// -// given(passwordEncoder.encode(Mockito.anyString())) -// .willReturn(requestDto.getPassword()); -// given(customAuthorityUtils.createRoles(Mockito.anyString())) -// .willReturn(roles); -// given(pointService.createPoint(Mockito.anyString())) -// .willReturn(point); -// -// Account savedAccount = getAccount(1L, requestDto.getEmail(), requestDto.getDisplayName(), -// requestDto.getPassword(), "path", point, roles, Account.AccountGrade.GRADE_BRONZE); -// -// given(accountRepository.save(Mockito.any(Account.class))) -// .willReturn(savedAccount); -// -// willDoNothing().given(point).updateAccount(Mockito.any(Account.class)); -// -// // when -// AccountDto.Response responseDto = accountService.createAccount(requestDto); -// -// // then -// assertThat(responseDto.getAccountId(), is(savedAccount.getAccountId())); -// } -// } -// -// @Nested -// class 이미지_수정 { -// // given -// String s3ImageUrl = "s3/path"; -// -// MockMultipartFile testImage = new MockMultipartFile("profileImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// 이미지_수정() throws IOException { -// } -// -// private void uploadImage() { -// given(s3Uploader.uploadImageToS3(Mockito.any(MockMultipartFile.class), Mockito.anyString())) -// .willReturn(s3ImageUrl); -// given(accountRepository.save(Mockito.any(Account.class))) -// .willReturn(account.toBuilder().profileImageUrl(s3ImageUrl).build()); -// } -// -// @Test -// public void 기존_이미지가_존재할_때() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// -// uploadImage(); -// -// // when , then -// assertDoesNotThrow(() -> accountService.updateProfileImage(testImage)); -// verify(s3Uploader, times(1)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// } -// -// @Test -// public void 기존_이미지가_없을_때() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account.toBuilder().profileImageUrl(null).build()); -// -// uploadImage(); -// -// // when, then -// assertDoesNotThrow(() -> accountService.updateProfileImage(testImage)); -// verify(s3Uploader, times(0)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// } -// } -// -// @Test -// public void 닉네임_수정() { -// // given -// String updatedDisplayName = "updatedDisplayName"; -// -// AccountDto.DisplayNamePatch displayNamePatchDto = AccountDto.DisplayNamePatch.builder().displayName(updatedDisplayName).build(); -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// given(accountRepository.save(Mockito.any(Account.class))) -// .willReturn(account.toBuilder().displayName(displayNamePatchDto.getDisplayName()).build()); -// -// // when, then -// assertDoesNotThrow(() -> accountService.updateDisplayName(displayNamePatchDto)); -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 비밀번호_수정 { -// // given -// String updatedPassword = "updatedPassword"; -// String samePassword = "user1234"; -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// AccountDto.PasswordPatch passwordPatchDto = AccountDto.PasswordPatch.builder() -// .presentPassword(account.getPassword()) -// .changedPassword(updatedPassword) -// .build(); -// -// private void init(String updatedPassword) { -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// given(passwordEncoder.encode(Mockito.anyString())) -// .willReturn(updatedPassword); -// } -// -// @Test -// @Order(1) -// public void 현재_비밀번호가_일치하지_않으면_실패() { -// init(updatedPassword); -// given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) -// .willReturn(false); -// -// // when, then -// BadCredentialsException exception = assertThrows(BadCredentialsException.class, -// () -> accountService.updatePassword(passwordPatchDto)); -// assertThat(exception.getMessage(), is("현재 비밀번호가 일치하지 않습니다.")); -// } -// -// @Test -// @Order(2) -// public void 새로운_비밀번호가_현재와_같으면_실패() { -// init(samePassword); -// given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) -// .willReturn(true); -// -// // when, then -// BadCredentialsException exception = assertThrows(BadCredentialsException.class, -// () -> accountService.updatePassword(passwordPatchDto)); -// assertThat(exception.getMessage(), is("새로운 비밀번호와 현재 비밀번호가 일치합니다.")); -// } -// -// @Test -// @Order(3) -// public void 아니면_수정_성공() { -// init(updatedPassword); -// given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) -// .willReturn(true); -// -// given(accountRepository.save(Mockito.any(Account.class))) -// .willReturn(account.toBuilder().password(updatedPassword).build()); -// // when, then -// assertDoesNotThrow(() -> accountService.updatePassword(passwordPatchDto)); -// } -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 단일_사용자_조회 { -// // given -// Long accountId = 1L; -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// @Test -// @Order(1) -// public void 입력한_ID의_사용자가_존재하지_않으면_실패() { -// accountVerification(accountId); -// } -// -// @Test -// @Order(2) -// public void 존재한다면_성공() { -// given(accountRepository.findById(accountId)) -// .willReturn(Optional.of(account)); -// -// // when -// AccountDto.Response responseDto = accountService.getAccount(accountId); -// -// // then -// assertThat(responseDto.getAccountId(), is(accountId)); -// } -// } -// -// @Test -// public void 전체_사용자_조회() { -// // given -// Account account1 = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Account account2 = getAccount(2L, "user2@gmail.com", "user2", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Account account3 = getAccount(3L, "user3@gmail.com", "user3", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// given(accountRepository.findAll()) -// .willReturn(List.of(account1, account2, account3)); -// -// // when -// List responseDtos = accountService.getAccounts(); -// -// // then -// assertThat(responseDtos.size(), is(3)); -// assertThat(responseDtos.get(0).getAccountId(), is(1L)); -// assertThat(responseDtos.get(1).getAccountId(), is(2L)); -// assertThat(responseDtos.get(2).getAccountId(), is(3L)); -// } -// -// @Nested -// class 사용자_관련_게시글_조회 { -// // given -// int page = 1; -// int size = 12; -// Long accountId = 1L; -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// BoardLike boardLike = BoardLike.builder().account(account).build(); -// Comment comment1 = Comment.builder().account(account).build(); -// Comment comment2 = Comment.builder().account(account).build(); -// Board board1 = getBoard(1L, account, List.of(comment1), new ArrayList<>()); -// Board board2 = getBoard(2L, account, List.of(comment2), List.of(boardLike)); -// Board board3 = getBoard(3L, account, new ArrayList<>(), new ArrayList<>()); -// -// -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 사용자가_쓴_게시글_조회 { -// @Test -// @Order(1) -// public void 입력한_ID의_사용자가_존재하지_않으면_실패() { -// accountVerification(accountId); -// } -// -// @Test -// @Order(2) -// public void 존재한다면_성공() { -// given(accountRepository.findById(accountId)) -// .willReturn(Optional.of(account.toBuilder() -// .boards(List.of(board1, board2, board3)) -// .build())); -// -// // when -// Page boardResponsePages = accountService.getAccountBoardWritten(page - 1, size, accountId); -// -// // then -// assertThat(boardResponsePages.getContent().size(), is(3)); -// assertThat(boardResponsePages.getNumber(), is(page - 1)); -// } -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 사용자가_좋아요_누른_게시글_조회 { -// @Test -// @Order(1) -// public void 입력한_ID의_사용자가_존재하지_않으면_실패() { -// accountVerification(accountId); -// } -// -// @Test -// @Order(2) -// public void 존재한다면_성공() { -// given(accountRepository.findById(accountId)) -// .willReturn(Optional.of(account.toBuilder() -// .boards(List.of(board2)) -// .boardLikes(List.of(boardLike.toBuilder().board(board2).build())) -// .build())); -// -// // when -// Page boardResponsePages = accountService.getAccountBoardLiked(page - 1, size, accountId); -// -// // then -// assertThat(boardResponsePages.getContent().size(), is(1)); -// assertThat(boardResponsePages.getNumber(), is(page - 1)); -// assertThat(boardResponsePages.getContent().get(0).getLikes(), contains(1L)); -// } -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 사용자가_댓글_쓴_게시글_조회 { -// @Test -// @Order(1) -// public void 입력한_ID의_사용자가_존재하지_않으면_실패() { -// accountVerification(accountId); -// } -// -// @Test -// @Order(2) -// public void 존재한다면_성공() { -// given(accountRepository.findById(accountId)) -// .willReturn(Optional.of(account.toBuilder() -// .boards(List.of(board1, board2)) -// .comments(List.of( -// comment1.toBuilder().board(board1).build(), -// comment2.toBuilder().board(board2).build())) -// .build())); -// -// // when -// Page boardResponsePages = accountService.getAccountCommentWrittenBoard(page - 1, size, accountId); -// -// // then -// assertThat(boardResponsePages.getContent().size(), is(2)); -// assertThat(boardResponsePages.getNumber(), is(page - 1)); -// assertThat(boardResponsePages.getContent().get(0).getCommentNums(), is(1)); -// assertThat(boardResponsePages.getContent().get(1).getCommentNums(), is(1)); -// } -// } -// } -// -// @Nested -// class 회원탈퇴 { -// // given -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// @Test -// public void 기존_이미지가_존재할_때() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// -// // when , then -// assertDoesNotThrow(() -> accountService.deleteAccount()); -// verify(s3Uploader, times(1)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// } -// -// @Test -// public void 기존_이미지가_없을_때() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account.toBuilder().profileImageUrl(null).build()); -// -// // when, then -// assertDoesNotThrow(() -> accountService.deleteAccount()); -// verify(s3Uploader, times(0)).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -// } -// } -// -// @Nested -// class 회원탈퇴_시_비밀번호_검증 { -// // given -// String password = "user1234"; -// String diffPassword = "admin1234"; -// -// AccountDto.PasswordVerify passwordVerifyDto = AccountDto.PasswordVerify -// .builder() -// .password(password) -// .build(); -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// password, "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// @Test -// public void 입력이_현재_비밀번호와_같을_때() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) -// .willReturn(passwordVerifyDto.getPassword().equals(account.getPassword())); -// -// // when -// Boolean response = accountService.verifyPassword(passwordVerifyDto); -// -// // then -// assertThat(response, is(true)); -// } -// -// @Test -// public void 입력이_현재_비밀번호와_다를_때() { -// passwordVerifyDto = passwordVerifyDto.toBuilder().password(diffPassword).build(); -// -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// given(passwordEncoder.matches(Mockito.anyString(), Mockito.anyString())) -// .willReturn(passwordVerifyDto.getPassword().equals(account.getPassword())); -// -// // when -// Boolean response = accountService.verifyPassword(passwordVerifyDto); -// -// // then -// assertThat(response, is(false));} -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 사용자_검증 { -// // given -// Map claims = new HashMap<>(); -// Long accountId = 1L; -// -// @BeforeEach -// private void securityInit() { -// SecurityContextHolder.setContext(securityContext); -// securityContext.setAuthentication(authentication); -// -// given(SecurityContextHolder.getContext().getAuthentication()) -// .willReturn(authentication); -// } -// -// @Test -// @Order(1) -// public void 로그인된_사용자가_없다면_실패() { -// given(SecurityContextHolder.getContext().getAuthentication()) -// .willReturn(null); -// -// //when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.isAuthIdMatching(accountId)); -// assertThat(exception.getExceptionCode().getStatus(), is(404)); -// assertThat(exception.getExceptionCode().getMessage(), is("Account not found")); -// } -// -// @Test -// @Order(2) -// public void 인증되지_않은_사용자라면_실패() { -// given(authentication.getPrincipal()) -// .willReturn(claims); -// -// given(authentication.getName()) -// .willReturn(null); -// -// //when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.isAuthIdMatching(accountId)); -// assertThat(exception.getExceptionCode().getStatus(), is(401)); -// assertThat(exception.getExceptionCode().getMessage(), is("Account unauthorized")); -// } -// -// @Test -// @Order(3) -// public void 익명_사용자라면_실패() { -// given(authentication.getPrincipal()) -// .willReturn(claims); -// -// given(authentication.getName()) -// .willReturn("anonymousUser"); -// -// //when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.isAuthIdMatching(accountId)); -// assertThat(exception.getExceptionCode().getStatus(), is(401)); -// assertThat(exception.getExceptionCode().getMessage(), is("Account unauthorized")); -// } -// -// @Test -// @Order(4) -// public void 로그인된_사용자와_입력된_사용자가_다르면_실패() { -// given(authentication.getPrincipal()) -// .willReturn(claims); -// -// claims.put("accountId", "999"); -// -// given(authentication.getName()) -// .willReturn("SeungTaeLee"); -// -// //when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.isAuthIdMatching(accountId)); -// assertThat(exception.getExceptionCode().getStatus(), is(405)); -// assertThat(exception.getExceptionCode().getMessage(), is("That Account doesn't have authority")); -// } -// -// @Test -// @Order(5) -// public void 로그인된_사용자가_입력과_동일하면_성공() { -// given(authentication.getPrincipal()) -// .willReturn(claims); -// -// claims.put("accountId", "1"); -// -// given(authentication.getName()) -// .willReturn("SeungTaeLee"); -// -// //when, then -// assertDoesNotThrow(() -> accountService.isAuthIdMatching(accountId)); -// } -// } -// -// private static Account getAccount(Long accountId, String email, String displayName, String password, -// String profileImageUrl, Point point, List roles, Account.AccountGrade accountGrade) { -// return Account.builder() -// .accountId(accountId) -// .email(email) -// .displayName(displayName) -// .password(password) -// .profileImageUrl(profileImageUrl) -// .point(point) -// .roles(roles) -// .accountGrade(accountGrade) -// .build(); -// } -// -// private static Board getBoard(Long boardId, Account account, List boardComments, List boardLikes) { -// return Board.builder() -// .boardId(boardId) -// .boardImages(new ArrayList<>()) -// .account(account) -// .boardComments(boardComments) -// .boardLikes(boardLikes) -// .build(); -// } -// -// private void accountVerification(Long accountId) { -// given(accountRepository.findById(accountId)) -// .willReturn(Optional.empty()); -// -// // when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> accountService.getAccount(accountId)); -// assertThat(exception.getExceptionCode().getStatus(), is(404)); -// assertThat(exception.getExceptionCode().getMessage(), is("Account not found")); -// } -//} diff --git a/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java b/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java deleted file mode 100644 index 9b6db6d2..00000000 --- a/server/src/test/java/com/growstory/domain/board/controller/BoardControllerTest.java +++ /dev/null @@ -1,77 +0,0 @@ -//package com.growstory.domain.board.controller; -// -//import com.google.gson.Gson; -//import com.growstory.domain.board.service.BoardService; -//import org.junit.jupiter.api.DisplayName; -//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.boot.test.context.SpringBootTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.test.web.servlet.MockMvc; -// -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -// -//import static org.junit.jupiter.api.Assertions.*; -// -// -//@SpringBootTest -//@AutoConfigureMockMvc -//class BoardControllerTest { -// -// // CICD plz -// @Autowired -// private MockMvc mockMvc; -// -// @Autowired -// private Gson gson; -// -// @MockBean -// private BoardService boardService; -// -// private final String BOARD_DEFAULT_URL = "/v1/boards"; -// -// -// @DisplayName("게시판 등록") -// @Test -// void 게시판_등록() throws Exception { -// -// // given -// String title = "게시글 제목"; -// String content = "게시글 본문"; -// List hashTagList = new ArrayList<>(); -// hashTagList.add("tag1"); -// hashTagList.add("tag2"); -// -// // when -// -// // then -// -// } -// -// @Test -// void getBoard() { -// } -// -// @Test -// void getBoards() { -// } -// -// @Test -// void getBoardsByKeyword() { -// } -// -// @Test -// void patchBoard() { -// } -// -// @Test -// void deleteBoard() { -// } -// -// @Test -// void getTop3LikedBoardsOfWeek() { -// } -//} \ No newline at end of file diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java deleted file mode 100644 index dacf2061..00000000 --- a/server/src/test/java/com/growstory/domain/board/service/BoardRankingTest.java +++ /dev/null @@ -1,192 +0,0 @@ -//package com.growstory.domain.board.service; -// -//import com.growstory.domain.board.repository.BoardHashTagRepository; -//import com.growstory.domain.board.repository.BoardRepository; -//import com.growstory.domain.comment.service.CommentService; -//import com.growstory.domain.hashTag.repository.HashTagRepository; -//import com.growstory.domain.hashTag.service.HashTagService; -//import com.growstory.domain.images.service.BoardImageService; -//import com.growstory.domain.rank.board_likes.entity.BoardLikesRank; -//import com.growstory.domain.stubdata.Stub; -//import com.growstory.global.auth.utils.AuthUserUtils; -//import org.junit.jupiter.api.*; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.Mockito; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//import java.time.LocalDateTime; -//import java.util.ArrayList; -//import java.util.List; -// -//import static org.hamcrest.MatcherAssert.assertThat; -//import static org.hamcrest.Matchers.is; -//import static org.mockito.BDDMockito.given; -// -//@ExtendWith(MockitoExtension.class) -//public class BoardRankingTest { -// -// @InjectMocks -// private BoardService boardService; -// @Mock -// private BoardRepository boardRepository; -// @Mock -// private HashTagService hashTagService; -// @Mock -// private BoardImageService boardImageService; -// @Mock -// private AuthUserUtils authUserUtils; -// @Mock -// private HashTagRepository hashTagRepository; -// @Mock -// private BoardHashTagRepository boardHashtagRepository; -// @Mock -// private CommentService commentService; -// -// -// @DisplayName("좋아요 기준 상위 3개의 게시글 랭킹과 함께 반환") -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class FindTop3LikedBoardRanksTest { -// -// //given -// List mockTopBoardsWithLikes = new ArrayList<>(); -// Object[] mockObjectsLike3_1 = {Stub.MockBoard.getMockBoard1(), 3L}; -// Object[] mockObjectsLike3_2 = {Stub.MockBoard.getMockBoard2(), 3L}; -// Object[] mockObjectsLike3_3 = {Stub.MockBoard.getMockBoard3(), 3L}; -// Object[] mockObjectsLike3_4 = {Stub.MockBoard.getMockBoard4(), 3L}; -// Object[] mockObjectsLike3_5 = {Stub.MockBoard.getMockBoard5(), 3L}; -// Object[] mockObjectsLike2_1 = {Stub.MockBoard.getMockBoard2(), 2L}; -// Object[] mockObjectsLike2_2 = {Stub.MockBoard.getMockBoard4(), 2L}; -// Object[] mockObjectsLike2_3 = {Stub.MockBoard.getMockBoard3(), 2L}; -// Object[] mockObjectsLike2_4 = {Stub.MockBoard.getMockBoard5(), 2L}; -// Object[] mockObjectsLike2_5 = {Stub.MockBoard.getMockBoard1(), 2L}; -// Object[] mockObjectsLike1_1 = {Stub.MockBoard.getMockBoard3(), 1L}; -// Object[] mockObjectsLike1_2 = {Stub.MockBoard.getMockBoard5(), 1L}; -// @BeforeEach -// public void setUp() { -// mockTopBoardsWithLikes.add(mockObjectsLike3_1); -// } -// -//// @AfterEach -//// public void tearDown() { -//// mockTopBoardsWithLikes.clear(); -//// } -// -// @Test -// @Order(1) -// void 동점자_없는_상위_3개_게시글() { -// //given -// // 좋아요 3, 2, 1 -// mockTopBoardsWithLikes.add(mockObjectsLike2_1); -// mockTopBoardsWithLikes.add(mockObjectsLike1_1); -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(mockTopBoardsWithLikes); -// -// //when -// List responses = boardService.findTop3LikedBoardRanks(); -// -// //then -// assertThat(responses.get(0).getLikeNum(), is(3L)); -// assertThat(responses.get(2).getLikeNum(), is(1L)); -// assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(3)); -// assertThat(responses.size(), is(3)); -// } -// -// @Test -// @Order(2) -// void 일등1_이등2_삼등1() { -// //given -// // 좋아요 3, 2, 2, 1 -// mockTopBoardsWithLikes.add(mockObjectsLike2_1); -// mockTopBoardsWithLikes.add(mockObjectsLike2_2); -// mockTopBoardsWithLikes.add(mockObjectsLike1_2); -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(mockTopBoardsWithLikes); -// -// //when -// List responses = boardService.findTop3LikedBoardRanks(); -// -// //then -// assertThat(responses.get(0).getLikeNum(), is(3L)); -// assertThat(responses.get(2).getLikeNum(), is(2L)); -// assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(2)); -// assertThat(responses.size(), is(3)); -// } -// -// @Test -// @Order(3) -// void 일등3_이등1() { -// //given -// // 좋아요 3, 3, 3, 1 -// mockTopBoardsWithLikes.add(mockObjectsLike3_2); -// mockTopBoardsWithLikes.add(mockObjectsLike3_3); -// mockTopBoardsWithLikes.add(mockObjectsLike1_1); -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(mockTopBoardsWithLikes); -// -// //when -// List responses = boardService.findTop3LikedBoardRanks(); -// -// //then -// assertThat(responses.get(0).getLikeNum(), is(3L)); -// assertThat(responses.get(2).getLikeNum(), is(3L)); -// assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); -// assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(1)); -// assertThat(responses.size(), is(3)); -// } -// @Test -// @Order(4) -// void 일등5_이등2() { -// //given -// // 좋아요 3, 3, 3, 3, 3, 1, 1 -// mockTopBoardsWithLikes.add(mockObjectsLike3_2); -// mockTopBoardsWithLikes.add(mockObjectsLike3_3); -// mockTopBoardsWithLikes.add(mockObjectsLike3_4); -// mockTopBoardsWithLikes.add(mockObjectsLike3_5); -// mockTopBoardsWithLikes.add(mockObjectsLike1_1); -// mockTopBoardsWithLikes.add(mockObjectsLike1_2); -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(mockTopBoardsWithLikes); -// -// //when -// List responses = boardService.findTop3LikedBoardRanks(); -// -// //then -// assertThat(responses.get(0).getLikeNum(), is(3L)); -// assertThat(responses.get(responses.size()-1).getLikeNum(), is(3L)); -// assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); -// assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(1)); -// assertThat(responses.size(), is(5)); -// } -// @Test -// @Order(5) -// void 일등1_이등5_삼등1() { -// //given -// // 좋아요 3, 2, 2, 2, 2, 2, 1 -// mockTopBoardsWithLikes.add(mockObjectsLike2_1); -// mockTopBoardsWithLikes.add(mockObjectsLike2_2); -// mockTopBoardsWithLikes.add(mockObjectsLike2_3); -// mockTopBoardsWithLikes.add(mockObjectsLike2_4); -// mockTopBoardsWithLikes.add(mockObjectsLike2_5); -// mockTopBoardsWithLikes.add(mockObjectsLike1_1); -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(mockTopBoardsWithLikes); -// -// //when -// List responses = boardService.findTop3LikedBoardRanks(); -// -// //then -// assertThat(responses.get(0).getLikeNum(), is(3L)); -// assertThat(responses.get(responses.size()-1).getLikeNum(), is(2L)); -// assertThat(responses.get(0).getRankOrders().getPosition(), is(1)); -// assertThat(responses.get(responses.size()-1).getRankOrders().getPosition(), is(2)); -// assertThat(responses.size(), is(6)); -// } -// -// -// -// -// } -//} diff --git a/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java b/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java deleted file mode 100644 index d07ca0ec..00000000 --- a/server/src/test/java/com/growstory/domain/board/service/BoardServiceTest.java +++ /dev/null @@ -1,62 +0,0 @@ -//package com.growstory.domain.board.service; -// -//import com.growstory.domain.account.entity.Account; -//import com.growstory.domain.board.entity.Board; -//import com.growstory.domain.board.repository.BoardRepository; -//import com.growstory.domain.rank.board_likes.dto.BoardLikesRankDto; -//import com.growstory.domain.stubdata.Stub; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.Mockito; -//import org.mockito.junit.jupiter.MockitoExtension; -//import org.springframework.test.context.TestPropertySource; -// -//import java.time.LocalDateTime; -//import java.util.ArrayList; -//import java.util.List; -// -//import static org.junit.jupiter.api.Assertions.assertEquals; -//import static org.mockito.BDDMockito.given; -// -//@ExtendWith(MockitoExtension.class) -//@TestPropertySource(properties = { -// "my.scheduled.cron=0 0 0 * * ?" // 예제 크론 표현식 -//}) -//public class BoardServiceTest { -// -// @InjectMocks -// private BoardService boardService; -// -// @Mock -// private BoardRepository boardRepository; -// -// @Test -// void testFindTop3LikedBoards() { -// // given, 가짜 데이터 생성 -// List fakeTopBoardsWithLikes = new ArrayList<>(); -// fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(1L).title("제목1").account(Account.builder().displayName("빵빵스").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes1()).build(), 3L}); -// fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(2L).title("제목2").account(Account.builder().displayName("김크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes2()).build(), 2L}); -// fakeTopBoardsWithLikes.add(new Object[] { Board.builder().boardId(3L).title("제목3").account(Account.builder().displayName("박크크").build()).boardLikes(Stub.MockBoardLikes.getBoardLikes3()).build(), 1L}); -// -// // 가짜 데이터를 반환하도록 Mock 설정 -// given(boardRepository.findTop3LikedBoards(Mockito.any(LocalDateTime.class))) -// .willReturn(fakeTopBoardsWithLikes); -// -// //when, 테스트 대상 메서드 호출 -// List response = boardService.findTop3LikedBoards(); -// -// //then, 결과 검증 -// assertEquals(3, response.size()); -// -// assertEquals(1L, response.get(0).getBoardId()); -// assertEquals(3, response.get(0).getLikeNum()); -// -// assertEquals(2L, response.get(1).getBoardId()); -// assertEquals(2, response.get(1).getLikeNum()); -// -// assertEquals(3L, response.get(2).getBoardId()); -// assertEquals(1, response.get(2).getLikeNum()); -// } -//} diff --git a/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java b/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java deleted file mode 100644 index 07618b5e..00000000 --- a/server/src/test/java/com/growstory/domain/comment/controller/CommentControllerTest.java +++ /dev/null @@ -1,127 +0,0 @@ -//package com.growstory.domain.comment.controller; -// -//import com.google.gson.Gson; -//import com.growstory.domain.comment.dto.CommentDto; -//import com.growstory.domain.comment.service.CommentService; -//import org.junit.jupiter.api.Test; -//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.context.SpringBootTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.http.MediaType; -//import org.springframework.security.test.context.support.WithMockUser; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.ResultActions; -//import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -//import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -// -//import static org.hamcrest.Matchers.is; -//import static org.hamcrest.Matchers.startsWith; -//import static org.mockito.BDDMockito.given; -//import static org.mockito.Mockito.doNothing; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// -// -//@SpringBootTest -//@AutoConfigureMockMvc -//class CommentControllerTest { -// -// @Autowired -// private MockMvc mockMvc; -// -// -// @Autowired -// private Gson gson; -// -// @MockBean -// private CommentService commentService; -// -// -// @Test -// void postComment() throws Exception { -// -// // given -// String content = "Comment Test"; -// Long boardId = 1L; -// Long commentId = 1L; -// -// CommentDto.Post commentDto = new CommentDto.Post(content); -// -// -// // when -// given(commentService.saveComment(Mockito.eq(boardId), Mockito.any(CommentDto.Post.class))) -// .willReturn(commentId); -// -// String content1 = gson.toJson(boardId); -// String content2 = gson.toJson(commentDto); -// -// // then -// ResultActions actions = -// mockMvc.perform( -// post("/v1/comments/boards/{boardId}", boardId) -// .accept(MediaType.APPLICATION_JSON) // 요청에서 받을 응답 데이터 타입을 JSON으로 설정합니다. 이것은 클라이언트가 JSON 형식의 응답을 기대한다고 나타냅니다. -// .contentType(MediaType.APPLICATION_JSON) // 요청 데이터의 타입을 JSON으로 설정합니다. 이것은 요청의 본문(content)이 JSON 형식임을 나타냅니다. -// .content(content1) -// .content(content2) -// ); -// -// actions -// .andExpect(status().isCreated()) -// .andExpect(header().string("Location", is(startsWith("/v1/comments/")))) -// .andDo(print()); -// } -// -// @Test -// @WithMockUser(username = "testuser", roles = "USER") // 사용자 권한으로 요청 보내기 -// void patchComment() throws Exception{ -// // given -// Long commentId = 1L; -// String content = "TestContent"; -// CommentDto.Patch commentDto = new CommentDto.Patch(content); -// -// -// // when -// doNothing().when(commentService).editComment(commentId, commentDto); -// -// String content1 = gson.toJson(commentId); -// String content2 = gson.toJson(commentDto); -// -// -// // then -// ResultActions actions = -// mockMvc.perform( -// patch("/v1/comments/{commentId}", commentId) -// .accept(MediaType.APPLICATION_JSON) // 요청에서 받을 응답 데이터 타입을 JSON으로 설정합니다. 이것은 클라이언트가 JSON 형식의 응답을 기대한다고 나타냅니다. -// .contentType(MediaType.APPLICATION_JSON) // 요청 데이터의 타입을 JSON으로 설정합니다. 이것은 요청의 본문(content)이 JSON 형식임을 나타냅니다 -// .content(content1) -// .content(content2) -// ); -// -// actions -// .andExpect(MockMvcResultMatchers.status().isNoContent()) -// .andDo(print()); -// } -// -// @Test -// @WithMockUser(username = "testuser", roles = "USER") // 사용자 권한으로 요청 보내기 -// void deleteComment() throws Exception { -// // given -// Long commentId = 1L; -// -// // when -// doNothing().when(commentService).deleteComment(commentId); -// -// // then -// ResultActions actions = mockMvc.perform( -// MockMvcRequestBuilders.delete("/v1/comments/{commentId}", commentId) -// ); -// actions -// .andExpect(MockMvcResultMatchers.status().isNoContent()) -// .andDo(print()); -// } -//} \ No newline at end of file diff --git a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java b/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java deleted file mode 100644 index 2d277818..00000000 --- a/server/src/test/java/com/growstory/domain/journal/JournalControllerTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.growstory.domain.journal; - -import com.google.gson.Gson; -import com.growstory.domain.journal.dto.JournalDto; -import com.growstory.domain.journal.service.JournalService; -import com.growstory.domain.stubdata.Stub; -import com.growstory.global.utils.UriCreator; -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.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -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.test.web.servlet.ResultActions; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -import static org.hamcrest.Matchers.*; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -@SpringBootTest -@AutoConfigureMockMvc -public class JournalControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private Gson gson; - - @MockBean - private JournalService journalService; - - @MockBean - private UriCreator uriCreator; - - private final String DEFAULT_URL = "/v1/leaves"; - - @DisplayName("식물 일지 전체 조회") - @Test - void getJournalsTest() throws Exception { - //given - Long accountId = 1L; - Long leafId = 1L; - List journals = Stub.MockJournal.getStubJournalResponseDtos(); - given(journalService.findAllJournals(anyLong(), anyLong())) - .willReturn(journals); - //when - ResultActions actions = mockMvc.perform( - get(DEFAULT_URL + "/{leaf-id}/journals", leafId) - .param("accountId", accountId.toString()) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - ); - - //then - actions.andExpect(status().isOk()) - .andExpect(jsonPath("$.data[0].journalId", is(1))) - .andExpect(jsonPath("$.data[0].title", is(journals.get(0).getTitle()))) - .andDo(print()); - } - - @DisplayName("식물 일지 등록 기능") - @Nested - class PostJournalTest { - - long leafId; -// JournalDto.LeafAuthor leafAuthor; - JournalDto.Post requestDto; - MockMultipartFile leafAuthorPart; - MockMultipartFile requestDtoPart; - MockMultipartFile imagePart; - JournalDto.Response mockJournalResponse; - - @BeforeEach - void init() { - leafId = 1L; -// leafAuthor = JournalDto.LeafAuthor.builder().accountId(1L).build(); - requestDto = JournalDto.Post.builder().title("식물 일지 제목").content("내용").build(); - - // MockMultipartFile 객체화 -// leafAuthorPart = createMockMultipartFile("leafAuthor", gson.toJson(leafAuthor), "application/json"); - requestDtoPart = createMockMultipartFile("postDto", gson.toJson(requestDto), "application/json"); - imagePart = createImageMockFile("src/test/resources/images/testImage.jpg"); - - //given - mockJournalResponse = Stub.MockJournal.getStubJournalResponse1(); - given(journalService.createJournal(anyLong(), Mockito.any(JournalDto.Post.class), Mockito.any(MultipartFile.class))) - .willReturn(mockJournalResponse); - - URI stubUri = URI.create("/v1/leaves/1/journals/1"); - given(uriCreator.createUri_test(Mockito.any(String.class), Mockito.anyLong())).willReturn(stubUri); - } - - private MockMultipartFile createMockMultipartFile(String name, String content, String contentType) { - return new MockMultipartFile(name, name + ".json", contentType, content.getBytes(StandardCharsets.UTF_8)); - } - - private MockMultipartFile createImageMockFile(String pathString) { - Path path = Paths.get(pathString); - byte[] content; - try { - content = Files.readAllBytes(path); - } catch (IOException e) { - throw new RuntimeException(e); - } - return new MockMultipartFile("image", "testImage.jpg", "image/jpeg", content); - } - - @Test - void 이미지_있는_식물_일지_등록() throws Exception { - - //when - ResultActions actions = mockMvc.perform( - multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) -// .file(leafAuthorPart) - .file(requestDtoPart) - .file(imagePart) - .contentType("multipart/form-data") - .accept(MediaType.APPLICATION_JSON) - .characterEncoding("UTF-8") - ); - - //then - URI expectedLocation = UriCreator.createUri(DEFAULT_URL + "/1/journals/", mockJournalResponse.getJournalId()); - actions - .andExpect(status().isCreated()) - .andExpect(header().string("Location", is(expectedLocation.toString()))) - .andDo(print()); - } - -// @Test -// void 이미지가_없는_식물_일지_등록() throws Exception { -// // given, imagePart를 null로 설정 -// MockMultipartFile imagePart = null; -// -// ResultActions actions = mockMvc.perform( -// multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) -// .file(leafAuthorPart) -// .file(requestDtoPart) -// // image part를 추가하지 않음 -// .file(imagePart) -// .contentType("multipart/form-data") -// .accept(MediaType.APPLICATION_JSON) -// .characterEncoding("UTF-8") -// ); -// -// // then -// actions -// .andExpect(status().isCreated()) // or isBadRequest(), depending on the expected outcome -// .andDo(print()); -// } - - @Test - void 이미지가_빈_식물_일지_등록() throws Exception { - // given, imagePart를 빈 내용으로 설정 - MockMultipartFile imagePart = new MockMultipartFile("image", "testImage.jpg", "image/jpeg", new byte[0]); - - // when - ResultActions actions = mockMvc.perform( - multipart(DEFAULT_URL + "/{leaf-id}/journals", leafId) -// .file(leafAuthorPart) - .file(requestDtoPart) - .file(imagePart) // empty image part - .contentType("multipart/form-data") - .accept(MediaType.APPLICATION_JSON) - .characterEncoding("UTF-8") - ); - - // then - actions - .andExpect(status().isCreated()) // or isBadRequest(), depending on the expected outcome - .andDo(print()); - } - } - - -} diff --git a/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java b/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java deleted file mode 100644 index 29f85263..00000000 --- a/server/src/test/java/com/growstory/domain/leaf/controller/LeafControllerTest.java +++ /dev/null @@ -1,191 +0,0 @@ -//package com.growstory.domain.leaf.controller; -// -//import com.google.gson.Gson; -//import com.growstory.domain.account.dto.AccountDto; -//import com.growstory.domain.leaf.dto.LeafDto; -//import com.growstory.domain.leaf.service.LeafService; -//import com.growstory.domain.point.entity.Point; -//import org.junit.jupiter.api.Test; -//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.context.SpringBootTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.http.HttpMethod; -//import org.springframework.http.MediaType; -//import org.springframework.mock.web.MockMultipartFile; -//import org.springframework.mock.web.MockPart; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.ResultActions; -//import org.springframework.web.multipart.MultipartFile; -// -//import javax.servlet.http.Part; -//import java.io.FileInputStream; -//import java.util.ArrayList; -//import java.util.List; -// -//import static org.hamcrest.Matchers.is; -//import static org.mockito.BDDMockito.given; -//import static org.mockito.BDDMockito.willDoNothing; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -// -//@SpringBootTest -//@AutoConfigureMockMvc -//public class LeafControllerTest { -// @Autowired -// private MockMvc mockMvc; -// -// @Autowired -// private Gson gson; -// -// @MockBean -// private LeafService leafService; -// -// @Test -// void 식물카드_생성() throws Exception { -// // given -// Long leafId = 1L; -// -// LeafDto.Post requestDto = LeafDto.Post.builder() -// .leafName("식물1") -// .content("본문1") -// .build(); -// -// LeafDto.Response responseDto = getResponseDto("김별명", leafId, requestDto.getLeafName(), requestDto.getContent(), "s3/path"); -// -// MockMultipartFile testImage = new MockMultipartFile("leafImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// -// given(leafService.createLeaf(Mockito.any(LeafDto.Post.class), Mockito.any(MultipartFile.class))) -// .willReturn(responseDto); -// -// // when -// ResultActions actions = mockMvc.perform( -// multipart(HttpMethod.POST, "/v1/leaves") -// .file(testImage) -// .file(new MockMultipartFile("leafPostDto", "", "application/json", gson.toJson(requestDto).getBytes()))); -// -// // then -// actions -// .andExpect(status().isCreated()) -// .andExpect(header().string("Location", is("/v1/leaves/" + responseDto.getLeafId().toString()))) -// .andDo(print()); -// } -// -// @Test -// void 식물카드_수정() throws Exception { -// // given -// LeafDto.Patch requestDto = LeafDto.Patch.builder() -// .leafId(1L) -// .leafName("식물1") -// .content("본문1") -// .build(); -// -// MockMultipartFile testImage = new MockMultipartFile("leafImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// willDoNothing().given(leafService).updateLeaf(Mockito.any(LeafDto.Patch.class), Mockito.any(MultipartFile.class)); -// -// // when -// ResultActions actions = mockMvc.perform( -// multipart(HttpMethod.PATCH, "/v1/leaves") -// .file(testImage) -// .file(new MockMultipartFile("leafPatchDto", "", "application/json", gson.toJson(requestDto).getBytes()))); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// @Test -// void 나의_식물카드_조회() throws Exception { -// // given -// Long accountId = 1L; -// -// List responseDtos = getResponseDtos(); -// -// given(leafService.findLeaves(Mockito.anyLong())) -// .willReturn(responseDtos); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/leaves/account/" + accountId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data[0].leafName", is("식물1"))) -// .andExpect(jsonPath("$.data[1].leafName", is("식물2"))) -// .andDo(print()); -// } -// -// @Test -// void 식물카드_단일_조회() throws Exception { -// // given -// Long leafId = 1L; -// -// LeafDto.Response responseDto = getResponseDto("김별명", 1L, "식물1", "본문1", "s3/path1"); -// -// given(leafService.findLeaf(Mockito.anyLong())) -// .willReturn(responseDto); -// -// // when -// ResultActions actions = mockMvc.perform( -// get("/v1/leaves/" + leafId)); -// -// // then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.leafName", is("식물1"))) -// .andDo(print()); -// } -// -// @Test -// void 식물카드_삭제() throws Exception { -// // given -// Long leafId = 1L; -// -// willDoNothing().given(leafService).deleteLeaf(Mockito.anyLong()); -// -// // when -// ResultActions actions = mockMvc.perform( -// delete("/v1/leaves/" + leafId)); -// -// // then -// actions -// .andExpect(status().isNoContent()) -// .andDo(print()); -// } -// -// private static LeafDto.Response getResponseDto(String displayName, Long leafId, String leafName, String content, String leafImageUrl) { -// return LeafDto.Response.builder() -// .displayName(displayName) -// .leafId(leafId) -// .leafName(leafName) -// .content(content) -// .leafImageUrl(leafImageUrl) -// .build(); -// } -// -// private static List getResponseDtos() { -// List responseDtos = new ArrayList<>(); -// -// LeafDto.Response responseDto1 = getResponseDto("김별명", 1L, "식물1", "본문1", "s3/path1"); -// LeafDto.Response responseDto2 = getResponseDto("김별명", 2L, "식물2", "본문2", "s3/path2"); -// -// responseDtos.add(responseDto1); -// responseDtos.add(responseDto2); -// -// return responseDtos; -// } -//} diff --git a/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java b/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java deleted file mode 100644 index c730cd88..00000000 --- a/server/src/test/java/com/growstory/domain/leaf/service/LeafServiceTest.java +++ /dev/null @@ -1,408 +0,0 @@ -//package com.growstory.domain.leaf.service; -// -//import com.growstory.domain.account.entity.Account; -//import com.growstory.domain.account.service.AccountService; -//import com.growstory.domain.images.entity.JournalImage; -//import com.growstory.domain.images.service.JournalImageService; -//import com.growstory.domain.journal.entity.Journal; -//import com.growstory.domain.leaf.dto.LeafDto; -//import com.growstory.domain.leaf.entity.Leaf; -//import com.growstory.domain.leaf.repository.LeafRepository; -//import com.growstory.domain.plant_object.entity.PlantObj; -//import com.growstory.domain.point.entity.Point; -//import com.growstory.global.auth.utils.AuthUserUtils; -//import com.growstory.global.aws.service.S3Uploader; -//import com.growstory.global.exception.BusinessLogicException; -//import com.growstory.global.exception.ExceptionCode; -//import org.junit.jupiter.api.*; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.Mockito; -//import org.mockito.junit.jupiter.MockitoExtension; -//import org.springframework.mock.web.MockMultipartFile; -//import org.springframework.security.authentication.BadCredentialsException; -//import org.springframework.web.multipart.MultipartFile; -// -//import java.io.FileInputStream; -//import java.io.FileNotFoundException; -//import java.io.IOException; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.Optional; -// -//import static org.hamcrest.MatcherAssert.assertThat; -//import static org.hamcrest.Matchers.contains; -//import static org.hamcrest.Matchers.is; -//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -//import static org.junit.jupiter.api.Assertions.assertThrows; -//import static org.mockito.BDDMockito.*; -// -//@ExtendWith(MockitoExtension.class) -//public class LeafServiceTest { -// @InjectMocks -// private LeafService leafService; -// @Mock -// private LeafRepository leafRepository; -// @Mock -// private AccountService accountService; -// @Mock -// private S3Uploader s3Uploader; -// @Mock -// private AuthUserUtils authUserUtils; -// @Mock -// private JournalImageService journalImageService; -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 식물카드_생성 { -// // given -// String s3ImageUrl = "s3/path"; -// -// LeafDto.Post leafPostDto = LeafDto.Post.builder() -// .leafName("식물1") -// .content("본문1") -// .build(); -// -// MockMultipartFile testImage = new MockMultipartFile("profileImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf = getLeaf(1L, leafPostDto.getLeafName(), leafPostDto.getContent(), s3ImageUrl); -// -// private List getLeaves(int size) { -// List leaves = new ArrayList<>(); -// for (int i = 0; i < size; i++) leaves.add(leaf); -// -// return leaves; -// } -// private void uploadImage() { -// given(s3Uploader.uploadImageToS3(Mockito.any(MockMultipartFile.class), Mockito.anyString())) -// .willReturn(s3ImageUrl); -// given(leafRepository.save(Mockito.any(Leaf.class))) -// .willReturn(leaf.toBuilder().leafImageUrl(s3ImageUrl).build()); -// } -// -// 식물카드_생성() throws IOException { -// } -// -// @Test -// @Order(1) -// public void 사용자의_식물카드가_50개_미만이면() { -// int leafSize = 1; -// List leaves = getLeaves(leafSize); -// -// // when -// Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); -// -// // then -// assertThat(grade.getStepDescription(), is("브론즈 가드너")); -// } -// -// @Test -// @Order(2) -// public void 사용자의_식물카드가_50개_이상_100개_미만이면() { -// int leafSize = 50; -// List leaves = getLeaves(leafSize); -// -// // when -// Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); -// -// // then -// assertThat(grade.getStepDescription(), is("실버 가드너")); -// } -// -// @Test -// @Order(3) -// public void 사용자의_식물카드가_100개_이상이면() { -// int leafSize = 100; -// List leaves = getLeaves(leafSize); -// -// // when -// Account.AccountGrade grade = leafService.updateAccountGrade(account.toBuilder().leaves(leaves).build()); -// -// // then -// assertThat(grade.getStepDescription(), is("골드 가드너")); -// } -// -// @Test -// @Order(4) -// public void 생성_성공() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account.toBuilder().leaves(new ArrayList<>()).build()); -// -// uploadImage(); -// -// // when -// LeafDto.Response responseDto = leafService.createLeaf(leafPostDto, testImage); -// -// // then -// assertThat(responseDto.getLeafId(), is(leaf.getLeafId())); -// } -// } -// -// @Nested -// @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -// class 식물카드_수정 { -// // given -// String s3ImageUrl = "s3/path"; -// -// LeafDto.Patch leafPatchDto = LeafDto.Patch.builder() -// .leafId(1L) -// .leafName("식물1") -// .content("본문1") -// .build(); -// -// MockMultipartFile testImage = new MockMultipartFile("profileImage", -// "testImage.jpg", -// "jpg", -// new FileInputStream("src/test/resources/images/testImage.jpg")); -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf = getLeaf(leafPatchDto.getLeafId(), leafPatchDto.getLeafName(), leafPatchDto.getContent(), s3ImageUrl); -// -// 식물카드_수정() throws IOException { -// } -// -// @Test -// @Order(1) -// public void 입력받은_식물카드가_존재하지_않으면_실패() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.empty()); -// -// // when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> leafService.updateLeaf(leafPatchDto, testImage)); -// assertThat(exception.getExceptionCode().getStatus(), is(404)); -// assertThat(exception.getExceptionCode().getMessage(), is("Leaf not found")); -// } -// -// @Test -// @Order(2) -// public void 입력받은_사용자와_식물카드의_주인이_다르면_실패() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account.toBuilder().accountId(2L).build()); -// -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); -// -// // when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> leafService.updateLeaf(leafPatchDto, testImage)); -// assertThat(exception.getExceptionCode().getStatus(), is(405)); -// assertThat(exception.getExceptionCode().getMessage(), is("That Account doesn't have authority")); -// } -// -//// @Test -//// @Order(3) -//// public void 입력받은_이미지가_없으면() { -//// given(authUserUtils.getAuthUser()) -//// .willReturn(account); -//// -//// given(leafRepository.findById(Mockito.anyLong())) -//// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); -//// -//// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -//// -//// // when, then -//// assertDoesNotThrow(() -> leafService.updateLeaf(leafPatchDto, null)); -//// verify(s3Uploader, times(0)).uploadImageToS3(Mockito.any(MultipartFile.class), Mockito.anyString()); -//// } -//// -//// @Test -//// @Order(4) -//// public void 입력받은_이미지가_있으면() { -//// given(authUserUtils.getAuthUser()) -//// .willReturn(account); -//// -//// given(leafRepository.findById(Mockito.anyLong())) -//// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); -//// -//// willDoNothing().given(s3Uploader).deleteImageFromS3(Mockito.anyString(), Mockito.anyString()); -//// -//// // when, then -//// assertDoesNotThrow(() -> leafService.updateLeaf(leafPatchDto, testImage)); -//// verify(s3Uploader, times(1)).uploadImageToS3(Mockito.any(MultipartFile.class), Mockito.anyString()); -//// } -// } -// -// @Nested -// class 식물카드_단일_조회 { -// // given -// Long leafId = 1L; -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3/path"); -// -// @Test -// public void 입력받은_식물카드가_존재할_때_성공() { -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(leaf.toBuilder() -// .account(account) -// .build())); -// -// // when -// LeafDto.Response responseDto = leafService.findLeaf(leafId); -// -// // then -// assertThat(responseDto.getLeafId(), is(leafId)); -// assertThat(responseDto.getLeafName(), is(leaf.getLeafName())); -// } -// -// @Test -// public void 입력받은_식물카드가_존재하지_않을_때_실패() { -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.empty()); -// -// // when, then -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> leafService.findLeaf(leafId)); -// assertThat(exception.getExceptionCode().getStatus(), is(404)); -// assertThat(exception.getExceptionCode().getMessage(), is("Leaf not found")); -// } -// } -// -// @Test -// public void 전체_식물카드_조회() { -// // given -// Long accountId = 1L; -// -// Account account = getAccount(accountId, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf1 = getLeaf(1L, "식물1", "본문1", "s3ImageUrl"); -// Leaf leaf2 = getLeaf(2L, "식물2", "본문1", "s3ImageUrl2"); -// -// -// given(accountService.findVerifiedAccount(Mockito.anyLong())) -// .willReturn(account); -// -// given(leafRepository.findByAccount(Mockito.any(Account.class))) -// .willReturn(List.of( -// leaf1.toBuilder().account(account).build(), -// leaf2.toBuilder().account(account).build())); -// -// // when -// List responseDtos = leafService.findLeaves(accountId); -// -// // then -// assertThat(responseDtos.size(), is(2)); -// assertThat(responseDtos.get(0).getLeafId(), is(1L)); -// assertThat(responseDtos.get(1).getLeafId(), is(2L)); -// } -// -// @Test -// public void findLeafEntityByTest() { -// // given -// Long leafId = 1L; -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3ImageUrl"); -// -// given(authUserUtils.getAuthUser()) -// .willReturn(account); -// -// given(leafRepository.findById(leafId)) -// .willReturn(Optional.of(leaf.toBuilder().account(account).build())); -// -// // when -// Leaf findLeaf = leafService.findLeafEntityBy(leafId); -// -// // then -// assertThat(findLeaf.getLeafId(), is(leafId)); -// } -// -// @Nested -// class 식물카드_삭제 { -// // given -// Long leafId = 1L; -// -// Account account = getAccount(1L, "user1@gmail.com", "user1", -// "user1234", "image/path", Point.builder().build(), -// List.of("USER"), Account.AccountGrade.GRADE_BRONZE); -// -// Leaf leaf = getLeaf(leafId, "식물1", "본문1", "s3ImageUrl"); -// -// Journal journal1 = Journal.builder().journalImage(JournalImage.builder().build()).build(); -// Journal journal2 = Journal.builder().build(); -// -// PlantObj plantObj = PlantObj.builder().build(); -// -// @BeforeEach -// private void init() { -// given(authUserUtils.getAuthUser()) -// .willReturn(account.toBuilder().leaves(new ArrayList<>(List.of(leaf))).build()); -// -// willDoNothing().given(journalImageService).deleteJournalImageWithS3(Mockito.any(JournalImage.class), Mockito.anyString()); -// } -// -// @Test -// public void 연결된_plantObj가_없으면(){ -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(leaf.toBuilder() -// .account(account) -// .journals(new ArrayList<>(List.of(journal1, journal2))) -// .build())); -// -// // when, then -// assertDoesNotThrow(() -> leafService.deleteLeaf(leafId)); -// } -// -// @Test -// public void 연결된_plantObj가_있으면(){ -// given(leafRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(leaf.toBuilder() -// .account(account) -// .journals(new ArrayList<>(List.of(journal1, journal2))) -// .plantObj(plantObj) -// .build())); -// // when -// leafService.deleteLeaf(leafId); -// -// // then -// assertThat(Optional.ofNullable(plantObj.getLeaf()), is(Optional.empty())); -// } -// } -// -// private static Account getAccount(Long accountId, String email, String displayName, String password, -// String profileImageUrl, Point point, List roles, Account.AccountGrade accountGrade) { -// return Account.builder() -// .accountId(accountId) -// .email(email) -// .displayName(displayName) -// .password(password) -// .profileImageUrl(profileImageUrl) -// .point(point) -// .roles(roles) -// .accountGrade(accountGrade) -// .build(); -// } -// -// private Leaf getLeaf(Long leafId, String leafName, String content, String leafImageUrl) { -// return Leaf.builder() -// .leafId(leafId) -// .leafName(leafName) -// .content(content) -// .leafImageUrl(leafImageUrl) -// .build(); -// } -//} diff --git a/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java b/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java deleted file mode 100644 index 47e5aa09..00000000 --- a/server/src/test/java/com/growstory/domain/plant_object/controller/PlantObjectControllerTest.java +++ /dev/null @@ -1,158 +0,0 @@ -//package com.growstory.domain.plant_object.controller; -// -//import com.google.gson.Gson; -//import com.growstory.domain.plant_object.dto.PlantObjDto; -//import com.growstory.domain.plant_object.service.PlantObjService; -//import com.growstory.domain.point.dto.PointDto; -//import com.growstory.domain.stubdata.Stub; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//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.context.SpringBootTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.http.MediaType; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.ResultActions; -// -//import java.util.List; -// -//import static org.hamcrest.Matchers.is; -//import static org.mockito.BDDMockito.anyLong; -//import static org.mockito.BDDMockito.given; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// -//@SpringBootTest -//@AutoConfigureMockMvc -//public class PlantObjectControllerTest { -// -// @Autowired -// private MockMvc mockMvc; -// -// @Autowired -// private Gson gson; -// -// @MockBean -// private PlantObjService plantObjService; -// -// private final String DEFAULT_URL = "/v1/gardens"; -// -// @DisplayName("정원 전체 정보 조회 API 테스트") -// @Test -// void getGardenInfoTest() throws Exception { -// //given -// Long accountId = 1L; -// PlantObjDto.GardenInfoResponse response = Stub.MockPlantObj.getStubGardenInfo(); -// given(plantObjService.findAllGardenInfo(Mockito.anyLong())) -// .willReturn(response); -// //when -// ResultActions actions = mockMvc.perform( -// get(DEFAULT_URL +"/{account-id}" ,accountId)); -// //then -// actions -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.plantObjs[0].plantObjId", is(1))) -// .andExpect(jsonPath("$.data.plantObjs[1].plantObjId", is(2))) -// .andDo(print()); -// } -// -// @DisplayName("유저 포인트로 오브젝트 구입 API 테스트") -// @Test -// void postPurchaseObjTest() throws Exception { -// //given -// Long accountId = 1L; -// Long productId = 1L; -// PlantObjDto.TradeResponse response = Stub.MockPlantObj.getStubTradeResponse(); -// given(plantObjService.buyProduct(Mockito.anyLong(), Mockito.anyLong())) -// .willReturn(response); -// -// //when -// ResultActions actions = mockMvc.perform( -// post(DEFAULT_URL +"/{account-id}/purchase", accountId) -// .param("product-id", productId.toString()) -// .contentType(MediaType.APPLICATION_JSON) -// .accept(MediaType.APPLICATION_JSON) -// ); -// -// //then -// actions.andExpect(status().isCreated()) -// .andExpect(jsonPath("$.data.plantObj.productId", is(productId.intValue()))) -// .andDo(print()); -// } -// -// @DisplayName("오브젝트 되팔기 API 테스트") -// @Test -// void deleteRefundObj() throws Exception { -// //given -// Long accountId = 1L; -// Long plantObjId = 1L; -// PointDto.Response response = PointDto.Response.builder().score(500).build(); -// given(plantObjService.refundPlantObj(anyLong(), anyLong())) -// .willReturn(response); -// -// //when -// ResultActions actions = mockMvc.perform( -// delete(DEFAULT_URL+"/{account-id}/refund", accountId) -// .param("plantobj-id", plantObjId.toString()) -// .contentType(MediaType.APPLICATION_JSON) -// .accept(MediaType.APPLICATION_JSON) -// ); -// -// //then -// actions.andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.score", is(500))); -// } -// -// @DisplayName("오브젝트 배치 (편집 완료) API 테스트") -// @Test -// void patchLocationsTest() throws Exception { -// //given -// Long accountId = 1L; -// List patchLocations -// = Stub.MockLocation.getStubPatchLocationResponses(); -// //gardenInfo, patchLocations의 위치 정보를 포함하고 있는 plantObjs를 목 데이터로 가지고 있음. -// PlantObjDto.GardenInfoResponse gardenInfo -// = Stub.MockPlantObj.getStubGardenInfo(); -// given(plantObjService.findAllGardenInfo(anyLong())).willReturn(gardenInfo); -// //when -// ResultActions actions = mockMvc.perform( -// patch(DEFAULT_URL+"/{account-id}/location", accountId) -// .contentType(MediaType.APPLICATION_JSON) -// .accept(MediaType.APPLICATION_JSON) -// .content(gson.toJson(patchLocations)) -// ); -// //then -// actions.andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.plantObjs[0].location.locationId", -// is(accountId.intValue()))) -// .andDo(print()); -// } -// -// @DisplayName("오브젝트와 식물 카드 연결 / 해제 / 교체") -// @Test -// void patchObjConnectionToLeafTest() throws Exception { -// //given -// Long accountId = 1L; -// Long plantObjId = 1L; -// Long leafId = 1L; -// PlantObjDto.Response response = Stub.MockPlantObj.getStubPlantObjResponseDto1(); -// given(plantObjService.updateLeafConnection(anyLong(), anyLong(), anyLong())) -// .willReturn(response); -// //when -// ResultActions actions = mockMvc.perform( -// patch(DEFAULT_URL+"/{account-id}/connection", accountId) -// .param("plantobj-id", String.valueOf(plantObjId)) -// .param("leaf-id", String.valueOf(leafId)) -// .contentType(MediaType.APPLICATION_JSON) -// .accept(MediaType.APPLICATION_JSON) -// ); -// //then -// actions.andExpect(status().isOk()) -// .andExpect(jsonPath("$.data.leafDto.id", is(leafId.intValue()))) -// .andDo(print()); -// } -//} diff --git a/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java b/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java deleted file mode 100644 index 70ffd2e1..00000000 --- a/server/src/test/java/com/growstory/domain/plant_object/service/PlantObjServiceTest.java +++ /dev/null @@ -1,407 +0,0 @@ -//package com.growstory.domain.plant_object.service; -// -//import com.growstory.domain.account.entity.Account; -//import com.growstory.domain.account.service.AccountService; -//import com.growstory.domain.leaf.dto.LeafDto; -//import com.growstory.domain.leaf.entity.Leaf; -//import com.growstory.domain.leaf.service.LeafService; -//import com.growstory.domain.plant_object.dto.PlantObjDto; -//import com.growstory.domain.plant_object.entity.PlantObj; -//import com.growstory.domain.plant_object.location.dto.LocationDto; -//import com.growstory.domain.plant_object.location.entity.Location; -//import com.growstory.domain.plant_object.location.service.LocationService; -//import com.growstory.domain.plant_object.mapper.PlantObjMapper; -//import com.growstory.domain.plant_object.repository.PlantObjRepository; -//import com.growstory.domain.point.entity.Point; -//import com.growstory.domain.product.dto.ProductDto; -//import com.growstory.domain.product.entity.Product; -//import com.growstory.domain.product.service.ProductService; -//import com.growstory.domain.stubdata.Stub; -//import com.growstory.global.auth.utils.AuthUserUtils; -//import com.growstory.global.customUser.annotation.WithMockCustomUser; -//import com.growstory.global.exception.BusinessLogicException; -//import com.growstory.global.exception.ExceptionCode; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.Mockito; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Optional; -// -//import static org.hamcrest.MatcherAssert.assertThat; -//import static org.hamcrest.Matchers.*; -//import static org.junit.jupiter.api.Assertions.assertThrows; -//import static org.mockito.BDDMockito.*; -// -// -//@ExtendWith(MockitoExtension.class) -//public class PlantObjServiceTest { -// -// @InjectMocks -// private PlantObjService plantObjService; -// @Mock -// private PlantObjRepository plantObjRepository; -// @Mock -// private ProductService productService; -// @Mock -// private AccountService accountService; -// @Mock -// private LocationService locationService; -// @Mock -// private LeafService leafService; -// @Mock -// private PlantObjMapper plantObjMapper; -// @Mock -// private AuthUserUtils authUserUtils; -// -// @BeforeEach -// public void init() { -// System.out.println("=".repeat(10) +"PlantObjServiceTest init"+"=".repeat(10)); -// } -// -// @DisplayName("buyProduct Test : 구매 금액이 충분하지 않을 때") -// @WithMockCustomUser(accountId = 1L, displayName = "관리자", email = "admin@gmail.com", password = "1234", profileImageUrl = "112", roles = "{ADMIN, USER}") -// @Test -// public void testBuyProduct_구매금액_불충분() { -// //given -// Long gardenAccountId = 1L; -// Long productId = 1L; -// // 물건을 구입할 계정은 0 포인트를 가지고 있음 -// Account findAccount = Stub.MockAccount.getStubAccount(); -// Point mockPoint = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); -// findAccount.updatePoint(mockPoint); -// // 가격이 500원인 Product -// Product boughtProduct = Stub.MockProduct.getStubProduct1(); -// PlantObj boughtPlantObj = Stub.MockPlantObj.getStubPlantObj1(); -// // 스텁 데이터 -// given(authUserUtils.getAuthUser()).willReturn(findAccount); -// given(productService.findVerifiedProduct(Mockito.anyLong())) -// .willReturn(boughtProduct); -// // given(plantObjRepository.save(Mockito.any(PlantObj.class))) -// // .willReturn(boughtPlantObj); -// -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.buyProduct(gardenAccountId, productId)); -// int httpStatusCode = exception.getExceptionCode().getStatus(); -// -// //then -// assertThat(exception.getClass(), is(BusinessLogicException.class)); -// assertThat(httpStatusCode, is(403)); -// } -// -// @DisplayName("buyProduct Test : 성공") -//// @WithMockCustomUser(accountId = 3L, displayName = "관리자", email = "admin@gmail.com", password = "1234", profileImageUrl = "112", roles = "{ADMIN, USER}") -// @Test -// public void testBuyProduct_구매금액_충분_성공() { -// //given -// Long gardenAccountId = 1L; -// Long productId = 1L; -// // 물건을 구입할 계정은 500 포인트를 가지고 있음 -// Account findAccount = Stub.MockAccount.getStubAccount(); -// Point mockPoint = Stub.MockPoint.getStubPointResponseDtoWith500Score(); -// findAccount.updatePoint(mockPoint); -// // 가격이 500원인 Product -// Product boughtProduct = Stub.MockProduct.getStubProduct1(); -// PlantObj boughtPlantObj = Stub.MockPlantObj.getStubPlantObj1(); -// -// // Mock 리턴 -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// given(authUserUtils.getAuthUser()).willReturn(findAccount); -// given(productService.findVerifiedProduct(Mockito.anyLong())) -// .willReturn(boughtProduct); -// given(plantObjRepository.save(Mockito.any(PlantObj.class))) -// .willReturn(boughtPlantObj); -// -// //when -// plantObjService.buyProduct(gardenAccountId, productId); -// -// //then -// verify(plantObjRepository).save(Mockito.any(PlantObj.class)); -// assertThat(findAccount.getPlantObjs(), hasItem(boughtPlantObj)); -// assertThat(findAccount.getPoint().getScore(), is(0)); -// } -// -// @DisplayName("refundPlantObj test : 사용자 소유의 환불 가능 품목이 없을 때") -//// @WithMockUser -// @Test -// public void testRefundPlantObj_환불_가능_품목_없음() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// -// Long accountId = 1L; -// Long plantObjId = 1L; -// Account findAccount = Stub.MockAccount.getStubAccount(); -// // 0 Point 매핑 (이미 물건을 구입 했다고 가정) -// Point point = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); -// findAccount.updatePoint(point); -// PlantObj plantObj = Stub.MockPlantObj.getStubPlantObj1(); -// // findAccount.addPlantObj(plantObj)를 실행하지 않는다. -// // 목 데이터 리턴 -// given(authUserUtils.getAuthUser()).willReturn(findAccount); -// given(plantObjRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(plantObj)); -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.refundPlantObj(accountId, plantObjId)); -// //then -// //환불 가능 품목이 존재하지 않을 때 PLANT_OBJ_NOT_FOUND 반환 -// assertThat(exception.getExceptionCode(), is(ExceptionCode.PLANT_OBJ_NOT_FOUND)); -// } -// -// @DisplayName("refundPlantObj test : 사용자 소유의 환불 가능 품목이 존재할 때") -// @Test -// public void testRefundPlantObj_환불_성공() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// -// Long accountId = 1L; -// Account findAccount = Stub.MockAccount.getStubAccount(); -// // 0 Point 매핑 (이미 물건을 구입 했다고 가정) -// Point point = Stub.MockPoint.getStubPointResponseDtoWithNoScore(); -// findAccount.updatePoint(point); -// PlantObj plantObj1 = Stub.MockPlantObj.getStubPlantObj1(); -// PlantObj plantObj2 = Stub.MockPlantObj.getStubPlantObj2(); -// findAccount.addPlantObj(plantObj1); -// findAccount.addPlantObj(plantObj2); -// // 목 데이터 리턴 -// given(authUserUtils.getAuthUser()).willReturn(findAccount); -// given(plantObjRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.of(plantObj2)); -// //when -// plantObjService.refundPlantObj(accountId, plantObj2.getPlantObjId()); -// //then -// assertThat(findAccount.getPoint().getScore(), is(plantObj2.getProduct().getPrice())); -// } -// -// -// @DisplayName("findAllGardenInfo Test : 전체 정원 정보 조회 성공") -// @Test -// public void testFindAllGardenInfo_전체_정원_정보조회_성공() { -// //given -// //정원 소유주 accountId -// Long gardenAccountId = 1L; -// //정원 소유자와 Point 목 데이터 매핑 -// Account findAccount = Stub.MockAccount.getStubAccount(); -// Point userPoint = Stub.MockPoint.getStubPointResponseDtoWith500Score(); -// findAccount.updatePoint(userPoint); -// //소유 PlantObjs 목 데이터 매핑 -// List plantObjList = Stub.MockPlantObj.getStubPlantObjs(); -// plantObjList.stream().forEach(findAccount::addPlantObj); -// List plantObjResponseList = Stub.MockPlantObj.getStubPlantObjsResponseDtos(); -// //상품 리스트 -// List products = Stub.MockProduct.getStubProductResponses(); -// //스텁 데이터 동작 지정 -// given(accountService.findVerifiedAccount(Mockito.anyLong())).willReturn(findAccount); -// given(productService.findAllProducts()).willReturn(products); -// given(plantObjMapper.toPlantObjResponseList(Mockito.anyList())).willReturn(plantObjResponseList); -// -// //when -// PlantObjDto.GardenInfoResponse gardenInfo = plantObjService.findAllGardenInfo(gardenAccountId); -// -// //then -// assertThat(gardenInfo.getPoint().getScore(), is(userPoint.getScore())); -// assertThat(gardenInfo.getDisplayName(), is(findAccount.getDisplayName())); -// assertThat(gardenInfo.getProducts(), is(products)); -// assertThat(gardenInfo.getPlantObjs().get(0).getPlantObjId(), is(plantObjList.get(0).getPlantObjId())); -// } -// -//// @DisplayName("saveLocation Test : 프로덕트 id와 로케이션 id 불일치") -//// @Test -//// public void testSaveLocation_프로덕트id_로케이션id_불일치() { -//// //given -//// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -//// Long accountId = 1L; -//// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); -//// patchLocations.add(PlantObjDto.PatchLocation.builder() -//// .plantObjId(3L) -//// .locationDto(LocationDto.Patch.builder() -//// .locationId(99L).x(0).y(0).isInstalled(false) -//// .build()) -//// .build()); -//// //when -//// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -//// () -> plantObjService.saveLocation(accountId, patchLocations)); -//// //then -//// assertThat(exception.getExceptionCode(), is(ExceptionCode.LOCATION_NOT_ALLOW)); -//// } -// -// @DisplayName("saveLocation Test : X축에 부적절한 위치 삽입1 (+좌표)") -// @Test -// public void testSaveLocation_X축_부적절한_위치_삽입() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); -// patchLocations.add(PlantObjDto.PatchLocation.builder() -// .plantObjId(3L) -// .locationDto(LocationDto.Patch.builder() -// .locationId(3L).x(12).y(0).isInstalled(false) -// .build()) -// .build()); -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.saveLocation(accountId, patchLocations)); -// //then -// assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); -// } -// -// @DisplayName("saveLocation Test : X축에 부적절한 위치 삽입 (-좌표)") -// @Test -// public void testSaveLocation_X축_부적절한_위치_삽입2() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); -// patchLocations.add(PlantObjDto.PatchLocation.builder() -// .plantObjId(3L) -// .locationDto(LocationDto.Patch.builder() -// .locationId(3L).x(-1).y(0).isInstalled(false) -// .build()) -// .build()); -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.saveLocation(accountId, patchLocations)); -// //then -// assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); -// } -// -// @DisplayName("saveLocation Test : Y축에 부적절한 위치 삽입 (+좌표)") -// @Test -// public void testSaveLocation_Y축_부적절한_위치_삽입1() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); -// patchLocations.add(PlantObjDto.PatchLocation.builder() -// .plantObjId(3L) -// .locationDto(LocationDto.Patch.builder() -// .locationId(3L).x(0).y(99).isInstalled(false) -// .build()) -// .build()); -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.saveLocation(accountId, patchLocations)); -// //then -// assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); -// } -// -// @DisplayName("saveLocation Test : Y축에 부적절한 위치 삽입 (-좌표)") -// @Test -// public void testSaveLocation_Y축_부적절한_위치_삽입2() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// List patchLocations = Stub.MockLocation.getStubPatchLocationResponses(); -// patchLocations.add(PlantObjDto.PatchLocation.builder() -// .plantObjId(3L) -// .locationDto(LocationDto.Patch.builder() -// .locationId(3L).x(0).y(-1).isInstalled(false) -// .build()) -// .build()); -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.saveLocation(accountId, patchLocations)); -// //then -// assertThat(exception.getExceptionCode(), is(ExceptionCode.INVALID_LOCATION)); -// } -// -// @DisplayName("saveLocation Test : 성공") -// @Test -// public void testSaveLocation_성공() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// List patchLocations = new ArrayList<>(); -// PlantObjDto.PatchLocation patchLocation = Stub.MockLocation.getStubPatchLocation1(); -// patchLocations.add(patchLocation); -// // PatchLocation 객체에 의해 저장된 Location 엔티티 객체 -// Location savedLocation = Stub.MockLocation.getStubLocation(); -// given(locationService.updateLocation(Mockito.any())).willReturn(savedLocation); -// //when -// plantObjService.saveLocation(accountId,patchLocations); -// //then -// assertThat(savedLocation.getLocationId(), is(patchLocation.getLocationDto().getLocationId())); -// assertThat(savedLocation.getX(), is(patchLocation.getLocationDto().getX())); -// assertThat(savedLocation.getY(), is(patchLocation.getLocationDto().getY())); -// assertThat(savedLocation.isInstalled(), is(patchLocation.getLocationDto().isInstalled())); -// } -// -// @DisplayName("updateLeafConnection Test : leafId가 null일 경우 연결해제") -// @Test -// public void testUpdateLeafConnection_Leaf_연결해제() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// Long plantObjId = 1L; -// Long leafId = null; -// PlantObj findPlantObj = Stub.MockPlantObj.getStubPlantObj1(); -// findPlantObj.updateLeaf(Stub.MockLeaf.getStubLeaf()); -// given(plantObjRepository.findById(Mockito.anyLong())).willReturn(Optional.of(findPlantObj)); -// given(plantObjMapper.toPlantObjResponse(Mockito.any(PlantObj.class))) -// .willReturn(PlantObjDto.Response.builder() -// .plantObjId(findPlantObj.getPlantObjId()) -// .leafDto(null) -// .build()); -// //when -// PlantObjDto.Response response = plantObjService.updateLeafConnection(accountId, plantObjId, leafId); -// -// //then -// verify(leafService, never()).findLeafEntityBy(Mockito.anyLong()); -//// verify(mock(findPlantObj.getClass()), times(1)).updateLeaf(null); -// assertThat(response, is(notNullValue())); -// } -// -// @DisplayName("updateLeafConnection Test : plantObj 미확인 예외발생") -// @Test -// public void testUpdateLeafConnection_Leaf_PlantObj_미확인() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// Long plantObjId = 99L; -// Long leafId = 1L; -// given(plantObjRepository.findById(Mockito.anyLong())) -// .willReturn(Optional.empty()); -// -// //when -// BusinessLogicException exception = assertThrows(BusinessLogicException.class, -// () -> plantObjService.updateLeafConnection(accountId, plantObjId, leafId)); -// -// //then -// assertThat(exception.getExceptionCode(), is(ExceptionCode.PLANT_OBJ_NOT_FOUND)); -// } -// -// @DisplayName("updateLeafConnection Test : leafId가 null이 아닐 경우 연결 성공") -// @Test -// public void testUpdateLeafConnection_Leaf_연결성공() { -// //given -// willDoNothing().given(accountService).isAuthIdMatching(Mockito.anyLong()); -// Long accountId = 1L; -// Long plantObjId = 1L; -// Long leafId = 1L; -// PlantObj findPlantObj = Stub.MockPlantObj.getStubPlantObj1(); -// Leaf findLeaf = Stub.MockLeaf.getStubLeaf(); -// findPlantObj.updateLeaf(findLeaf); -// LeafDto.ResponseForGardenInfo leafResponseDto = Stub.MockLeaf.getStubLeafResponseDto(); -// given(plantObjRepository.findById(Mockito.anyLong())).willReturn(Optional.of(findPlantObj)); -// given(leafService.findLeafEntityBy(leafId)).willReturn(findLeaf); -// given(plantObjMapper.toPlantObjResponse(Mockito.any(PlantObj.class))) -// .willReturn(PlantObjDto.Response.builder() -// .plantObjId(findPlantObj.getPlantObjId()) -// .leafDto(leafResponseDto) -// .build()); -// //when -// PlantObjDto.Response response = plantObjService.updateLeafConnection(accountId, plantObjId, leafId); -// -// //then -// verify(leafService).findLeafEntityBy(Mockito.anyLong()); -// assertThat(response, is(notNullValue())); -// assertThat(response.getLeafDto().getId(), is(leafId)); -// } -//} diff --git a/server/src/test/java/com/growstory/domain/stubdata/Stub.java b/server/src/test/java/com/growstory/domain/stubdata/Stub.java deleted file mode 100644 index deb86635..00000000 --- a/server/src/test/java/com/growstory/domain/stubdata/Stub.java +++ /dev/null @@ -1,499 +0,0 @@ -package com.growstory.domain.stubdata; - -import com.growstory.domain.account.dto.AccountDto; -import com.growstory.domain.account.entity.Account; -import com.growstory.domain.board.entity.Board; -import com.growstory.domain.images.entity.JournalImage; -import com.growstory.domain.journal.dto.JournalDto; -import com.growstory.domain.journal.entity.Journal; -import com.growstory.domain.leaf.dto.LeafDto; -import com.growstory.domain.leaf.entity.Leaf; -import com.growstory.domain.likes.entity.BoardLike; -import com.growstory.domain.plant_object.dto.PlantObjDto; -import com.growstory.domain.plant_object.entity.PlantObj; -import com.growstory.domain.plant_object.location.dto.LocationDto; -import com.growstory.domain.plant_object.location.entity.Location; -import com.growstory.domain.point.entity.Point; -import com.growstory.domain.product.dto.ProductDto; -import com.growstory.domain.product.entity.Product; -import org.springframework.http.HttpMethod; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class Stub { - - public static class MockAccount { - private static Map stubRequestBody; - static { - stubRequestBody = new HashMap<>(); - stubRequestBody.put(HttpMethod.POST, AccountDto.Post.builder() - .displayName("관리자") - .email("admin@gmail.com") - .password("1111") - .build()); - } - - public static Object getRequestBody(HttpMethod method) { - return stubRequestBody.get(method); - } - - public static Account getStubAccount() { - return Account.builder() - .accountId(1L) - .displayName("김닉네임") - .profileImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/profiles/" + - "e617d918-a5e4-479b-9e3d-386e5346c184book-1822474__340.jpg") -// .point(MockPoint.getStubPointResponseDtoWith500Score()) - .plantObjs(new ArrayList<>()) - .build(); - } - - public static AccountDto.Response getSingleResponseBody() { - return AccountDto.Response - .builder() - .accountId(1L) -// .boardLiked(boardLikes) -// .boardWritten(boardWrittens) - .displayName("김닉네임") - .profileImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/profiles/" + - "e617d918-a5e4-479b-9e3d-386e5346c184book-1822474__340.jpg") -// .commentWritten(commentWrittens) - .point(Point.builder().pointId(1L).score(500).build()) - .build(); - } - } - - public static class MockGarden { - public static PlantObjDto.GardenInfoResponse getStubGardenInfo() { - return PlantObjDto.GardenInfoResponse.builder() - .displayName(MockAccount.getSingleResponseBody().getDisplayName()) - .products(MockProduct.getStubProductResponses()) -// .plantObjs(MockPlantObj.getStubPlantObjsResponseDtos()) -// .point(MockPoint.getStubPointResponseDto()) - .build(); - } - } - - public static class MockProduct { - public static Product getStubProduct1() { - return Product.builder() - .productId(1L) - .name("building_brown") - .korName("벽돌 유적") - .price(500) - .imageUrlSmall("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_brown_sm.svg") - .imageUrlLarge("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_brown_lg.svg") - .build(); - } - - public static Product getStubProduct2() { - return Product.builder() - .productId(2L) - .name("building_yellow") - .korName("콜로세움") - .price(500) - .imageUrlSmall("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_yellow_sm.svg") - .imageUrlLarge("https://growstory.s3.ap-northeast-2.amazonaws.com/image/products/building_yellow_lg.svg") - .build(); - } - - public static ProductDto.ImageUrlTable getStubImageUrlTable1() { - return new ProductDto.ImageUrlTable( - getStubProduct1().getImageUrlSmall(), - getStubProduct1().getImageUrlLarge()); - } - - public static ProductDto.ImageUrlTable getStubImageUrlTable2() { - return new ProductDto.ImageUrlTable( - getStubProduct2().getImageUrlSmall(), - getStubProduct2().getImageUrlLarge()); - } - - public static List getStubProductResponses() { - List productResponseDtos = new ArrayList<>(); - productResponseDtos.add( - ProductDto.Response.builder() - .productId(getStubProduct1().getProductId()) - .imageUrlTable(getStubImageUrlTable1()) - .price(getStubProduct1().getPrice()) - .korName(getStubProduct1().getKorName()) - .name(getStubProduct1().getName()) - .build()); - productResponseDtos.add( - ProductDto.Response.builder() - .productId(getStubProduct2().getProductId()) - .imageUrlTable(getStubImageUrlTable2()) - .price(getStubProduct2().getPrice()) - .korName(getStubProduct2().getKorName()) - .name(getStubProduct2().getName()) - .build()); - return productResponseDtos; - } - } - - public static class MockPlantObj { - - public static PlantObj getStubPlantObj1() { - return PlantObj.builder() - .plantObjId(1L) - .product(MockProduct.getStubProduct1()) -// .account(MockAccount.getStubAccount()) -// .leaf(MockLeaf.getStubLeaf()) - .build(); - } - - public static PlantObj getStubPlantObj2() { - return PlantObj.builder() - .plantObjId(2L) - .product(MockProduct.getStubProduct2()) - .build(); - } - - public static List getStubPlantObjs() { - ArrayList plantObjs = new ArrayList<>(); - plantObjs.add(getStubPlantObj1()); - plantObjs.add(getStubPlantObj2()); - return plantObjs; - } - - public static PlantObjDto.Response getStubPlantObjResponseDto1() { - PlantObjDto.PatchLocation location1 = MockLocation.getStubPatchLocation1(); - LeafDto.ResponseForGardenInfo leaf1 = MockLeaf.getStubLeafResponseDto(); - return PlantObjDto.Response.builder() - .productId(MockAccount.getStubAccount().getAccountId()) - .plantObjId(MockProduct.getStubProduct1().getProductId()) - .productName(MockProduct.getStubProduct1().getName()) - .korName(MockProduct.getStubProduct1().getKorName()) - .price(MockProduct.getStubProduct1().getPrice()) - .location(LocationDto.Response.builder() - .locationId(location1.getLocationDto().getLocationId()) // 1 - .x(location1.getLocationDto().getX()) //5 - .y(location1.getLocationDto().getY()) //6 - .isInstalled(location1.getLocationDto().isInstalled()) //true - .build()) - .leafDto(LeafDto.ResponseForGardenInfo.builder() - .id(leaf1.getId()) - .build()) - .imageUrlTable(MockProduct.getStubImageUrlTable1()) - .build(); - } - public static PlantObjDto.Response getStubPlantObjResponseDto2() { - PlantObjDto.PatchLocation location2 = MockLocation.getStubPatchLocation2(); - return PlantObjDto.Response.builder() - .productId(MockAccount.getStubAccount().getAccountId()) - .plantObjId(MockProduct.getStubProduct2().getProductId()) - .productName(MockProduct.getStubProduct2().getName()) - .korName(MockProduct.getStubProduct2().getKorName()) - .price(MockProduct.getStubProduct2().getPrice()) - .location(LocationDto.Response.builder() - .locationId(location2.getLocationDto().getLocationId()) // 2 - .x(location2.getLocationDto().getX()) //0 - .y(location2.getLocationDto().getY()) //0 - .isInstalled(location2.getLocationDto().isInstalled()) //false - .build()) -// .leafDto(MockLeaf.getStubLeafResponseDto()) - .imageUrlTable(MockProduct.getStubImageUrlTable2()) - .build(); - } - - public static List getStubPlantObjsResponseDtos() { - - return List.of(getStubPlantObjResponseDto1(), - getStubPlantObjResponseDto2()); - } - - public static PlantObjDto.GardenInfoResponse getStubGardenInfo() { - return PlantObjDto.GardenInfoResponse.builder() - .plantObjs(getStubPlantObjsResponseDtos()) - .build(); - } - - public static PlantObjDto.TradeResponse getStubTradeResponse() { - return PlantObjDto.TradeResponse.builder() - .plantObj(getStubPlantObjResponseDto1()) - .build(); - } - } - - public static class MockLocation { - public static LocationDto.Response getStubLocationResponseDto1() { - return LocationDto.Response.builder() - .locationId(1L) - .x(0) - .y(0) - .isInstalled(false) - .build(); - } - public static LocationDto.Response getStubLocationResponseDto2() { - return LocationDto.Response.builder() - .locationId(2L) - .x(4) - .y(8) - .isInstalled(true) - .build(); - } - public static PlantObjDto.PatchLocation getStubPatchLocation1() { - return PlantObjDto.PatchLocation.builder() - .plantObjId(1L) - .locationDto(LocationDto.Patch.builder() - .locationId(1L).x(5).y(6).isInstalled(true).build()) - .build(); - } - public static PlantObjDto.PatchLocation getStubPatchLocation2() { - return PlantObjDto.PatchLocation.builder() - .plantObjId(2L) - .locationDto(LocationDto.Patch.builder() - .locationId(2L).x(0).y(0).isInstalled(false).build()) - .build(); - } - public static List getStubPatchLocationResponses() { - List patchLocations = new ArrayList<>(); - patchLocations.add(getStubPatchLocation1()); - patchLocations.add(getStubPatchLocation2()); - - return patchLocations; - } - - public static Location getStubLocation() { - PlantObjDto.PatchLocation patchLocation = getStubPatchLocation1(); - - return Location.builder() - .locationId(patchLocation.getLocationDto().getLocationId()) - .x(patchLocation.getLocationDto().getX()) - .y(patchLocation.getLocationDto().getY()) - .isInstalled(patchLocation.getLocationDto().isInstalled()) - .build(); - } - } - - public static class MockLeaf { - public static Leaf getStubLeaf() { - return Leaf.builder() - .leafId(1L) - .leafName("월동자 선인장") - .leafImageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/leaves/4b8b4998-bf12-4c1c-a6ec-dfcaa1dcd339book-1822474__340.jpg") - .content("나의 월동자 선인장은 귀엽다.") -// .account(MockAccount.getStubAccount()) - .journals(null) -// .plantObj(MockPlantObj.getStubPlantObj()) - .build(); - } - public static LeafDto.ResponseForGardenInfo getStubLeafResponseDto() { - return LeafDto.ResponseForGardenInfo.builder() - .id(getStubLeaf().getLeafId()) - .name(getStubLeaf().getLeafName()) - .imageUrl(getStubLeaf().getLeafImageUrl()) - .journalCount(MockJournal.getStubJournalResponseDtos().size()) - .build(); - } - } - - public static class MockJournal { - public static Journal getStubJournal1() { - return Journal.builder() - .journalId(1L) - .title("230909 일지") - .content("오늘은 물을 줬다.") -// .journalImage(MockJournalImage.getStubJournalImage1()) - .leaf(MockLeaf.getStubLeaf()) - .build(); - } - public static Journal getStubJournal2() { - return Journal.builder() - .journalId(2L) - .title("230910 일지") - .content("오늘은 물을 안줬다.") -// .journalImage(MockJournalImage.getStubJournalImage2()) - .leaf(MockLeaf.getStubLeaf()) - .build(); - } - public static JournalDto.Response getStubJournalResponse1() { - LocalDateTime dateTime = LocalDateTime.of(2023, 9, 9, 14, 30, 0); - return JournalDto.Response.builder() - .journalId(getStubJournal1().getJournalId()) - .title(getStubJournal1().getTitle()) - .content(getStubJournal1().getContent()) - .imageUrl(MockJournalImage.getStubJournalImage1().getImageUrl()) - .createdAt(dateTime) - .build(); - } - public static JournalDto.Response getStubJournalResponse2() { - LocalDateTime dateTime = LocalDateTime.of(2023, 9, 10, 14, 30, 0); - return JournalDto.Response.builder() - .journalId(getStubJournal2().getJournalId()) - .title(getStubJournal2().getTitle()) - .content(getStubJournal2().getContent()) - .imageUrl(MockJournalImage.getStubJournalImage2().getImageUrl()) - .createdAt(dateTime) - .build(); - } - public static List getStubJournalResponseDtos() { - return List.of(getStubJournalResponse1(), getStubJournalResponse2()); - } - } - - public static class MockJournalImage { - public static JournalImage getStubJournalImage1() { - return JournalImage.builder() - .journalImageId(1L) - .journal(MockJournal.getStubJournal1()) - .originName("cdde9ac4bc61logo") - .imageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/journal_image/1a4ee5d0-3f55-40c6-aff9-cdde9ac4bc61logo.png") - .build(); - } - public static JournalImage getStubJournalImage2() { - return JournalImage.builder() - .journalImageId(2L) - .journal(MockJournal.getStubJournal2()) - .originName("cdde9ac4bc61logo") - .imageUrl("https://growstory.s3.ap-northeast-2.amazonaws.com/image/journal_image/1a4ee5d0-3f55-40c6-aff9-cdde9ac4bc61logo.png") - .build(); - } - - - } - - public static class MockPoint { - public static Point getStubPointResponseDtoWith500Score() { - return Point.builder() - .score(500) - .build(); - } - public static Point getStubPointResponseDtoWithNoScore() { - return Point.builder() - .score(0) - .build(); - } - } - - public static class MockBoardLikes { - public static BoardLike getBoardLike1_1() { - Account account1 = Account.builder() - .accountId(1L).displayName("김별명1").build(); - return BoardLike.builder() - .boardLikeId(1L) - .account(account1) - .board(Board.builder().boardId(1L).build()) - .build(); - } - public static BoardLike getBoardLike1_2() { - Account account1 = Account.builder() - .accountId(2L).displayName("김별명2").build(); - return BoardLike.builder() - .boardLikeId(2L) - .account(account1) - .board(Board.builder().boardId(1L).build()) - .build(); - } - public static BoardLike getBoardLike1_3() { - Account account1 = Account.builder() - .accountId(3L).displayName("김별명3").build(); - return BoardLike.builder() - .boardLikeId(3L) - .account(account1) - .board(Board.builder().boardId(1L).build()) - .build(); - } - - public static BoardLike getBoardLike2_1() { - Account account1 = Account.builder() - .accountId(1L).displayName("김별명1").build(); - return BoardLike.builder() - .boardLikeId(1L) - .account(account1) - .board(Board.builder().boardId(2L).build()) - .build(); - } - public static BoardLike getBoardLike2_2() { - Account account1 = Account.builder() - .accountId(2L).displayName("김별명2").build(); - return BoardLike.builder() - .boardLikeId(2L) - .account(account1) - .board(Board.builder().boardId(2L).build()) - .build(); - } - - public static BoardLike getBoardLike3_1() { - Account account1 = Account.builder() - .accountId(1L).displayName("김별명1").build(); - return BoardLike.builder() - .boardLikeId(1L) - .account(account1) - .board(Board.builder().boardId(3L).build()) - .build(); - } - - public static List getBoardLikes1() { - List boardLikes = new ArrayList<>(); - boardLikes.add(getBoardLike1_1()); - boardLikes.add(getBoardLike1_2()); - boardLikes.add(getBoardLike1_3()); - return boardLikes; - } - - public static List getBoardLikes2() { - List boardLikes = new ArrayList<>(); - boardLikes.add(getBoardLike2_1()); - boardLikes.add(getBoardLike2_2()); - return boardLikes; - } - - public static List getBoardLikes3() { - List boardLikes = new ArrayList<>(); - boardLikes.add(getBoardLike3_1()); - return boardLikes; - } - } - - public static class MockBoard { - - public static Board getMockBoard1() { - return Board.builder() - .account(Account.builder().accountId(1L).displayName("김별명1").build()) - .boardId(1L) - .title("제목1") - .content("내용1") - .build(); - } - - public static Board getMockBoard2() { - return Board.builder() - .account(Account.builder().accountId(2L).displayName("김별명2").build()) - .boardId(2L) - .title("제목2") - .content("내용2") - .build(); - } - - public static Board getMockBoard3() { - return Board.builder() - .account(Account.builder().accountId(3L).displayName("김별명3").build()) - .boardId(3L) - .title("제목3") - .content("내용3") - .build(); - } - public static Board getMockBoard4() { - return Board.builder() - .account(Account.builder().accountId(4L).displayName("김별명4").build()) - .boardId(4L) - .title("제목4") - .content("내용4") - .build(); - } - public static Board getMockBoard5() { - return Board.builder() - .account(Account.builder().accountId(5L).displayName("김별명5").build()) - .boardId(5L) - .title("제목4") - .content("내용4") - .build(); - } - - } -} diff --git a/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java b/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java deleted file mode 100644 index d6167f3d..00000000 --- a/server/src/test/java/com/growstory/global/customUser/annotation/WithMockCustomUser.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.growstory.global.customUser.annotation; - -import com.growstory.global.customUser.factory.WithMockCustomUserSecurityContextFactory; -import org.springframework.security.test.context.support.WithSecurityContext; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) -public @interface WithMockCustomUser { - long accountId() default 1L; - String email() default "admin@gmail.com"; - String displayName() default "관리자"; - String password() default "admin1234"; - String profileImageUrl() default ""; - String[] roles() default {"USER", "ADMIN"}; - -} diff --git a/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java b/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java deleted file mode 100644 index bc444564..00000000 --- a/server/src/test/java/com/growstory/global/customUser/factory/WithMockCustomUserSecurityContextFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.growstory.global.customUser.factory; - -import com.growstory.global.auth.utils.CustomAuthorityUtils; -import com.growstory.global.customUser.annotation.WithMockCustomUser; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.test.context.support.WithSecurityContextFactory; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory { - private static CustomAuthorityUtils authorityUtils; - private static PasswordEncoder passwordEncoder; - - public WithMockCustomUserSecurityContextFactory(CustomAuthorityUtils authorityUtils, PasswordEncoder passwordEncoder) { - this.authorityUtils = authorityUtils; - this.passwordEncoder = passwordEncoder; - } - - @Override - public SecurityContext createSecurityContext(WithMockCustomUser customUser) { - SecurityContext context = SecurityContextHolder.createEmptyContext(); - - Map claims = new HashMap<>(); - claims.put("accountId", customUser.accountId()); - claims.put("username", customUser.email()); - claims.put("displayName", customUser.displayName()); - claims.put("profileImageUrl", customUser.profileImageUrl()); - claims.put("roles", customUser.roles()); - System.out.println(Arrays.asList((String[]) claims.get("roles"))); - List authorities = authorityUtils.createAuthorities(Arrays.asList((String[]) claims.get("roles"))); - // 인증 토큰을 만들어 authentication으로 어퍼 캐스팅하여 SecurityContextHolder에 저장한다. - Authentication authentication = new UsernamePasswordAuthenticationToken(claims, passwordEncoder.encode(customUser.password()), authorities); - context.setAuthentication(authentication); - - return context; - } -}