diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml new file mode 100644 index 0000000..68c9040 --- /dev/null +++ b/.github/workflows/dev-cd.yml @@ -0,0 +1,103 @@ + +# 워크플로우의 이름 지정 +name: WINEY-DEV-CD + +# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정 +on: + push: + branches: [ dev ] # dev branch로 push 될 때 실행됩니다. + +env: + S3_BUCKET_NAME: winey-dev + +jobs: + build: + name: Code deployment + + # 실행 환경 + runs-on: ubuntu-latest + + steps: + + # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요 + - name: checkout + uses: actions/checkout@v3 + + # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + + # 3) 환경변수 파일 생성 + - name: make application.properties 파일 생성 + run: | + ## create application.yml + mkdir -p ./src/main/resources + + # application.yml 파일 생성 + touch ./application.yml + + # GitHub-Actions 에서 설정한 값을 application.yml 파일에 쓰기 + echo "${{ secrets.WINEY_DEV_APPLICATION }}" >> ./application.yaml + + # winey-firebase.json 파일 생성 + touch ./winey-firebase.json + + # Firebase secrets 파일 복사 + echo "${{ secrets.WINEY_FIREBASE }}" >> ./winey-firebase.json + + # application.yml 파일 확인 + cat ./application.yml + shell: bash + + # 이 워크플로우는 gradle build + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle # 실제 application build 테스트 제외 + run: ./gradlew build -x test + + # 디렉토리 생성 + - name: Make Directory + run: mkdir -p deploy + + # Jar 파일 복사 + - name: Copy Jar + run: cp ./build/libs/*.jar ./deploy + + # appspec.yml 파일 복사 + - name: Copy appspec.yml + run: cp appspec.yml ./deploy + + # script files 복사 + - name: Copy script + run: cp ./scripts/*.sh ./deploy + + - name: Make zip file + run: zip -r ./winey_dev_server.zip ./deploy + shell: bash + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_DEV_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_DEV_SECRET_KEY }} + aws-region: ap-northeast-2 + + - name: Upload to S3 + run: aws s3 cp --region ap-northeast-2 ./winey_dev_server.zip s3://$S3_BUCKET_NAME/ + + # Deploy + - name: Deploy + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DEV_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DEV_SECRET_KEY }} + run: + aws deploy create-deployment + --application-name winey-dev-codedeploy + --deployment-group-name winey-dev-codedeploy-group + --file-exists-behavior OVERWRITE + --s3-location bucket=winey-dev,bundleType=zip,key=winey_dev_server.zip + --region ap-northeast-2 diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml new file mode 100644 index 0000000..483697d --- /dev/null +++ b/.github/workflows/dev-ci.yml @@ -0,0 +1,53 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: WINEY DEV CI + +on: + pull_request: + branches: [ "dev" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + + - name: make application.properties 파일 생성 + run: | + ## create application.yml + mkdir -p ./src/main/resources + + # application.yml 파일 생성 + touch ./application.yml + + # GitHub-Actions 에서 설정한 값을 application.yml 파일에 쓰기 + echo "${{ secrets.WINEY_DEV_APPLICATION }}" >> ./application.yml + + # Firebase secrets 파일 복사 + echo "${{ secrets.WINEY_FIREBASE }}" >> ./winey-firebase.json + + # application.yml 파일 확인 + cat ./application.yml + shell: bash + + # 이 워크플로우는 gradle build + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle # 실제 application build + run: ./gradlew build -PactiveProfiles=local diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7fd3784..ebe3024 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -44,7 +44,7 @@ jobs: - name: make application.properties 파일 생성 run: | ## create application.yml - cd ./src/main/resources + mkdir -p ./src/main/resources # application.yml 파일 생성 touch ./application.yml diff --git a/.gitignore b/.gitignore index 162f436..e9d8ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ out/ ### application.yml ### application.yaml +application.yml ### DS_Store .DS_Store diff --git a/build.gradle b/build.gradle index e6ddff3..8885425 100644 --- a/build.gradle +++ b/build.gradle @@ -33,8 +33,8 @@ dependencies { // JPA & Database implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'mysql:mysql-connector-java:8.0.32' - +// implementation 'mysql:mysql-connector-java:8.0.32' + runtimeOnly 'com.mysql:mysql-connector-j' // S3 AWS implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE' @@ -58,6 +58,7 @@ dependencies { //FCM implementation group: 'com.google.firebase', name: 'firebase-admin', version: '6.8.1' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' // Firebase 서버로 푸시 메시지 전송 시 필요 //rabbitmq implementation 'org.springframework.boot:spring-boot-starter-amqp' @@ -66,6 +67,10 @@ dependencies { // ShedLock implementation 'net.javacrumbs.shedlock:shedlock-spring:4.14.0' implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.14.0' + + // ssh + implementation 'com.github.mwiede:jsch:0.2.17' + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" } tasks.named('test') { diff --git a/scripts/deploy.sh b/scripts/deploy.sh index b905050..f2a3a52 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -40,17 +40,17 @@ if [ -z $IDLE_PID ] then echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." else - echo "> kill -15 $IDLE_PID" - kill -15 $IDLE_PID + echo "> kill -9 $IDLE_PID" + kill -9 $IDLE_PID sleep 20 fi echo "> $IDLE_PROFILE 배포" nohup java -jar -Duser.timezone=Asia/Seoul -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH >> /home/ubuntu/app/nohup.out 2>&1 & -echo "> $IDLE_PROFILE 10초 후 Health check 시작" +echo "> $IDLE_PROFILE 60초 후 Health check 시작" echo "> curl -s http://localhost:$IDLE_PORT/health " -sleep 10 +sleep 60 for retry_count in {1..10} do diff --git a/src/main/java/org/winey/server/common/message/MessageQueueReceiver.java b/src/main/java/org/winey/server/common/message/MessageQueueReceiver.java index dd1c546..b23a3fd 100644 --- a/src/main/java/org/winey/server/common/message/MessageQueueReceiver.java +++ b/src/main/java/org/winey/server/common/message/MessageQueueReceiver.java @@ -6,6 +6,8 @@ import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import org.winey.server.domain.notification.Notification; import org.winey.server.service.FcmService; @@ -13,6 +15,9 @@ import java.io.*; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; + @Component @AllArgsConstructor public class MessageQueueReceiver { @@ -24,7 +29,8 @@ public class MessageQueueReceiver { value = @Queue(name = "like-notification"), key = "like-noti") ) - public void likeReceiver(byte[] likeNoti){ + public void likeReceiver(byte[] likeNoti, Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws + IOException { System.out.println("좋아요 noti receiver"); ByteArrayInputStream bis = new ByteArrayInputStream(likeNoti); ObjectInput in = null; @@ -34,10 +40,14 @@ public void likeReceiver(byte[] likeNoti){ System.out.println("여기까진 오는가"); if (obj instanceof FcmRequestDto){ FcmRequestDto notification = (FcmRequestDto) obj; - fcmService.sendByToken(notification); + String response = String.valueOf(fcmService.sendByToken(notification)); + if (response.isEmpty()){ + throw new IOException(); + } } }catch (IOException | ClassNotFoundException e){ e.printStackTrace(); + channel.basicNack(tag,false,true); }finally { try{ bis.close(); @@ -62,9 +72,12 @@ public void commentReceiver(byte[] commentNoti){ try { in = new ObjectInputStream(bis); Object obj = in.readObject(); - if (obj instanceof Notification){ + if (obj instanceof FcmRequestDto){ FcmRequestDto notification = (FcmRequestDto) obj; - fcmService.sendByToken(notification); + String response = String.valueOf(fcmService.sendByToken(notification)); + if (response.isEmpty()){ + throw new IOException(); + } } }catch (IOException | ClassNotFoundException e){ e.printStackTrace(); diff --git a/src/main/java/org/winey/server/config/ssh/SshDataSourceConfig.java b/src/main/java/org/winey/server/config/ssh/SshDataSourceConfig.java new file mode 100644 index 0000000..5ede4c5 --- /dev/null +++ b/src/main/java/org/winey/server/config/ssh/SshDataSourceConfig.java @@ -0,0 +1,37 @@ +package org.winey.server.config.ssh; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; + +import javax.sql.DataSource; + +@Slf4j +@Profile({"local","set1","set2"}) +@Configuration +@RequiredArgsConstructor +public class SshDataSourceConfig { + + private final SshTunnelingInitializer initializer; + + @Bean("dataSource") + @Primary + public DataSource dataSource(DataSourceProperties properties) { + + Integer forwardedPort = initializer.buildSshConnection(); // ssh 연결 및 터널링 설정 + String url = properties.getUrl().replace("[forwardedPort]", Integer.toString(forwardedPort)); + log.info(url); + return DataSourceBuilder.create() + .url(url) + .username(properties.getUsername()) + .password(properties.getPassword()) + .driverClassName(properties.getDriverClassName()) + .build(); + } + +} diff --git a/src/main/java/org/winey/server/config/ssh/SshTunnelingInitializer.java b/src/main/java/org/winey/server/config/ssh/SshTunnelingInitializer.java new file mode 100644 index 0000000..a739b6b --- /dev/null +++ b/src/main/java/org/winey/server/config/ssh/SshTunnelingInitializer.java @@ -0,0 +1,83 @@ +package org.winey.server.config.ssh; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.PreDestroy; +import javax.validation.constraints.NotNull; +import java.util.Properties; + +import static java.lang.System.exit; + +@Slf4j +@Profile({"local","set1","set2"}) +@Component +@ConfigurationProperties(prefix = "ssh") +@Validated +@Setter +public class SshTunnelingInitializer { + + @NotNull + private String remoteJumpHost; + @NotNull + private String user; + @NotNull + private int sshPort; + @NotNull + private String privateKey; + @NotNull + private String databaseUrl; + @NotNull + private int databasePort; + + private Session session; + + @PreDestroy + public void closeSSH() { + if (session.isConnected()) + session.disconnect(); + } + + public Integer buildSshConnection() { + + Integer forwardedPort = null; + + try { + log.info("{}@{}:{}:{} with privateKey",user, remoteJumpHost, sshPort, databasePort); + + log.info("start ssh tunneling.."); + JSch jSch = new JSch(); + + log.info("creating ssh session"); + jSch.addIdentity(privateKey); // 개인키 + session = jSch.getSession(user, remoteJumpHost, sshPort); // 세션 설정 + Properties config = new Properties(); + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + log.info("complete creating ssh session"); + + log.info("start connecting ssh connection"); + session.connect(); // ssh 연결 + log.info("success connecting ssh connection "); + + // 로컬pc의 남는 포트 하나와 원격 접속한 pc의 db포트 연결 + log.info("start forwarding"); + forwardedPort = session.setPortForwardingL(0, databaseUrl, databasePort); + log.info("successfully connected to database"); + + } catch (Exception e){ + log.error("fail to make ssh tunneling"); + this.closeSSH(); + e.printStackTrace(); + exit(1); + } + + return forwardedPort; + } +} diff --git a/src/main/java/org/winey/server/controller/BroadCastController.java b/src/main/java/org/winey/server/controller/BroadCastController.java new file mode 100644 index 0000000..b140814 --- /dev/null +++ b/src/main/java/org/winey/server/controller/BroadCastController.java @@ -0,0 +1,35 @@ +package org.winey.server.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.winey.server.common.dto.ApiResponse; +import org.winey.server.controller.request.broadcast.BroadCastAllUserDto; +import org.winey.server.exception.Success; +import org.winey.server.service.BroadCastService; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.firebase.messaging.FirebaseMessagingException; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/broadcast") +@Tag(name = "BroadCast", description = "위니 전체 푸시 API Document") +public class BroadCastController { + private final BroadCastService broadCastService; + + @PostMapping("/send-all") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "전체 유저에게 메시지 발송 API", description = "전체 유저에게 메시지를 발송합니다.") + public ApiResponse sendMessageToEntireUser(@RequestBody BroadCastAllUserDto broadCastAllUserDto){ + return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, broadCastService.broadAllUser(broadCastAllUserDto)); + } + +} diff --git a/src/main/java/org/winey/server/controller/GoalController.java b/src/main/java/org/winey/server/controller/GoalController.java deleted file mode 100644 index efb955d..0000000 --- a/src/main/java/org/winey/server/controller/GoalController.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.winey.server.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -import org.winey.server.common.dto.ApiResponse; -import org.winey.server.config.resolver.UserId; -import org.winey.server.controller.request.goal.GoalRequestCreateDto; -import org.winey.server.controller.response.goal.GoalResponseCreateDto; -import org.winey.server.exception.Success; -import org.winey.server.service.GoalService; - -import javax.validation.Valid; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/goal") -@Tag(name = "Goal", description = "위니 목표 API Document") -public class GoalController { - - private final GoalService goalService; - - @PostMapping("") - @ResponseStatus(HttpStatus.CREATED) - @Operation(summary = "목표 생성 API", description = "위니 목표를 설정합니다.") - public ApiResponse create(@RequestBody @Valid final GoalRequestCreateDto requestDto, @UserId Long userId) { - return ApiResponse.success(Success.CREATE_GOAL_SUCCESS, goalService.createGoal(requestDto, userId)); - } -} diff --git a/src/main/java/org/winey/server/controller/UserController.java b/src/main/java/org/winey/server/controller/UserController.java index aa3fc9b..74ae27e 100644 --- a/src/main/java/org/winey/server/controller/UserController.java +++ b/src/main/java/org/winey/server/controller/UserController.java @@ -10,6 +10,7 @@ import org.winey.server.controller.request.UpdateAllowedPushDto; import org.winey.server.controller.request.UpdateFcmTokenDto; import org.winey.server.controller.request.UpdateUserNicknameDto; +import org.winey.server.controller.response.user.GetAchievementStatusResponseDto; import org.winey.server.controller.response.user.UserResponseDto; import org.winey.server.exception.Error; import org.winey.server.exception.Success; @@ -77,4 +78,11 @@ public ApiResponse updateAllowedNotification (@UserId Long userId, @RequestBody }; return ApiResponse.success(Success.UPDATE_PUSH_ALLOWED_SUCCESS, result); } + + @GetMapping("/achievement-status") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "레벨 달성 현황 조회 API", description = "유저의 레벨 달성 현황을 조회합니다.") + public ApiResponse getAchievementStatus(@UserId Long userId) { + return ApiResponse.success(Success.GET_ACHIEVEMENT_STATUS_SUCCESS, userService.getAchievementStatus(userId)); + } } diff --git a/src/main/java/org/winey/server/controller/request/CreateFeedRequestDto.java b/src/main/java/org/winey/server/controller/request/CreateFeedRequestDto.java index f8c3421..83be69c 100644 --- a/src/main/java/org/winey/server/controller/request/CreateFeedRequestDto.java +++ b/src/main/java/org/winey/server/controller/request/CreateFeedRequestDto.java @@ -3,6 +3,7 @@ import lombok.*; import org.springframework.web.multipart.MultipartFile; +import javax.annotation.Nullable; import javax.validation.constraints.DecimalMax; import javax.validation.constraints.NotNull; @@ -16,4 +17,10 @@ public class CreateFeedRequestDto { private MultipartFile feedImage; @NotNull @DecimalMax(value = "9999999") private Long feedMoney; + @Nullable + private String feedType = null; + + public void setFeedType(String type) { + this.feedType = type; + } } diff --git a/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java b/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java new file mode 100644 index 0000000..4baa93a --- /dev/null +++ b/src/main/java/org/winey/server/controller/request/broadcast/BroadCastAllUserDto.java @@ -0,0 +1,15 @@ +package org.winey.server.controller.request.broadcast; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor +public class BroadCastAllUserDto { + String title; + + String message; +} diff --git a/src/main/java/org/winey/server/controller/request/goal/GoalRequestCreateDto.java b/src/main/java/org/winey/server/controller/request/goal/GoalRequestCreateDto.java deleted file mode 100644 index 726b103..0000000 --- a/src/main/java/org/winey/server/controller/request/goal/GoalRequestCreateDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.winey.server.controller.request.goal; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.validation.constraints.DecimalMax; -import javax.validation.constraints.DecimalMin; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class GoalRequestCreateDto { - @DecimalMin(value = "30000") @DecimalMax(value = "999999999") - private Long targetMoney; - @DecimalMin(value = "5") @DecimalMax(value = "365") - private int targetDay; -} diff --git a/src/main/java/org/winey/server/controller/response/feed/CreateFeedResponseDto.java b/src/main/java/org/winey/server/controller/response/feed/CreateFeedResponseDto.java index 7fcb7ea..accccd1 100644 --- a/src/main/java/org/winey/server/controller/response/feed/CreateFeedResponseDto.java +++ b/src/main/java/org/winey/server/controller/response/feed/CreateFeedResponseDto.java @@ -13,9 +13,10 @@ public class CreateFeedResponseDto { private Long feedId; private LocalDateTime createdAt; + private Boolean levelUpgraded; - public static CreateFeedResponseDto of(Long feedId, LocalDateTime createdAt){ - return new CreateFeedResponseDto(feedId, createdAt); + public static CreateFeedResponseDto of(Long feedId, LocalDateTime createdAt, Boolean levelUpgraded){ + return new CreateFeedResponseDto(feedId, createdAt, levelUpgraded); } } diff --git a/src/main/java/org/winey/server/controller/response/feed/GetFeedResponseDto.java b/src/main/java/org/winey/server/controller/response/feed/GetFeedResponseDto.java index 4071046..8e4b893 100644 --- a/src/main/java/org/winey/server/controller/response/feed/GetFeedResponseDto.java +++ b/src/main/java/org/winey/server/controller/response/feed/GetFeedResponseDto.java @@ -18,6 +18,7 @@ public class GetFeedResponseDto { private String nickName; private int writerLevel; // 여기까쥐 + private String feedType; private String feedTitle; private String feedImage; private Long feedMoney; @@ -28,8 +29,8 @@ public class GetFeedResponseDto { private LocalDateTime createdAt; - public static GetFeedResponseDto of(Long feedId, Long userId, String nickName, int writerLevel, String feedTitle, String feedImage, + public static GetFeedResponseDto of(Long feedId, Long userId, String nickName, int writerLevel, String feedType, String feedTitle, String feedImage, Long feedMoney, Boolean isLiked, Long likes, Long comments, String timeAgo, LocalDateTime createdAt) { - return new GetFeedResponseDto(feedId, userId, nickName, writerLevel, feedTitle, feedImage, feedMoney, isLiked, likes, comments, timeAgo, createdAt); + return new GetFeedResponseDto(feedId, userId, nickName, writerLevel, feedType, feedTitle, feedImage, feedMoney, isLiked, likes, comments, timeAgo, createdAt); } } diff --git a/src/main/java/org/winey/server/controller/response/goal/GoalResponseCreateDto.java b/src/main/java/org/winey/server/controller/response/goal/GoalResponseCreateDto.java deleted file mode 100644 index 762c1fc..0000000 --- a/src/main/java/org/winey/server/controller/response/goal/GoalResponseCreateDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.winey.server.controller.response.goal; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDate; -import java.time.LocalDateTime; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class GoalResponseCreateDto { - private Long userId; - private Long targetMoney; - private LocalDate targetDate; - private LocalDateTime createdAt; - - public static GoalResponseCreateDto of(Long userId, Long targetMoney, LocalDate targetDate, LocalDateTime createdAt) { - return new GoalResponseCreateDto(userId, targetMoney, targetDate, createdAt); - } -} diff --git a/src/main/java/org/winey/server/controller/response/user/GetAchievementStatusResponseDto.java b/src/main/java/org/winey/server/controller/response/user/GetAchievementStatusResponseDto.java new file mode 100644 index 0000000..cfcbb38 --- /dev/null +++ b/src/main/java/org/winey/server/controller/response/user/GetAchievementStatusResponseDto.java @@ -0,0 +1,21 @@ +package org.winey.server.controller.response.user; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.winey.server.domain.user.UserLevel; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class GetAchievementStatusResponseDto { + + private String userLevel; + private Long remainingAmount; + private Long remainingCount; + + public static GetAchievementStatusResponseDto of(UserLevel userLevel, Long remainingAmount, Long remainingCount) { + return new GetAchievementStatusResponseDto(userLevel.getName(), remainingAmount, remainingCount); + } +} diff --git a/src/main/java/org/winey/server/controller/response/user/UserResponseDto.java b/src/main/java/org/winey/server/controller/response/user/UserResponseDto.java index 7425703..9b98541 100644 --- a/src/main/java/org/winey/server/controller/response/user/UserResponseDto.java +++ b/src/main/java/org/winey/server/controller/response/user/UserResponseDto.java @@ -10,11 +10,31 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE) public class UserResponseDto { - private UserResponseUserDto userResponseUserDto; - private UserResponseGoalDto userResponseGoalDto; - public static UserResponseDto of(UserResponseUserDto userResponseUserDto, UserResponseGoalDto userResponseGoalDto) { - return new UserResponseDto(userResponseUserDto, userResponseGoalDto); + private UserData userData; + + @Getter + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class UserData { + private Long userId; + private Long createdDday; + private String nickname; + private String userLevel; + private Boolean fcmIsAllowed; + private Long accumulatedAmount; + private Long accumulatedCount; + private Long amountSavedTwoWeeks; + private Long amountSpentTwoWeeks; + private Long remainingAmount; + private Long remainingCount; } -} + public static UserResponseDto of(Long userId, Long createdDday, String nickname, String userLevel, + Boolean fcmIsAllowed, Long accumulatedAmount,Long accumulatedCount, Long amountSavedTwoWeeks, + Long amountSpentTwoWeeks, Long remainingAmount, Long remainingCount) { + UserData userData = new UserData(userId, createdDday, nickname, userLevel, fcmIsAllowed, accumulatedAmount, accumulatedCount, + amountSavedTwoWeeks, amountSpentTwoWeeks,remainingAmount,remainingCount); + return new UserResponseDto(userData); + } +} diff --git a/src/main/java/org/winey/server/controller/response/user/UserResponseGoalDto.java b/src/main/java/org/winey/server/controller/response/user/UserResponseGoalDto.java deleted file mode 100644 index 42c8a45..0000000 --- a/src/main/java/org/winey/server/controller/response/user/UserResponseGoalDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.winey.server.controller.response.user; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class UserResponseGoalDto { - private Long duringGoalAmount; - private Long duringGoalCount; - private Long targetMoney; - private int targetDay; - private int dDay; - private Boolean isOver; - private Boolean isAttained; - - public static UserResponseGoalDto of(Long duringGoalAmount, Long duringGoalCount, Long targetMoney, int targetDay, int dDay, boolean isOver, boolean isAttained) { - return new UserResponseGoalDto(duringGoalAmount, duringGoalCount, targetMoney, targetDay, dDay, isOver, isAttained); - } - -} diff --git a/src/main/java/org/winey/server/controller/response/user/UserResponseUserDto.java b/src/main/java/org/winey/server/controller/response/user/UserResponseUserDto.java deleted file mode 100644 index ad40ac9..0000000 --- a/src/main/java/org/winey/server/controller/response/user/UserResponseUserDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.winey.server.controller.response.user; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class UserResponseUserDto { - private Long userId; - private String nickname; - private String userLevel; - private Boolean fcmIsAllowed; - - public static UserResponseUserDto of(Long userId, String nickname, String userLevel, Boolean fcmIsAllowed) { - return new UserResponseUserDto(userId, nickname, userLevel, fcmIsAllowed); - } -} \ No newline at end of file diff --git a/src/main/java/org/winey/server/domain/feed/Feed.java b/src/main/java/org/winey/server/domain/feed/Feed.java index f586daa..96eb6cd 100644 --- a/src/main/java/org/winey/server/domain/feed/Feed.java +++ b/src/main/java/org/winey/server/domain/feed/Feed.java @@ -1,27 +1,26 @@ package org.winey.server.domain.feed; +import java.util.List; +import javax.persistence.*; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; import org.winey.server.domain.AuditingTimeEntity; import org.winey.server.domain.comment.Comment; -import org.winey.server.domain.goal.Goal; import org.winey.server.domain.user.User; -import javax.persistence.*; -import java.time.LocalDateTime; -import java.util.List; - @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Feed extends AuditingTimeEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "feed_id") private Long feedId; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="user_id") private User user; @@ -32,13 +31,13 @@ public class Feed extends AuditingTimeEntity { @Column(nullable = false) private String feedImage; + @Column + @Enumerated(EnumType.STRING) + private FeedType feedType; + @Column(nullable = false) private Long feedMoney; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "goal_id") - private Goal goal; - @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "feed", orphanRemoval = true) private List feedLikes; @@ -46,11 +45,11 @@ public class Feed extends AuditingTimeEntity { private List comments; @Builder - public Feed(User user, String feedTitle, String feedImage, Long feedMoney, Goal goal){ + public Feed(User user, String feedTitle, String feedImage, Long feedMoney, FeedType feedType) { this.user = user; this.feedTitle = feedTitle; this.feedImage = feedImage; this.feedMoney = feedMoney; - this.goal = goal; + this.feedType = feedType; } } diff --git a/src/main/java/org/winey/server/domain/feed/FeedType.java b/src/main/java/org/winey/server/domain/feed/FeedType.java new file mode 100644 index 0000000..03c5272 --- /dev/null +++ b/src/main/java/org/winey/server/domain/feed/FeedType.java @@ -0,0 +1,23 @@ +package org.winey.server.domain.feed; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +@Getter +@AllArgsConstructor +public enum FeedType { + + SAVE("SAVE"), + CONSUME("CONSUME"); + + private final String stringVal; + + public static boolean isValidFeedType(String type) { + for (FeedType feed : FeedType.values()) { + if (Objects.equals(feed.getStringVal(), type)) return true; + } + return false; + } +} diff --git a/src/main/java/org/winey/server/domain/goal/Goal.java b/src/main/java/org/winey/server/domain/goal/Goal.java deleted file mode 100644 index d318f53..0000000 --- a/src/main/java/org/winey/server/domain/goal/Goal.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.winey.server.domain.goal; - -import java.util.ArrayList; -import java.util.List; -import lombok.*; -import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.DynamicInsert; -import org.winey.server.domain.AuditingTimeEntity; -import org.winey.server.domain.feed.Feed; -import org.winey.server.domain.notification.Notification; -import org.winey.server.domain.user.User; - -import javax.persistence.*; -import java.time.LocalDate; - -@Entity -@Getter -@DynamicInsert -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Goal extends AuditingTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long goalId; - - @Column(nullable = false) - private Long targetMoney; - - @Column(nullable = false) - private LocalDate targetDate; - - @Column(nullable = false) - private Long duringGoalAmount; - - @Column(nullable = false) - private boolean isAttained; - - @Column(nullable = false) - private Long duringGoalCount; - - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT)) - private User user; - - @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "goal", orphanRemoval = true) - private List feeds = new ArrayList<>(); - - @Builder - public Goal(Long targetMoney, LocalDate targetDate, User user) { - this.targetMoney = targetMoney; - this.targetDate = targetDate; - this.user = user; - this.duringGoalCount = 0L; - this.isAttained = false; - this.duringGoalAmount = 0L; - - } - - public void updateIsAttained(boolean isAttained) { - this.isAttained = isAttained; - } - - public void updateGoalCountAndAmount(Long feedMoney, boolean createOrDelete) { - if (createOrDelete) { - this.duringGoalCount += 1; - this.duringGoalAmount += feedMoney; - } else { - this.duringGoalCount -= 1; - this.duringGoalAmount -= feedMoney; - } - } - - public void resetGoalCountAndAmount(){ - this.duringGoalAmount = 0L; - this.duringGoalCount = 0L; - } -} diff --git a/src/main/java/org/winey/server/domain/goal/GoalType.java b/src/main/java/org/winey/server/domain/goal/GoalType.java new file mode 100644 index 0000000..dae96c5 --- /dev/null +++ b/src/main/java/org/winey/server/domain/goal/GoalType.java @@ -0,0 +1,28 @@ +package org.winey.server.domain.goal; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.winey.server.domain.user.UserLevel; + +@Getter +@AllArgsConstructor +public enum GoalType { + COMMONER_GOAL(UserLevel.COMMONER, 5000, 2, "아메리카노"), + KNIGHT_GOAL(UserLevel.KNIGHT, 30000, 7, "치킨"), + ARISTOCRAT_GOAL(UserLevel.ARISTOCRAT, 150000, 10, "운동화"), + EMPEROR_GOAL(UserLevel.EMPEROR, 300000, 20, "에어팟"); + + private final UserLevel userLevel; + private final int targetMoney; + private final int targetCount; + private final String targetProduct; + + public static GoalType findGoalTypeByUserLevel(UserLevel userLevel) { + for (GoalType goalType : GoalType.values()) { + if (goalType.getUserLevel() == userLevel) { + return goalType; + } + } + return null; + } +} diff --git a/src/main/java/org/winey/server/domain/notification/NotiType.java b/src/main/java/org/winey/server/domain/notification/NotiType.java index a99464d..086059e 100644 --- a/src/main/java/org/winey/server/domain/notification/NotiType.java +++ b/src/main/java/org/winey/server/domain/notification/NotiType.java @@ -2,10 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; - -import javax.validation.constraints.Null; @Getter @AllArgsConstructor @@ -18,8 +14,7 @@ public enum NotiType { //삭제로 등급 강등 DELETERANKDOWNTO3("게시글이 삭제되어 등급이 귀족으로 내려갔어요."), DELETERANKDOWNTO2("게시글이 삭제되어 등급이 기사로 내려갔어요."), - - DELETERANKDOWNTO1("게시글이 삭제되어 등급이 귀족으로 내려갔어요."), + DELETERANKDOWNTO1("게시글이 삭제되어 등급이 평민으로 내려갔어요."), //목표 달성 실패 GOALFAILED("이번에는 아쉽지만 힘내서 다음 목표를 세워볼까요?"), @@ -31,7 +26,7 @@ public enum NotiType { COMMENTNOTI("님이 회원님의 게시글에 댓글을 남겼어요."), //처음 로그인했을 때 생성하는 알림 - HOWTOLEVELUP("위니의 캐릭터 레벨업 방법을 알아볼까요?"); + HOWTOLEVELUP("절약 콘텐츠 업로드를 통해 레벨업을 해보세요!"); private final String type; diff --git a/src/main/java/org/winey/server/domain/user/User.java b/src/main/java/org/winey/server/domain/user/User.java index f20fdc0..1cd95a5 100644 --- a/src/main/java/org/winey/server/domain/user/User.java +++ b/src/main/java/org/winey/server/domain/user/User.java @@ -1,5 +1,7 @@ package org.winey.server.domain.user; +import static org.winey.server.domain.user.UserLevel.COMMONER; + import java.util.List; import java.util.Objects; import javax.persistence.CascadeType; @@ -21,7 +23,6 @@ import org.winey.server.domain.comment.Comment; import org.winey.server.domain.feed.Feed; import org.winey.server.domain.feed.FeedLike; -import org.winey.server.domain.goal.Goal; import org.winey.server.domain.notification.Notification; import org.winey.server.domain.recommend.Recommend; @@ -58,11 +59,15 @@ public class User extends AuditingTimeEntity { @Column(nullable = true) private Boolean fcmIsAllowed = true; - @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "user", orphanRemoval = true) - private List goals; + @Column + private Long savedAmount; + + @Column + private Long savedCount; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "user", orphanRemoval = true) private List recommends; + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "user", orphanRemoval = true) private List feeds; @@ -78,15 +83,33 @@ public class User extends AuditingTimeEntity { @Builder public User(String nickname, String socialId, SocialType socialType) { this.nickname = nickname; - this.userLevel = UserLevel.COMMONER; + this.userLevel = COMMONER; this.socialId = socialId; this.socialType = socialType; + this.savedCount = 0L; + this.savedAmount = 0L; } public void updateUserLevel(UserLevel userLevel){ this.userLevel = userLevel; } + public void upgradeUserLevel() { + switch (this.userLevel) { + case COMMONER: + this.userLevel = UserLevel.KNIGHT; + break; + case KNIGHT: + this.userLevel = UserLevel.ARISTOCRAT; + break; + case ARISTOCRAT: + this.userLevel = UserLevel.EMPEROR; + break; + case EMPEROR: + break; + } + } + public void updateRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } @@ -99,6 +122,16 @@ public void updateNickname(String nickname) { public void updateFcmIsAllowed(Boolean isAllowed){this.fcmIsAllowed = isAllowed;} + public void increaseSavedAmountAndCount(Long money) { + this.savedAmount += money; + this.savedCount += 1; + } + + public void decreaseSavedAmountAndCount(Long money) { + this.savedCount -= 1; + this.savedAmount -= money; + } + public String getFcmToken() { if (Objects.nonNull(this.fcmToken)) { return this.fcmToken; diff --git a/src/main/java/org/winey/server/domain/user/UserLevel.java b/src/main/java/org/winey/server/domain/user/UserLevel.java index 5303744..f7194e7 100644 --- a/src/main/java/org/winey/server/domain/user/UserLevel.java +++ b/src/main/java/org/winey/server/domain/user/UserLevel.java @@ -6,11 +6,33 @@ @Getter @AllArgsConstructor public enum UserLevel { - COMMONER("평민", 1), - KNIGHT("기사", 2), - ARISTOCRAT("귀족", 3), - EMPEROR("황제", 4); + COMMONER("평민", 1, 0L, 0L), + KNIGHT("기사", 2, 30000L, 2L), + ARISTOCRAT("귀족", 3, 150000L, 4L), + EMPEROR("황제", 4, 300000L, 10L); private final String name; private final int levelNumber; + private final Long minimumAmount; + private final Long minimumCount; + + public static UserLevel calculateUserLevel(Long amount, Long count) { + if (amount >= EMPEROR.minimumAmount && count >= EMPEROR.minimumCount) { + return EMPEROR; + } else if (amount >= ARISTOCRAT.minimumAmount && count >= ARISTOCRAT.minimumCount) { + return ARISTOCRAT; + } else if (amount >= KNIGHT.minimumAmount && count >= KNIGHT.minimumCount) { + return KNIGHT; + } + return COMMONER; + } + + public static UserLevel getNextUserLevel(UserLevel currentLevel) { + for (UserLevel level : UserLevel.values()) { + if (level.getLevelNumber() == currentLevel.getLevelNumber() + 1) { + return level; + } + } + return null; + } } diff --git a/src/main/java/org/winey/server/exception/Error.java b/src/main/java/org/winey/server/exception/Error.java index 343ba9b..146fccb 100644 --- a/src/main/java/org/winey/server/exception/Error.java +++ b/src/main/java/org/winey/server/exception/Error.java @@ -17,6 +17,7 @@ public enum Error { LOGIN_SUCCESS(HttpStatus.OK, "로그인에 성공했습니다."), INVALID_PASSWORD_EXCEPTION(HttpStatus.BAD_REQUEST, "잘못된 비밀번호가 입력됐습니다."), NOT_FOUND_IMAGE_EXCEPTION(HttpStatus.BAD_REQUEST, "잘못된 이미지 파일입니다"), + INVALID_FEEDTYPE(HttpStatus.BAD_REQUEST, "잘못된 피드유형 입니다"), VALIDATION_REQUEST_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "요청값이 입력되지 않았습니다."), VALIDATION_REQUEST_HEADER_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "요청 헤더값이 입력되지 않았습니다."), VALIDATION_REQUEST_PARAMETER_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "요청 파라미터값이 입력되지 않았습니다."), @@ -32,6 +33,7 @@ public enum Error { INVALID_APPLE_CLAIMS(HttpStatus.BAD_REQUEST, "Apple OAuth Claims 값이 올바르지 않습니다."), INVALID_ENCRYPT_COMMUNICATION(HttpStatus.BAD_REQUEST, "Apple OAuth 통신 암호화 과정 중 문제가 발생했습니다."), CREATE_PUBLIC_KEY_EXCEPTION(HttpStatus.BAD_REQUEST, "Apple OAuth 로그인 중 public verify 생성에 문제가 발생했습니다."), + INVALID_USER_LEVEL_EXCEPTION(HttpStatus.BAD_REQUEST, "존재하지 않는 유저 레벨입니다."), /** * 401 UNAUTHORIZED @@ -64,6 +66,9 @@ public enum Error { * 422 UNPROCESSABLE ENTITY */ UNPROCESSABLE_ENTITY_DELETE_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "클라의 요청을 이해했지만 삭제하지 못했습니다."), + UNPROCESSABLE_FIND_USERS(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 이해했지만 유저들을 찾을 수 없었습니다."), + UNPROCESSABLE_SEND_TO_FIREBASE(HttpStatus.UNPROCESSABLE_ENTITY, "파이어베이스로 전송하는 과정에서 에러가 발생했습니다."), + UNPROCESSABLE_KAKAO_SERVER_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 이해했지만 카카오 서버에러가 발생했습니다."), /** * 500 INTERNAL SERVER ERROR diff --git a/src/main/java/org/winey/server/exception/Success.java b/src/main/java/org/winey/server/exception/Success.java index 15fdc40..02492a4 100644 --- a/src/main/java/org/winey/server/exception/Success.java +++ b/src/main/java/org/winey/server/exception/Success.java @@ -27,6 +27,9 @@ public enum Success { CHECK_NICKNAME_DUPLICATE_SUCCESS(HttpStatus.OK, "닉네임 중복 확인 성공"), CHECK_NEW_NOTIFICATION_SUCCESS(HttpStatus.OK, "새 알림 여부 조회 성공"), BLOCK_USER_SUCCESS(HttpStatus.OK, "유저 차단 성공"), + GET_ACHIEVEMENT_STATUS_SUCCESS(HttpStatus.OK, "레벨 달성 현황 조회 성공"), + + SEND_ENTIRE_MESSAGE_SUCCESS(HttpStatus.OK, "전체 메시지 전송 성공"), /** * 201 CREATED diff --git a/src/main/java/org/winey/server/infrastructure/FeedRepository.java b/src/main/java/org/winey/server/infrastructure/FeedRepository.java index 6a20c82..442256c 100644 --- a/src/main/java/org/winey/server/infrastructure/FeedRepository.java +++ b/src/main/java/org/winey/server/infrastructure/FeedRepository.java @@ -1,17 +1,16 @@ package org.winey.server.infrastructure; +import java.time.LocalDateTime; import java.util.Collection; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; import org.winey.server.domain.feed.Feed; import org.winey.server.domain.user.User; -import java.util.List; -import java.util.Optional; - public interface FeedRepository extends Repository { void save(Feed feed); Optional findByFeedId(Long feedId); @@ -20,4 +19,9 @@ public interface FeedRepository extends Repository { Page findAllByUserOrderByCreatedAtDesc(User user, Pageable pageable); Page findByUserNotInOrderByCreatedAtDesc(Collection users, Pageable pageable); + @Query("select sum(f.feedMoney) from Feed f where f.user = :user and f.feedType = 'SAVE' and f.createdAt > :date") + Long getSavedAmountForPeriod(@Param("user") User user, @Param("date") LocalDateTime date); + + @Query("select sum(f.feedMoney) from Feed f where f.user = :user and f.feedType = 'CONSUME' and f.createdAt > :date") + Long getSpentAmountForPeriod(@Param("user") User user, @Param("date") LocalDateTime date); } diff --git a/src/main/java/org/winey/server/infrastructure/GoalRepository.java b/src/main/java/org/winey/server/infrastructure/GoalRepository.java deleted file mode 100644 index 6e82685..0000000 --- a/src/main/java/org/winey/server/infrastructure/GoalRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.winey.server.infrastructure; - -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; -import org.winey.server.domain.goal.Goal; -import org.winey.server.domain.user.User; - -import java.util.List; - -public interface GoalRepository extends Repository { - // CREATE - Goal save(Goal goal); - - // READ - - int countByUserAndIsAttained(User user, boolean isAttained); - @Query("SELECT g FROM Goal g WHERE g.createdAt IN (SELECT MAX(g2.createdAt) FROM Goal g2 WHERE g2.user = g.user)") - List findLatestGoalsForEachUser(); - - @Query("select g from Goal g where g.user = ?1 order by g.createdAt DESC") - List findByUserOrderByCreatedAtDesc(User user); - -} \ No newline at end of file diff --git a/src/main/java/org/winey/server/infrastructure/UserRepository.java b/src/main/java/org/winey/server/infrastructure/UserRepository.java index 9203dde..bcae789 100644 --- a/src/main/java/org/winey/server/infrastructure/UserRepository.java +++ b/src/main/java/org/winey/server/infrastructure/UserRepository.java @@ -2,8 +2,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.lang.Nullable; import org.winey.server.domain.feed.Feed; -import org.winey.server.domain.goal.Goal; import org.winey.server.domain.recommend.Recommend; import org.winey.server.domain.user.SocialType; import org.winey.server.domain.user.User; @@ -18,6 +18,9 @@ public interface UserRepository extends Repository { // READ Optional findByUserId(Long userId); + List findByFcmTokenNotNull(); + + Boolean existsBySocialIdAndSocialType(String socialId, SocialType socialType); Optional findBySocialIdAndSocialType(String socialId, SocialType socialType); diff --git a/src/main/java/org/winey/server/service/BroadCastService.java b/src/main/java/org/winey/server/service/BroadCastService.java new file mode 100644 index 0000000..14913fc --- /dev/null +++ b/src/main/java/org/winey/server/service/BroadCastService.java @@ -0,0 +1,54 @@ +package org.winey.server.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.winey.server.common.dto.ApiResponse; +import org.winey.server.controller.request.broadcast.BroadCastAllUserDto; +import org.winey.server.domain.user.User; +import org.winey.server.exception.Error; +import org.winey.server.exception.Success; +import org.winey.server.exception.model.CustomException; +import org.winey.server.infrastructure.UserRepository; +import org.winey.server.service.message.SendAllFcmDto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.sun.net.httpserver.Authenticator; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class BroadCastService { + + private final FcmService fcmService; + + private final UserRepository userRepository; + + public ApiResponse broadAllUser(BroadCastAllUserDto broadCastAllUserDto){ + List allUser = userRepository.findByFcmTokenNotNull(); + List tokenList; + if (!allUser.isEmpty()){ + try { + tokenList = allUser.stream().map( + User::getFcmToken).collect(Collectors.toList()); + System.out.println(tokenList); + fcmService.sendAllByTokenList( + SendAllFcmDto.of(tokenList, broadCastAllUserDto.getTitle(), broadCastAllUserDto.getMessage())); + return ApiResponse.success(Success.SEND_ENTIRE_MESSAGE_SUCCESS, + Success.SEND_ENTIRE_MESSAGE_SUCCESS.getMessage()); + }catch (FirebaseMessagingException | JsonProcessingException e){ + return ApiResponse.error(Error.UNPROCESSABLE_SEND_TO_FIREBASE, Error.UNPROCESSABLE_SEND_TO_FIREBASE.getMessage()); + } + } + return ApiResponse.error(Error.UNPROCESSABLE_FIND_USERS, Error.UNPROCESSABLE_FIND_USERS.getMessage()); + } + + + + +} diff --git a/src/main/java/org/winey/server/service/FcmService.java b/src/main/java/org/winey/server/service/FcmService.java index c9b0363..373db71 100644 --- a/src/main/java/org/winey/server/service/FcmService.java +++ b/src/main/java/org/winey/server/service/FcmService.java @@ -1,24 +1,36 @@ package org.winey.server.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.net.HttpHeaders; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.messaging.*; import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import org.winey.server.service.message.FcmMessage; import org.winey.server.service.message.FcmRequestDto; +import org.winey.server.service.message.SendAllFcmDto; import javax.annotation.PostConstruct; import java.io.IOException; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j @@ -33,6 +45,9 @@ public class FcmService{ @Value("${fcm.key.scope}") private String fireBaseScope; + @Value("${fcm.api.url}") + private String FCM_API_URL; + // fcm 기본 설정 진행 @PostConstruct public void init() { @@ -55,6 +70,7 @@ public void init() { } + // 알림 보내기 public void sendByTokenList(List tokenList) { @@ -90,26 +106,136 @@ public void sendByTokenList(List tokenList) { } // 좋아요나 댓글 관련 알림 로직 작성 @Async - public void sendByToken(FcmRequestDto wineyNotification) { + public CompletableFuture sendByToken(FcmRequestDto wineyNotification) throws JsonProcessingException { // 메시지 만들기 - Message message = Message.builder() - .putData("feedId", String.valueOf(wineyNotification.getFeedId())) - .putData("notiType", String.valueOf(wineyNotification.getType())) - .putData("token", wineyNotification.getToken()) - .setNotification(new Notification("위니 제국의 편지가 도착했어요.", wineyNotification.getMessage())) - .setToken(wineyNotification.getToken()) + String jsonMessage = makeSingleMessage(wineyNotification); + // 요청에 대한 응답을 받을 response + Response response; + // 알림 발송 + response = sendPushMessage(jsonMessage); + return CompletableFuture.completedFuture(response); + } + + // 실제 파이어베이스 서버로 푸시 메시지를 전송하는 메서드 + private Response sendPushMessage(String message) { + + try { + OkHttpClient client = new OkHttpClient(); + RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8")); + Request httpRequest = new Request.Builder() + .url(FCM_API_URL) // 요청을 보낼 위치 (to 파이어베이스 서버) + .post(requestBody) + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken()) + .addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8") .build(); - // 요청에 대한 응답을 받을 response - String response; + Response response = client.newCall(httpRequest).execute(); + + log.info("단일 기기 알림 전송 성공 ! successCount: 1 messages were sent successfully"); + log.info("알림 전송: {}", response.body().string()); + return response; + } catch (IOException e) { + throw new IllegalArgumentException("파일을 읽는 데 실패했습니다."); + } + } + + // Firebase에서 Access Token 가져오기 + private String getAccessToken() { + try { - System.out.println("여까지는 왔다."); - // 알림 발송 - response = FirebaseMessaging.getInstance().send(message); - System.out.println(response); - } catch (FirebaseMessagingException e) { - log.error("푸시 알림을 보내다가 오류가 발생했습니다. error info : {}", e.getMessage()); + GoogleCredentials googleCredentials = GoogleCredentials + .fromStream(new ClassPathResource(FCM_PRIVATE_KEY_PATH).getInputStream()) + .createScoped(List.of("https://www.googleapis.com/auth/cloud-platform")); + googleCredentials.refreshIfExpired(); + log.info("getAccessToken() - googleCredentials: {} ", googleCredentials.getAccessToken().getTokenValue()); + + return googleCredentials.getAccessToken().getTokenValue(); + } catch (IOException e) { + throw new IllegalArgumentException("파일을 읽는 데 실패했습니다."); + } + } + private String makeSingleMessage(FcmRequestDto wineyNotification) throws JsonProcessingException { + + try { + FcmMessage fcmMessage = FcmMessage.builder() + .message(FcmMessage.Message.builder() + .token(wineyNotification.getToken()) // 1:1 전송 시 반드시 필요한 대상 토큰 설정 + .data(FcmMessage.Data.builder() + .title("위니 제국의 편지가 도착했어요.") + .message(wineyNotification.getMessage()) + .feedId(String.valueOf(wineyNotification.getFeedId())) + .notiType(String.valueOf(wineyNotification.getType())) + .build()) + .notification(FcmMessage.Notification.builder() + .title("위니 제국의 편지가 도착했어요.") + .body(wineyNotification.getMessage()) + .build()) + .build() + ).validateOnly(false) + .build(); + return new ObjectMapper().writeValueAsString(fcmMessage); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("JSON 처리 도중에 예외가 발생했습니다."); + } + } + + // private List makeCustomMessages(SendAllFcmDto wineyNotification) throws JsonProcessingException { + // try { + // List messages = wineyNotification.getTokenList() + // .stream() + // .map(token -> FcmMessage.builder() + // .message(FcmMessage.Message.builder() + // .token(token) // 1:1 전송 시 반드시 필요한 대상 토큰 설정 + // .data(FcmMessage.Data.builder() + // .title("위니 제국의 편지가 도착했어요.") + // .message(wineyNotification.getMessage()) + // .feedId(null) + // .notiType(null) + // .build()) + // .notification(FcmMessage.Notification.builder() + // .title("위니 제국의 편지가 도착했어요.") + // .body(wineyNotification.getMessage()) + // .build()) + // .build() + // ).validateOnly(false) + // .build()).collect(Collectors.toList()); + // return messages; + // } catch (Exception e) { + // throw new IllegalArgumentException("JSON 처리 도중에 예외가 발생했습니다."); + // } + // } + + public CompletableFuture sendAllByTokenList(SendAllFcmDto wineyNotification) throws JsonProcessingException, FirebaseMessagingException { + // These registration tokens come from the client FCM SDKs. + List registrationTokens = wineyNotification.getTokenList(); + try { + MulticastMessage message = MulticastMessage.builder() + .putData("title", wineyNotification.getTitle()) + .putData("message", wineyNotification.getMessage()) + .setNotification(new Notification(wineyNotification.getTitle(), wineyNotification.getMessage())) + .addAllTokens(registrationTokens) + .build(); + BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message); + if (response.getFailureCount() > 0) { + List responses = response.getResponses(); + List failedTokens = new ArrayList<>(); + for (int i = 0; i < responses.size(); i++) { + if (!responses.get(i).isSuccessful()) { + // The order of responses corresponds to the order of the registration tokens. + failedTokens.add(registrationTokens.get(i)); + } + } + + System.out.println("List of tokens that caused failures: " + failedTokens); + } + return CompletableFuture.completedFuture(response); + } catch (Exception e){ + log.info(e.getMessage()); } + return null; + } + private Notification convertToFirebaseNotification(FcmMessage.Notification customNotification) { + return new Notification(customNotification.getTitle(), customNotification.getBody()); } } diff --git a/src/main/java/org/winey/server/service/FeedService.java b/src/main/java/org/winey/server/service/FeedService.java index 6c3a813..b31c734 100644 --- a/src/main/java/org/winey/server/service/FeedService.java +++ b/src/main/java/org/winey/server/service/FeedService.java @@ -1,6 +1,10 @@ package org.winey.server.service; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -9,25 +13,27 @@ import org.winey.server.controller.request.CreateFeedRequestDto; import org.winey.server.controller.response.PageResponseDto; import org.winey.server.controller.response.comment.CommentResponseDto; -import org.winey.server.controller.response.feed.*; +import org.winey.server.controller.response.feed.CreateFeedResponseDto; +import org.winey.server.controller.response.feed.GetAllFeedResponseDto; +import org.winey.server.controller.response.feed.GetFeedDetailResponseDto; +import org.winey.server.controller.response.feed.GetFeedResponseDto; import org.winey.server.domain.block.BlockUser; import org.winey.server.domain.feed.Feed; -import org.winey.server.domain.goal.Goal; +import org.winey.server.domain.feed.FeedType; import org.winey.server.domain.notification.NotiType; import org.winey.server.domain.notification.Notification; import org.winey.server.domain.user.User; import org.winey.server.domain.user.UserLevel; import org.winey.server.exception.Error; -import org.winey.server.exception.model.ForbiddenException; +import org.winey.server.exception.model.BadRequestException; import org.winey.server.exception.model.NotFoundException; import org.winey.server.exception.model.UnauthorizedException; -import org.winey.server.infrastructure.*; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.stream.Collectors; +import org.winey.server.infrastructure.BlockUserRepository; +import org.winey.server.infrastructure.CommentRepository; +import org.winey.server.infrastructure.FeedLikeRepository; +import org.winey.server.infrastructure.FeedRepository; +import org.winey.server.infrastructure.NotiRepository; +import org.winey.server.infrastructure.UserRepository; @Service @RequiredArgsConstructor @@ -35,7 +41,6 @@ public class FeedService { private final FeedRepository feedRepository; private final UserRepository userRepository; - private final GoalRepository goalRepository; private final FeedLikeRepository feedLikeRepository; private final CommentRepository commentRepository; private final NotiRepository notiRepository; @@ -43,93 +48,105 @@ public class FeedService { @Transactional public CreateFeedResponseDto createFeed(CreateFeedRequestDto request, Long userId, String imageUrl) { + // 1. 유저를 가져온다. User presentUser = userRepository.findByUserId(userId) .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); - Goal myGoal = goalRepository.findByUserOrderByCreatedAtDesc(presentUser).stream().findFirst() - .orElseThrow(() -> new ForbiddenException(Error.FEED_FORBIDDEN_EXCEPTION, Error.FEED_FORBIDDEN_EXCEPTION.getMessage())); //목표 설정 안하면 피드 못만듬 -> 에러처리 + // 2. 피드 타입 값을 받아와 올바른 피드유형인가 판별 + String feedType = request.getFeedType(); +// if (!FeedType.isValidFeedType(feedType)) +// throw new BadRequestException(Error.INVALID_FEEDTYPE, Error.INVALID_FEEDTYPE.getMessage()); + // 3. 피드를 생성한다. Feed feed = Feed.builder() - .feedImage(imageUrl) - .feedMoney(request.getFeedMoney()) - .feedTitle(request.getFeedTitle()) - .user(presentUser) - .goal(myGoal) - .build(); - feedRepository.save(feed); - - myGoal.updateGoalCountAndAmount(feed.getFeedMoney(), true); // 절약 금액, 피드 횟수 업데이트. - - if (myGoal.isAttained()) { - System.out.println("이미 목표달성"); - return CreateFeedResponseDto.of(feed.getFeedId(), feed.getCreatedAt()); - } + .feedImage(imageUrl) + .feedType(feedType == null || feedType.isEmpty() ? null : FeedType.valueOf(feedType)) + .feedMoney(request.getFeedMoney()) + .feedTitle(request.getFeedTitle()) + .user(presentUser) + .build(); - if (LocalDate.now().isAfter(myGoal.getTargetDate())){ - System.out.println("목표를 제한 시간 내에 이루지 못함."); - throw new ForbiddenException(Error.FEED_FORBIDDEN_EXCEPTION, Error.FEED_FORBIDDEN_EXCEPTION.getMessage()); //목표 설정 새로 하게 유도. - } + feedRepository.save(feed); - if (myGoal.getDuringGoalAmount() >= myGoal.getTargetMoney()) { - myGoal.updateIsAttained(true); // 달성여부 체크 - if (presentUser.getUserLevel().getLevelNumber() != checkUserLevelUp(presentUser)) {//userLevel 변동사항 체크, 만약에 레벨에 변동이 생겼다면? 레벨 강등 알림 생성. - switch (checkUserLevelUp(presentUser)){ - case 2: - notificationBuilderInFeed(NotiType.RANKUPTO2, presentUser); - break; - case 3: - notificationBuilderInFeed(NotiType.RANKUPTO3, presentUser); - break; - case 4: - notificationBuilderInFeed(NotiType.RANKUPTO4, presentUser); - break; - } + // 4. 유저의 피드 유형이 절약인 경우 누적 절약 금액과 누적 절약 피드 개수 업데이트한다. + if (Objects.equals(feedType, "SAVE")) presentUser.increaseSavedAmountAndCount(feed.getFeedMoney()); + + // 5. 레벨업을 체크한다. + UserLevel newUserLevel = UserLevel.calculateUserLevel(presentUser.getSavedAmount(), presentUser.getSavedCount()); + + // 레벨업 달성 여부 담는 Bool 값 + Boolean levelUpgraded = false; + + if (presentUser.getUserLevel() != newUserLevel) { + // 4-1. 레벨업한다. + presentUser.updateUserLevel(newUserLevel); + + // 4-2. 레벨업 달성 여부를 true로 수정 + levelUpgraded = true; + + // 4-3. 레벨업 알림을 생성한다. + switch (newUserLevel) { + case KNIGHT: + notificationBuilderInFeed(NotiType.RANKUPTO2, presentUser); + break; + case ARISTOCRAT: + notificationBuilderInFeed(NotiType.RANKUPTO3, presentUser); + break; + case EMPEROR: + notificationBuilderInFeed(NotiType.RANKUPTO4, presentUser); + break; + default: + break; } } - return CreateFeedResponseDto.of(feed.getFeedId(), feed.getCreatedAt()); - } - + return CreateFeedResponseDto.of(feed.getFeedId(), feed.getCreatedAt(), levelUpgraded); + } @Transactional public String deleteFeed(Long userId, Long feedId) { + // 1. 유저를 가져온다. User presentUser = userRepository.findByUserId(userId) .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); - Goal presentGoal = goalRepository.findByUserOrderByCreatedAtDesc(presentUser).stream().findFirst() - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_GOAL_EXCEPTION, Error.NOT_FOUND_GOAL_EXCEPTION.getMessage())); + + // 2. 지우고자 하는 피드를 가져온다. Feed wantDeleteFeed = feedRepository.findByFeedId(feedId) .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_FEED_EXCEPTION, Error.NOT_FOUND_FEED_EXCEPTION.getMessage())); + // 3. 피드를 작성한 유저와 현재 접속한 유저가 다르면 삭제할 수 없다. if (presentUser != wantDeleteFeed.getUser()) { throw new UnauthorizedException(Error.DELETE_UNAUTHORIZED, Error.DELETE_UNAUTHORIZED.getMessage()); // 삭제하는 사람 아니면 삭제 못함 처리. } - // 현재 삭제하고자 하는 피드의 goal 아이디 != 현재 진행 중인 goal 아이디 --> 넘어가! - if (wantDeleteFeed.getGoal().getGoalId() != presentGoal.getGoalId()) { - feedRepository.delete(wantDeleteFeed); - return wantDeleteFeed.getFeedImage(); + // 4. 절약 피드가 삭제되면 유저의 누적 절약 금액과 누적 절약 피드 개수 감소 + if (wantDeleteFeed.getFeedType() == FeedType.SAVE) { + presentUser.decreaseSavedAmountAndCount(wantDeleteFeed.getFeedMoney()); } -// if ((!presentGoal.getCreatedAt().isBefore(wantDeleteFeed.getCreatedAt())) || (!presentGoal.getTargetDate().isAfter(wantDeleteFeed.getCreatedAt().toLocalDate()))) { -// feedRepository.delete(wantDeleteFeed); -// return wantDeleteFeed.getFeedImage(); -// } - - presentGoal.updateGoalCountAndAmount(wantDeleteFeed.getFeedMoney(), false); - - if (presentUser.getUserLevel().getLevelNumber() >= 3 && (presentGoal.getTargetMoney() > presentGoal.getDuringGoalAmount())) { //귀족 이상이면 강등로직. - presentGoal.updateIsAttained(false); // 달성여부 체크 - if (presentUser.getUserLevel().getLevelNumber() != checkUserLevelUp(presentUser)) {//userLevel 변동사항 체크, 만약에 레벨에 변동이 생겼다면? 레벨 강등 알림 생성. - switch (checkUserLevelUp(presentUser)){ - case 3: - notificationBuilderInFeed(NotiType.DELETERANKDOWNTO3, presentUser); - break; - case 2: - notificationBuilderInFeed(NotiType.DELETERANKDOWNTO2, presentUser); - break; - } + // 5. 레벨다운을 체크한다. + UserLevel newUserLevel = UserLevel.calculateUserLevel(presentUser.getSavedAmount(), presentUser.getSavedCount()); + + if (presentUser.getUserLevel() != newUserLevel) { + // 4-1. 레벨다운한다. + presentUser.updateUserLevel(newUserLevel); + + // 4-2. 레벨다운 알림을 생성한다. + switch (newUserLevel) { + case COMMONER: + notificationBuilderInFeed(NotiType.DELETERANKDOWNTO1, presentUser); + break; + case KNIGHT: + notificationBuilderInFeed(NotiType.DELETERANKDOWNTO2, presentUser); + break; + case ARISTOCRAT: + notificationBuilderInFeed(NotiType.DELETERANKDOWNTO3, presentUser); + break; + default: + break; } } + + // 6. 피드를 삭제한다. notiRepository.deleteByLinkId(feedId); feedRepository.delete(wantDeleteFeed); return wantDeleteFeed.getFeedImage(); @@ -161,6 +178,7 @@ public GetAllFeedResponseDto getAllFeed(int page, Long userId) { feed.getUser().getUserId(), feed.getUser().getNickname(), feed.getUser().getUserLevel().getLevelNumber(), + feed.getFeedType() == null ? null : feed.getFeedType().getStringVal(), feed.getFeedTitle(), feed.getFeedImage(), feed.getFeedMoney(), @@ -185,6 +203,7 @@ public GetAllFeedResponseDto getMyFeed(int page, Long userId) { myFeed.getUser().getUserId(), myFeed.getUser().getNickname(), myFeed.getUser().getUserLevel().getLevelNumber(), + myFeed.getFeedType() == null ? null : myFeed.getFeedType().getStringVal(), myFeed.getFeedTitle(), myFeed.getFeedImage(), myFeed.getFeedMoney(), @@ -218,6 +237,7 @@ public GetFeedDetailResponseDto getFeedDetail(Long feedId, Long userId) { detailFeed.getUser().getUserId(), detailFeed.getUser().getNickname(), detailFeed.getUser().getUserLevel().getLevelNumber(), + detailFeed.getFeedType() == null ? null : detailFeed.getFeedType().getStringVal(), detailFeed.getFeedTitle(), detailFeed.getFeedImage(), detailFeed.getFeedMoney(), @@ -268,21 +288,4 @@ private void notificationBuilderInFeed(NotiType type, User user){ notification.updateLinkId(null); notiRepository.save(notification); } - - private int checkUserLevelUp(User presentUser) { - int userAchievedGoals = goalRepository.countByUserAndIsAttained(presentUser, true); //Goal 중 userid가 맞고 isAttained true 개수 세기 - if (userAchievedGoals < 1) { - presentUser.updateUserLevel(UserLevel.COMMONER); - return 1; - } else if (userAchievedGoals < 3) { - presentUser.updateUserLevel(UserLevel.KNIGHT); - return 2; - } else if (userAchievedGoals < 9) { - presentUser.updateUserLevel(UserLevel.ARISTOCRAT); - return 3; - } else { - presentUser.updateUserLevel(UserLevel.EMPEROR); - return 4; - } - } } diff --git a/src/main/java/org/winey/server/service/GoalService.java b/src/main/java/org/winey/server/service/GoalService.java deleted file mode 100644 index b9a073e..0000000 --- a/src/main/java/org/winey/server/service/GoalService.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.winey.server.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.winey.server.controller.request.goal.GoalRequestCreateDto; -import org.winey.server.controller.response.goal.GoalResponseCreateDto; -import org.winey.server.domain.goal.Goal; -import org.winey.server.domain.user.User; -import org.winey.server.exception.Error; -import org.winey.server.exception.model.NotFoundException; -import org.winey.server.infrastructure.GoalRepository; -import org.winey.server.infrastructure.UserRepository; - -import java.time.LocalDate; - -@Service -@RequiredArgsConstructor -public class GoalService { - private final GoalRepository goalRepository; - private final UserRepository userRepository; - - @Transactional - public GoalResponseCreateDto createGoal(GoalRequestCreateDto requestDto, Long userId) { - User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); - - LocalDate targetDate = LocalDate.now().plusDays(requestDto.getTargetDay()); - Goal createGoal = goalRepository.save(Goal.builder() - .targetMoney(requestDto.getTargetMoney()) - .targetDate(targetDate) - .user(user) - .build()); - - return GoalResponseCreateDto.of(userId, createGoal.getTargetMoney(), createGoal.getTargetDate(), createGoal.getCreatedAt()); - } -} diff --git a/src/main/java/org/winey/server/service/NotiService.java b/src/main/java/org/winey/server/service/NotiService.java index 68c96e3..e0bae86 100644 --- a/src/main/java/org/winey/server/service/NotiService.java +++ b/src/main/java/org/winey/server/service/NotiService.java @@ -1,6 +1,5 @@ package org.winey.server.service; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -9,23 +8,17 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import net.javacrumbs.shedlock.core.SchedulerLock; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.winey.server.controller.response.notification.GetAllNotiResponseDto; import org.winey.server.controller.response.notification.GetNotiResponseDto; -import org.winey.server.domain.goal.Goal; import org.winey.server.domain.notification.NotiType; import org.winey.server.domain.notification.Notification; import org.winey.server.domain.user.User; import org.winey.server.exception.Error; -import org.winey.server.exception.model.BadRequestException; import org.winey.server.exception.model.NotFoundException; -import org.winey.server.infrastructure.GoalRepository; import org.winey.server.infrastructure.NotiRepository; import org.winey.server.infrastructure.UserRepository; @@ -37,8 +30,6 @@ public class NotiService { private final NotiRepository notiRepository; private final UserRepository userRepository; - private final GoalRepository goalRepository; - private static final Logger logger = LoggerFactory.getLogger(NotiService.class); @Transactional(readOnly = true) public GetAllNotiResponseDto getAllNoti(Long userId) { @@ -77,33 +68,33 @@ public Boolean checkNewNoti(Long userId) { return notifications.size() != 0; } - @Transactional - @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") - @SchedulerLock(name = "SchedulerLock", lockAtMostForString = "PT1M", lockAtLeastForString = "PT1M") - public void checkGoalDateNotification() { - logger.info("목표 달성 체크 스케줄러 작동"); - - TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); - List allGoals = goalRepository.findLatestGoalsForEachUser(); - LocalDate today = LocalDate.now(); - - logger.info("오늘 날짜: {}", today); - - for (Goal currentGoal : allGoals) { - if (currentGoal.getTargetDate().isEqual(today.minusDays(1))) { - logger.info("알림 생성 goalID: {}", currentGoal.getGoalId()); - - Notification notification = Notification.builder() - .notiType(NotiType.GOALFAILED) - .notiReciver(currentGoal.getUser()) - .notiMessage(NotiType.GOALFAILED.getType()) - .isChecked(false) - .build(); - notification.updateLinkId(null); - notiRepository.save(notification); - } - } - } +// @Transactional +// @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") +// @SchedulerLock(name = "SchedulerLock", lockAtMostForString = "PT1M", lockAtLeastForString = "PT1M") +// public void checkGoalDateNotification() { +// logger.info("목표 달성 체크 스케줄러 작동"); +// +// TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); +// List allGoals = goalRepository.findLatestGoalsForEachUser(); +// LocalDate today = LocalDate.now(); +// +// logger.info("오늘 날짜: {}", today); +// +// for (Goal currentGoal : allGoals) { +// if (currentGoal.getTargetDate().isEqual(today.minusDays(1))) { +// logger.info("알림 생성 goalID: {}", currentGoal.getGoalId()); +// +// Notification notification = Notification.builder() +// .notiType(NotiType.GOALFAILED) +// .notiReciver(currentGoal.getUser()) +// .notiMessage(NotiType.GOALFAILED.getType()) +// .isChecked(false) +// .build(); +// notification.updateLinkId(null); +// notiRepository.save(notification); +// } +// } +// } @Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul") //혹시 모를 racing에 대비해 새벽 2시에 시작되도록 함. @Transactional diff --git a/src/main/java/org/winey/server/service/UserService.java b/src/main/java/org/winey/server/service/UserService.java index d6d93ce..05ffbf3 100644 --- a/src/main/java/org/winey/server/service/UserService.java +++ b/src/main/java/org/winey/server/service/UserService.java @@ -1,77 +1,82 @@ package org.winey.server.service; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import lombok.RequiredArgsConstructor; -import org.joda.time.LocalDateTime; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.winey.server.controller.request.UpdateFcmTokenDto; import org.winey.server.controller.request.UpdateUserNicknameDto; +import org.winey.server.controller.response.user.GetAchievementStatusResponseDto; import org.winey.server.controller.response.user.UserResponseDto; -import org.winey.server.controller.response.user.UserResponseGoalDto; -import org.winey.server.controller.response.user.UserResponseUserDto; -import org.winey.server.domain.goal.Goal; -import org.winey.server.domain.notification.NotiType; -import org.winey.server.domain.notification.Notification; import org.winey.server.domain.user.User; +import org.winey.server.domain.user.UserLevel; import org.winey.server.exception.Error; import org.winey.server.exception.model.BadRequestException; import org.winey.server.exception.model.NotFoundException; -import org.winey.server.infrastructure.GoalRepository; -import org.winey.server.infrastructure.NotiRepository; +import org.winey.server.infrastructure.FeedRepository; import org.winey.server.infrastructure.UserRepository; -import java.time.Duration; -import java.time.LocalDate; -import java.time.Period; -import java.time.temporal.ChronoUnit; -import java.util.List; - @Service @RequiredArgsConstructor public class UserService { + private final UserRepository userRepository; - private final GoalRepository goalRepository; + private final FeedRepository feedRepository; - @Transactional(readOnly = true) + @Transactional public UserResponseDto getUser(Long userId) { User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, + Error.NOT_FOUND_USER_EXCEPTION.getMessage())); - UserResponseUserDto userDto = UserResponseUserDto.of(user.getUserId(), user.getNickname(), user.getUserLevel().getName(),user.getFcmIsAllowed()); + LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2); + Long createdDday = Math.abs(ChronoUnit.DAYS.between(user.getCreatedAt(), LocalDateTime.now())) + 1; + Long amountSavedTwoWeeks = feedRepository.getSavedAmountForPeriod(user, twoWeeksAgo); + Long amountSpentTwoWeeks = feedRepository.getSpentAmountForPeriod(user, twoWeeksAgo); - List goalList = goalRepository.findByUserOrderByCreatedAtDesc(user); + UserLevel nextUserLevel = UserLevel.getNextUserLevel(user.getUserLevel()); - if (goalList.size() == 0) { - return UserResponseDto.of(userDto, null); - } - Goal presentGoal = goalList.stream().findFirst() - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_GOAL_EXCEPTION, Error.NOT_FOUND_GOAL_EXCEPTION.getMessage())); - - int targetDay = (int) Period.between(presentGoal.getCreatedAt().toLocalDate(), presentGoal.getTargetDate()).getDays(); - int dDay = (int) ChronoUnit.DAYS.between(LocalDate.now(), presentGoal.getTargetDate()); - Boolean isOver = LocalDate.now().isAfter(presentGoal.getTargetDate()); - UserResponseGoalDto goalDto = UserResponseGoalDto.of(presentGoal.getDuringGoalAmount(), presentGoal.getDuringGoalCount(), presentGoal.getTargetMoney(), targetDay, dDay, isOver, presentGoal.isAttained()); - return UserResponseDto.of(userDto, goalDto); + long savedAmountOfUser = user.getSavedAmount() == null ? 0L : user.getSavedAmount(); //기존의 getSavedAmount()했을 시 null -> 0L로 처리 + long savedCountOfUser = user.getSavedCount() == null ? 0L : user.getSavedCount(); //위와 이유 같음. + + long remainingAmount = nextUserLevel == null ? 0L : nextUserLevel.getMinimumAmount() - savedAmountOfUser; + long remainingCount = nextUserLevel == null ? 0L : nextUserLevel.getMinimumCount() - savedCountOfUser; + + return UserResponseDto.of(user.getUserId(), createdDday, user.getNickname(), + user.getUserLevel().getName(), + user.getFcmIsAllowed(), + savedAmountOfUser, + savedCountOfUser, + amountSavedTwoWeeks == null ? 0L : amountSavedTwoWeeks, + amountSpentTwoWeeks == null ? 0L : amountSpentTwoWeeks, + remainingAmount < 0 ? 0L : remainingAmount, + remainingCount < 0 ? 0L : remainingCount + ); } @Transactional public void updateNickname(Long userId, UpdateUserNicknameDto requestDto) { User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, + Error.NOT_FOUND_USER_EXCEPTION.getMessage())); user.updateNickname(requestDto.getNickname()); } + @Transactional - public void updateFcmToken(Long userId, UpdateFcmTokenDto updateFcmTokenDto){ + public void updateFcmToken(Long userId, UpdateFcmTokenDto updateFcmTokenDto) { User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, + Error.NOT_FOUND_USER_EXCEPTION.getMessage())); user.updateFcmToken(updateFcmTokenDto.getToken()); } //푸시알림 동의 여부 수정 api @Transactional - public Boolean allowedPushNotification(Long userId, Boolean fcmIsAllowed){ + public Boolean allowedPushNotification(Long userId, Boolean fcmIsAllowed) { User user = userRepository.findByUserId(userId) - .orElseThrow(()-> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, + Error.NOT_FOUND_USER_EXCEPTION.getMessage())); if (fcmIsAllowed == user.getFcmIsAllowed()) { //같은 경우면 에러가 날 수 있으니 에러 띄움. throw new BadRequestException(Error.REQUEST_VALIDATION_EXCEPTION, Error.REQUEST_VALIDATION_EXCEPTION.getMessage()); @@ -80,6 +85,27 @@ public Boolean allowedPushNotification(Long userId, Boolean fcmIsAllowed){ return fcmIsAllowed; } + @Transactional(readOnly = true) + public GetAchievementStatusResponseDto getAchievementStatus(Long userId) { + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, + Error.NOT_FOUND_USER_EXCEPTION.getMessage())); + + UserLevel nextUserLevel = UserLevel.getNextUserLevel(user.getUserLevel()); + + if (nextUserLevel == null) { + return GetAchievementStatusResponseDto.of(user.getUserLevel(), 0L, 0L); + } + + long remainingAmount = nextUserLevel.getMinimumAmount() - user.getSavedAmount(); + long remainingCount = nextUserLevel.getMinimumCount() - user.getSavedCount(); + return GetAchievementStatusResponseDto.of( + user.getUserLevel(), + remainingAmount < 0 ? 0L : remainingAmount, + remainingCount < 0 ? 0L : remainingCount + ); + } + public Boolean checkNicknameDuplicate(String nickname) { return userRepository.existsByNickname(nickname); } diff --git a/src/main/java/org/winey/server/service/auth/AuthService.java b/src/main/java/org/winey/server/service/auth/AuthService.java index 4ce32b1..8a4eff0 100644 --- a/src/main/java/org/winey/server/service/auth/AuthService.java +++ b/src/main/java/org/winey/server/service/auth/AuthService.java @@ -1,7 +1,7 @@ package org.winey.server.service.auth; -import com.sun.net.httpserver.Authenticator; -import feign.FeignException; +import java.util.Random; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,35 +9,25 @@ import org.winey.server.controller.request.auth.SignInRequestDto; import org.winey.server.controller.response.auth.SignInResponseDto; import org.winey.server.controller.response.auth.TokenResponseDto; -import org.winey.server.domain.feed.Feed; import org.winey.server.domain.notification.NotiType; import org.winey.server.domain.notification.Notification; import org.winey.server.domain.user.SocialType; - import org.winey.server.domain.user.User; import org.winey.server.exception.Error; import org.winey.server.exception.model.NotFoundException; import org.winey.server.exception.model.UnprocessableEntityException; import org.winey.server.infrastructure.BlockUserRepository; -import org.winey.server.infrastructure.FeedRepository; -import org.winey.server.infrastructure.GoalRepository; import org.winey.server.infrastructure.NotiRepository; import org.winey.server.infrastructure.UserRepository; import org.winey.server.service.auth.apple.AppleSignInService; import org.winey.server.service.auth.kakao.KakaoSignInService; -import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Random; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor public class AuthService { private final AppleSignInService appleSignInService; private final KakaoSignInService kakaoSignInService; private final JwtService jwtService; - private final UserRepository userRepository; private final BlockUserRepository blockUserRepository; @@ -53,7 +43,6 @@ public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto reque String socialId = login(socialType, socialAccessToken); Boolean isRegistered = userRepository.existsBySocialIdAndSocialType(socialId, socialType); - if (!isRegistered) { String randomString= new Random().ints(6, 0, 36).mapToObj(i -> Character.toString("abcdefghijklmnopqrstuvwxyz0123456789".charAt(i))).collect(Collectors.joining()); while (userRepository.existsByNickname("위니"+randomString)) { @@ -135,12 +124,10 @@ public void withdraw(Long userId){ if (user == null) { throw new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage()); } - System.out.println("User: " + user); - System.out.println("Goals: " + user.getGoals()); - System.out.println("Recommends: " + user.getRecommends()); - System.out.println("Feeds: " + user.getFeeds()); - System.out.println("FeedLikes: " + user.getFeedLikes()); - System.out.println("Comments: "+ user.getComments()); + if (user.getSocialType() == SocialType.KAKAO){ + String deleteSocialId = kakaoSignInService.withdrawKakao(user.getSocialId()); + System.out.println(deleteSocialId); + } // 유저가 생성한 반응과 관련된 알림 삭제 notiRepository.deleteByRequestUserId(userId); diff --git a/src/main/java/org/winey/server/service/auth/kakao/KakaoSignInService.java b/src/main/java/org/winey/server/service/auth/kakao/KakaoSignInService.java index 2d0f213..b7e7983 100644 --- a/src/main/java/org/winey/server/service/auth/kakao/KakaoSignInService.java +++ b/src/main/java/org/winey/server/service/auth/kakao/KakaoSignInService.java @@ -2,30 +2,67 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonArray; + +import java.util.Map; import lombok.RequiredArgsConstructor; + import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import org.winey.server.exception.Error; +import org.winey.server.exception.model.UnprocessableEntityException; -import java.util.Map; - +import java.util.HashMap; @Service @RequiredArgsConstructor public class KakaoSignInService { - @Value("${jwt.KAKAO_URL}") - private String KAKAO_URL; - - public String getKaKaoId(String accessToken) { - RestTemplate restTemplate = new RestTemplate(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization","Bearer "+ accessToken); - HttpEntity httpEntity = new HttpEntity<>(headers); - ResponseEntity responseData; - responseData = restTemplate.postForEntity(KAKAO_URL,httpEntity,Object.class); - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.convertValue(responseData.getBody(), Map.class).get("id").toString(); //소셜 id만 가져오는듯. - } + @Value("${jwt.KAKAO_URL}") + private String KAKAO_URL; + + @Value("${jwt.KAKAO_AK}") + private String KAKAO_AK; + + @Value("${jwt.KAKAO_WITHDRAW_URL}") + private String KAKAO_WITHDRAW; + + public String getKaKaoId(String accessToken) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + HttpEntity httpEntity = new HttpEntity<>(headers); + ResponseEntity responseData; + responseData = restTemplate.postForEntity(KAKAO_URL, httpEntity, Object.class); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.convertValue(responseData.getBody(), Map.class).get("id").toString(); //소셜 id만 가져오는듯. + } + + public String withdrawKakao(String socialId) { + ResponseEntity responseData = requestKakaoServer(socialId); + ObjectMapper objectMapper = new ObjectMapper(); + HashMap profileResponse = (HashMap)objectMapper.convertValue(responseData.getBody(), Map.class); + return profileResponse.get("id").toString(); + } + + private ResponseEntity requestKakaoServer(String socialId) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + + headers.add("Authorization", "KakaoAK " + KAKAO_AK); + + MultiValueMap param = new LinkedMultiValueMap<>(); + param.set("target_id_type", "user_id"); + param.set("target_id", socialId); + HttpEntity> httpEntity = new HttpEntity<>(param, headers); + try { + return restTemplate.postForEntity(KAKAO_WITHDRAW, httpEntity, Object.class); + } catch (Exception e) { + throw new UnprocessableEntityException(Error.UNPROCESSABLE_KAKAO_SERVER_EXCEPTION, + Error.UNPROCESSABLE_KAKAO_SERVER_EXCEPTION.getMessage()); + } + } } diff --git a/src/main/java/org/winey/server/service/message/FcmMessage.java b/src/main/java/org/winey/server/service/message/FcmMessage.java new file mode 100644 index 0000000..fe11ba3 --- /dev/null +++ b/src/main/java/org/winey/server/service/message/FcmMessage.java @@ -0,0 +1,46 @@ +package org.winey.server.service.message; + + + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class FcmMessage { + + private boolean validateOnly; + private Message message; + + @Builder + @AllArgsConstructor + @Getter + public static class Message{ + private Data data; + private String token; + private Notification notification; + } + + @Builder + @AllArgsConstructor + @Getter + public static class Data{ + private String title; + private String message; + private String feedId; + private String notiType; + } + + @Builder + @AllArgsConstructor + @Getter + public static class Notification{ + private String title; + private String body; + } + + +} diff --git a/src/main/java/org/winey/server/service/message/SendAllFcmDto.java b/src/main/java/org/winey/server/service/message/SendAllFcmDto.java new file mode 100644 index 0000000..091df36 --- /dev/null +++ b/src/main/java/org/winey/server/service/message/SendAllFcmDto.java @@ -0,0 +1,23 @@ +package org.winey.server.service.message; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.winey.server.domain.notification.NotiType; + +import java.io.Serializable; +import java.util.List; +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class SendAllFcmDto { + private List tokenList; + + private String title; + + private String message; + + public static SendAllFcmDto of(List tokenList, String title, String message){ + return new SendAllFcmDto(tokenList, title, message); + } +} diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml deleted file mode 100644 index 86bca5a..0000000 --- a/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - ${LOGS_DIR}/NotiService_INFO_${byDate}.log - true - true - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] INFO %logger{36} - %msg%n - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] INFO %logger{36} - %msg%n - - - - - - - - diff --git a/src/test/java/org/winey/server/ServerApplicationTests.java b/src/test/java/org/winey/server/ServerApplicationTests.java index 7d98d8e..7f631a4 100644 --- a/src/test/java/org/winey/server/ServerApplicationTests.java +++ b/src/test/java/org/winey/server/ServerApplicationTests.java @@ -1,13 +1,10 @@ package org.winey.server; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ServerApplicationTests { - - @Test - void contextLoads() { - } - -} +//@SpringBootTest +//class ServerApplicationTests { +// +// @Test +// void contextLoads() { +// } +// +//}